WSL should support while loops
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Sep 2017 01:05:05 +0000 (01:05 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Sep 2017 01:05:05 +0000 (01:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176581

Reviewed by Filip Pizlo.

There are a few interesting pieces to this patch:
1. Because the evaluator is just a node-based descent through the tree, the implementation of break
and continue are just "throw".
2. The ReturnChecker now has to return a three-state enum because of nested blocks. The outer block
cares about if the inner block has "break" as opposed to it having no returns nor breaks/continues.
3. ReturnChecker will treat "while (true)" the same as { }, but will not descend deeper for things
like "while (3 == 3)".
4. This patch also adds a few more boolean conditional functions like operator<().
5. I added another pass which makes sure there are no bare break/return keywords outside of loops.

* WebGPUShadingLanguageRI/All.js:
* WebGPUShadingLanguageRI/Break.js: Copied from Tools/WebGPUShadingLanguageRI/CheckUnreachableCode.js.
(Break):
(Break.prototype.get origin):
(Break.prototype.toString):
* WebGPUShadingLanguageRI/CheckLoops.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
(checkLoops):
* WebGPUShadingLanguageRI/CheckReturns.js:
(checkReturns):
* WebGPUShadingLanguageRI/CheckUnreachableCode.js:
(checkUnreachableCode):
* WebGPUShadingLanguageRI/Checker.js:
* WebGPUShadingLanguageRI/Continue.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
(Continue):
(Continue.prototype.get origin):
(Continue.prototype.toString):
* WebGPUShadingLanguageRI/Evaluator.js:
(Evaluator.prototype.visitWhileLoop):
(Evaluator.prototype.visitBreak):
(Evaluator.prototype.visitContinue):
* WebGPUShadingLanguageRI/Intrinsics.js:
(Intrinsics):
* WebGPUShadingLanguageRI/Lexer.js:
(Lexer.prototype.next):
(Lexer):
* WebGPUShadingLanguageRI/LoopChecker.js: Copied from Tools/WebGPUShadingLanguageRI/ReturnChecker.js.
(LoopChecker):
(LoopChecker.prototype.visitFuncDef):
(LoopChecker.prototype.visitWhileLoop):
(LoopChecker.prototype.visitBreak):
(LoopChecker.prototype.visitContinue):
* WebGPUShadingLanguageRI/NameResolver.js:
(NameResolver.prototype.visitWhileLoop):
* WebGPUShadingLanguageRI/Node.js:
(Node.prototype.visit):
* WebGPUShadingLanguageRI/Parse.js:
(parsePossibleRelationalInequality):
(parseBreak):
(parseContinue):
(parseWhile):
* WebGPUShadingLanguageRI/Prepare.js:
(prepare):
* WebGPUShadingLanguageRI/ReturnChecker.js:
(ReturnChecker):
(ReturnChecker.prototype.visitFuncDef):
(ReturnChecker.prototype.visitBlock):
(ReturnChecker.prototype.visitIfStatement):
(ReturnChecker.prototype.visitWhileLoop):
(ReturnChecker.prototype.visitReturn):
(ReturnChecker.prototype.visitBreak):
(ReturnChecker.prototype.visitContinue):
* WebGPUShadingLanguageRI/Rewriter.js:
(Rewriter.prototype.visitContinue):
(Rewriter.prototype.visitBreak):
(Rewriter.prototype.visitWhileLoop):
(Rewriter):
* WebGPUShadingLanguageRI/StandardLibraryPrologue.js:
* WebGPUShadingLanguageRI/Test.html:
* WebGPUShadingLanguageRI/Test.js:
(TEST_returnIf):
(TEST_simpleWhile):
* WebGPUShadingLanguageRI/UnreachableCodeChecker.js:
(UnreachableCodeChecker):
(UnreachableCodeChecker.prototype.visitBlock):
* WebGPUShadingLanguageRI/Visitor.js:
(Visitor.prototype.visitWhileLoop):
(Visitor.prototype.visitContinue):
(Visitor.prototype.visitBreak):
* WebGPUShadingLanguageRI/WhileLoop.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
(WhileLoop):
(WhileLoop.prototype.get origin):
(WhileLoop.prototype.get conditional):
(WhileLoop.prototype.get body):
(WhileLoop.prototype.toString):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221842 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
Tools/ChangeLog
Tools/WebGPUShadingLanguageRI/All.js
Tools/WebGPUShadingLanguageRI/Break.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/CheckLoops.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/CheckReturns.js
Tools/WebGPUShadingLanguageRI/CheckUnreachableCode.js
Tools/WebGPUShadingLanguageRI/Checker.js
Tools/WebGPUShadingLanguageRI/Continue.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/DoWhileLoop.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/Evaluator.js
Tools/WebGPUShadingLanguageRI/Intrinsics.js
Tools/WebGPUShadingLanguageRI/Lexer.js
Tools/WebGPUShadingLanguageRI/LoopChecker.js [new file with mode: 0644]
Tools/WebGPUShadingLanguageRI/NameResolver.js
Tools/WebGPUShadingLanguageRI/Parse.js
Tools/WebGPUShadingLanguageRI/Prepare.js
Tools/WebGPUShadingLanguageRI/ReturnChecker.js
Tools/WebGPUShadingLanguageRI/Rewriter.js
Tools/WebGPUShadingLanguageRI/StandardLibrary.js
Tools/WebGPUShadingLanguageRI/Test.html
Tools/WebGPUShadingLanguageRI/Test.js
Tools/WebGPUShadingLanguageRI/UnreachableCodeChecker.js
Tools/WebGPUShadingLanguageRI/Visitor.js
Tools/WebGPUShadingLanguageRI/WhileLoop.js [new file with mode: 0644]

index 36af21f..8aa3d86 100644 (file)
@@ -1,3 +1,95 @@
+2017-09-10  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        WSL should support while loops
+        https://bugs.webkit.org/show_bug.cgi?id=176581
+
+        Reviewed by Filip Pizlo.
+
+        There are a few interesting pieces to this patch:
+        1. Because the evaluator is just a node-based descent through the tree, the implementation of break
+        and continue are just "throw".
+        2. The ReturnChecker now has to return a three-state enum because of nested blocks. The outer block
+        cares about if the inner block has "break" as opposed to it having no returns nor breaks/continues.
+        3. ReturnChecker will treat "while (true)" the same as { }, but will not descend deeper for things
+        like "while (3 == 3)".
+        4. This patch also adds a few more boolean conditional functions like operator<().
+        5. I added another pass which makes sure there are no bare break/return keywords outside of loops.
+
+        * WebGPUShadingLanguageRI/All.js:
+        * WebGPUShadingLanguageRI/Break.js: Copied from Tools/WebGPUShadingLanguageRI/CheckUnreachableCode.js.
+        (Break):
+        (Break.prototype.get origin):
+        (Break.prototype.toString):
+        * WebGPUShadingLanguageRI/CheckLoops.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
+        (checkLoops):
+        * WebGPUShadingLanguageRI/CheckReturns.js:
+        (checkReturns):
+        * WebGPUShadingLanguageRI/CheckUnreachableCode.js:
+        (checkUnreachableCode):
+        * WebGPUShadingLanguageRI/Checker.js:
+        * WebGPUShadingLanguageRI/Continue.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
+        (Continue):
+        (Continue.prototype.get origin):
+        (Continue.prototype.toString):
+        * WebGPUShadingLanguageRI/Evaluator.js:
+        (Evaluator.prototype.visitWhileLoop):
+        (Evaluator.prototype.visitBreak):
+        (Evaluator.prototype.visitContinue):
+        * WebGPUShadingLanguageRI/Intrinsics.js:
+        (Intrinsics):
+        * WebGPUShadingLanguageRI/Lexer.js:
+        (Lexer.prototype.next):
+        (Lexer):
+        * WebGPUShadingLanguageRI/LoopChecker.js: Copied from Tools/WebGPUShadingLanguageRI/ReturnChecker.js.
+        (LoopChecker):
+        (LoopChecker.prototype.visitFuncDef):
+        (LoopChecker.prototype.visitWhileLoop):
+        (LoopChecker.prototype.visitBreak):
+        (LoopChecker.prototype.visitContinue):
+        * WebGPUShadingLanguageRI/NameResolver.js:
+        (NameResolver.prototype.visitWhileLoop):
+        * WebGPUShadingLanguageRI/Node.js:
+        (Node.prototype.visit):
+        * WebGPUShadingLanguageRI/Parse.js:
+        (parsePossibleRelationalInequality):
+        (parseBreak):
+        (parseContinue):
+        (parseWhile):
+        * WebGPUShadingLanguageRI/Prepare.js:
+        (prepare):
+        * WebGPUShadingLanguageRI/ReturnChecker.js:
+        (ReturnChecker):
+        (ReturnChecker.prototype.visitFuncDef):
+        (ReturnChecker.prototype.visitBlock):
+        (ReturnChecker.prototype.visitIfStatement):
+        (ReturnChecker.prototype.visitWhileLoop):
+        (ReturnChecker.prototype.visitReturn):
+        (ReturnChecker.prototype.visitBreak):
+        (ReturnChecker.prototype.visitContinue):
+        * WebGPUShadingLanguageRI/Rewriter.js:
+        (Rewriter.prototype.visitContinue):
+        (Rewriter.prototype.visitBreak):
+        (Rewriter.prototype.visitWhileLoop):
+        (Rewriter):
+        * WebGPUShadingLanguageRI/StandardLibraryPrologue.js:
+        * WebGPUShadingLanguageRI/Test.html:
+        * WebGPUShadingLanguageRI/Test.js:
+        (TEST_returnIf):
+        (TEST_simpleWhile):
+        * WebGPUShadingLanguageRI/UnreachableCodeChecker.js:
+        (UnreachableCodeChecker):
+        (UnreachableCodeChecker.prototype.visitBlock):
+        * WebGPUShadingLanguageRI/Visitor.js:
+        (Visitor.prototype.visitWhileLoop):
+        (Visitor.prototype.visitContinue):
+        (Visitor.prototype.visitBreak):
+        * WebGPUShadingLanguageRI/WhileLoop.js: Copied from Tools/WebGPUShadingLanguageRI/CheckReturns.js.
+        (WhileLoop):
+        (WhileLoop.prototype.get origin):
+        (WhileLoop.prototype.get conditional):
+        (WhileLoop.prototype.get body):
+        (WhileLoop.prototype.toString):
+
 2017-09-07  Filip Pizlo  <fpizlo@apple.com>
 
         WSL overload resolution should not be cascading
index 83c0cc6..e7056f0 100644 (file)
@@ -39,18 +39,22 @@ load("ArrayType.js");
 load("Assignment.js");
 load("Block.js");
 load("BoolLiteral.js");
+load("Break.js");
 load("CallAssignment.js");
 load("CallExpression.js");
 load("CallFunction.js");
 load("Check.js");
 load("CheckLiteralTypes.js");
+load("CheckLoops.js");
 load("CheckRecursion.js");
 load("CheckReturns.js");
 load("CheckUnreachableCode.js");
 load("Checker.js");
 load("CommaExpression.js");
 load("ConstexprTypeParameter.js");
+load("Continue.js");
 load("DereferenceExpression.js");
+load("DoWhileLoop.js");
 load("DotExpression.js");
 load("EArrayRef.js");
 load("EBuffer.js");
@@ -76,6 +80,7 @@ load("Lexer.js");
 load("LexerToken.js");
 load("LiteralTypeChecker.js");
 load("LogicalNot.js");
+load("LoopChecker.js");
 load("MakePtrExpression.js");
 load("NameContext.js");
 load("NameResolver.js");
@@ -121,3 +126,4 @@ load("VisitingSet.js");
 load("WSyntaxError.js");
 load("WTrapError.js");
 load("WTypeError.js");
+load("WhileLoop.js");
diff --git a/Tools/WebGPUShadingLanguageRI/Break.js b/Tools/WebGPUShadingLanguageRI/Break.js
new file mode 100644 (file)
index 0000000..e972623
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class Break extends Node {
+    constructor(origin)
+    {
+        super();
+        this._origin = origin;
+    }
+    
+    get origin() { return this._origin; }
+    
+    toString()
+    {
+        return "break";
+    }
+};
+
diff --git a/Tools/WebGPUShadingLanguageRI/CheckLoops.js b/Tools/WebGPUShadingLanguageRI/CheckLoops.js
new file mode 100644 (file)
index 0000000..cb362e9
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+function checkLoops(program)
+{
+    program.visit(new LoopChecker());
+}
+
index 4b60a1b..465fe36 100644 (file)
@@ -26,6 +26,6 @@
 
 function checkReturns(program)
 {
-    program.visit(new ReturnChecker());
+    program.visit(new ReturnChecker(program));
 }
 
index d54606b..df31da9 100644 (file)
@@ -26,6 +26,6 @@
 
 function checkUnreachableCode(program)
 {
-    program.visit(new UnreachableCodeChecker());
+    program.visit(new UnreachableCodeChecker(program));
 }
 
index 8081b67..3f3e5e2 100644 (file)
@@ -237,6 +237,26 @@ class Checker extends Visitor {
         if (node.elseBody)
             node.elseBody.visit(this);
     }
+
+    visitWhileLoop(node)
+    {
+        let conditionalResultType = node.conditional.visit(this);
+        if (!conditionalResultType)
+            throw new Error("While loop conditional has no type: " + node.conditional);
+        if (!conditionalResultType.equals(this._program.intrinsics.bool))
+            throw new WError("While loop conditional isn't a bool: " + node.conditional);
+        node.body.visit(this);
+    }
+
+    visitDoWhileLoop(node)
+    {
+        node.body.visit(this);
+        let conditionalResultType = node.conditional.visit(this);
+        if (!conditionalResultType)
+            throw new Error("Do-While loop conditional has no type: " + node.conditional);
+        if (!conditionalResultType.equals(this._program.intrinsics.bool))
+            throw new WError("Do-While loop conditional isn't a bool: " + node.conditional);
+    }
     
     visitCommaExpression(node)
     {
diff --git a/Tools/WebGPUShadingLanguageRI/Continue.js b/Tools/WebGPUShadingLanguageRI/Continue.js
new file mode 100644 (file)
index 0000000..cb5cc93
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class Continue extends Node {
+    constructor(origin)
+    {
+        super();
+        this._origin = origin;
+    }
+    
+    get origin() { return this._origin; }
+    
+    toString()
+    {
+        return "Continue";
+    }
+};
+
diff --git a/Tools/WebGPUShadingLanguageRI/DoWhileLoop.js b/Tools/WebGPUShadingLanguageRI/DoWhileLoop.js
new file mode 100644 (file)
index 0000000..a2b8a21
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class DoWhileLoop extends Node {
+    constructor(origin, body, conditional)
+    {
+        super();
+        this._origin = origin;
+        this._body = body;
+        this._conditional = conditional;
+    }
+
+    get origin() { return this._origin; }
+    get body() { return this._body; }
+    get conditional() { return this._conditional; }
+
+    toString()
+    {
+        return "do " + this.body + " while (" + this.conditional + ");";
+    }
+};
+
index 1e270e2..c17bc8b 100644 (file)
@@ -159,6 +159,46 @@ class Evaluator extends Visitor {
         else if (node.elseBody)
             return node.elseBody.visit(this);
     }
+
+    visitWhileLoop(node)
+    {
+        while (node.conditional.visit(this).loadValue()) {
+            try {
+                node.body.visit(this);
+            } catch (e) {
+                if (e instanceof Break)
+                    break;
+                if (e instanceof Continue)
+                    continue;
+                throw e;
+            }
+        }
+    }
+
+    visitDoWhileLoop(node)
+    {
+        do {
+            try {
+                node.body.visit(this);
+            } catch (e) {
+                if (e instanceof Break)
+                    break;
+                if (e instanceof Continue)
+                    continue;
+                throw e;
+            }
+        } while (node.conditional.visit(this).loadValue());
+    }
+
+    visitBreak(node)
+    {
+        throw node;
+    }
+
+    visitContinue(node)
+    {
+        throw node;
+    }
     
     visitCallExpression(node)
     {
index bca4073..1bd821d 100644 (file)
@@ -178,6 +178,62 @@ class Intrinsics {
                     EPtr.box(left.loadValue() == right.loadValue());
             });
         
+        this._map.set(
+            "native bool operator<<>(int,int)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() < right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator<<>(uint,uint)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() < right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator<=<>(int,int)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() <= right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator<=<>(uint,uint)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() <= right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator><>(int,int)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() > right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator><>(uint,uint)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() > right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator>=<>(int,int)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() >= right.loadValue());
+            });
+        
+        this._map.set(
+            "native bool operator>=<>(uint,uint)",
+            func => {
+                func.implementation = ([left, right]) =>
+                    EPtr.box(left.loadValue() >= right.loadValue());
+            });
+        
         let arrayElementPtr = func => {
             func.implementation = ([ref, index], node) => {
                 ref = ref.loadValue();
index 214b36a..3604375 100644 (file)
@@ -123,7 +123,7 @@ class Lexer {
         if (/^([0-9]*\.[0-9]+)|([0-9]+\.[0-9]*)/.test(relevantText))
             return result("doubleLiteral");
         
-        if (/^->|=>|<=|==|!=|\+=|-=|\*=|\/=|%=|^=|\|=|&=|([{}()\[\]?:=+*\/,.%!~^&|<>@;-])/.test(relevantText))
+        if (/^->|>=|<=|==|!=|\+=|-=|\*=|\/=|%=|^=|\|=|&=|([{}()\[\]?:=+*\/,.%!~^&|<>@;-])/.test(relevantText))
             return result("punctuation");
         
         let remaining = relevantText.substring(0, 20).split(/\s/)[0];
diff --git a/Tools/WebGPUShadingLanguageRI/LoopChecker.js b/Tools/WebGPUShadingLanguageRI/LoopChecker.js
new file mode 100644 (file)
index 0000000..7ef9a74
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class LoopChecker extends Visitor {
+    constructor()
+    {
+        super();
+        this._loopDepth = 0;
+    }
+
+    visitFuncDef(node)
+    {
+        if (this._loopDepth != 0) {
+            throw new Error("LoopChecker does not understand nested functions.");
+        }
+        super.visitFuncDef(node);
+    }
+
+    visitWhileLoop(node)
+    {
+        node.conditional.visit(this);
+        ++this._loopDepth;
+        node.body.visit(this);
+        if (this._loopDepth == 0) {
+            throw new Error("The number of nested loops is negative!");
+        }
+        --this._loopDepth;
+    }
+
+    visitDoWhileLoop(node)
+    {
+        ++this._loopDepth;
+        node.body.visit(this);
+        if (this._loopDepth == 0) {
+            throw new Error("The number of nested loops is negative!");
+        }
+        --this._loopDepth;
+        node.conditional.visit(this);
+    }
+    
+    visitBreak(node)
+    {
+        if (this._loopDepth == 0) {
+            throw new WError("Break statement without enclosing loop!");
+        }
+        super.visitBreak(node);
+    }
+    
+    visitContinue(node)
+    {
+        if (this._loopDepth == 0) {
+            throw new WError("Continue statement without enclosing loop!");
+        }
+        super.visitContinue(node);
+    }
+}
index d44c250..97684f1 100644 (file)
@@ -95,11 +95,25 @@ class NameResolver extends Visitor {
     visitIfStatement(node)
     {
         node.conditional.visit(this);
-        // If statement's bodies might not be Blocks, so we need to explicitly give them a new context.
+        // The bodies might not be Blocks, so we need to explicitly give them a new context.
         node.body.visit(new NameResolver(new NameContext(this._nameContext)));
         if (node.elseBody)
             node.elseBody.visit(new NameResolver(new NameContext(this._nameContext)));
     }
+
+    visitWhileLoop(node)
+    {
+        node.conditional.visit(this);
+        // The bodies might not be Blocks, so we need to explicitly give them a new context.
+        node.body.visit(new NameResolver(new NameContext(this._nameContext)));
+    }
+
+    visitDoWhileLoop(node)
+    {
+        // The bodies might not be Blocks, so we need to explicitly give them a new context.
+        node.body.visit(new NameResolver(new NameContext(this._nameContext)));
+        node.conditional.visit(this);
+    }
     
     visitProtocolDecl(node)
     {
index af6cc80..eda7c7c 100644 (file)
@@ -404,7 +404,7 @@ function parse(program, origin, originKind, lineNumberOffset, text)
     
     function parsePossibleRelationalInequality()
     {
-        return parseLeftOperatorCall(["<", ">", "<=", "=>"], parsePossibleShift);
+        return parseLeftOperatorCall(["<", ">", "<=", ">="], parsePossibleShift);
     }
     
     function parsePossibleRelationalEquality()
@@ -535,6 +535,20 @@ function parse(program, origin, originKind, lineNumberOffset, text)
         consume(";");
         return new Return(origin, expression);
     }
+    
+    function parseBreak()
+    {
+        let origin = consume("break");
+        consume(";");
+        return new Break(origin);
+    }
+    
+    function parseContinue()
+    {
+        let origin = consume("continue");
+        consume(";");
+        return new Continue(origin);
+    }
 
     function parseIfStatement()
     {
@@ -548,6 +562,27 @@ function parse(program, origin, originKind, lineNumberOffset, text)
             elseBody = parseStatement();
         return new IfStatement(origin, new CallExpression(conditional.origin, "bool", [], [conditional]), body, elseBody);
     }
+
+    function parseWhile()
+    {
+        let origin = consume("while");
+        consume("(");
+        let conditional = parseExpression();
+        consume(")");
+        let body = parseStatement();
+        return new WhileLoop(origin, new CallExpression(conditional.origin, "bool", [], [conditional]), body);
+    }
+
+    function parseDo()
+    {
+        let origin = consume("do");
+        let body = parseStatement();
+        consume("while");
+        consume("(");
+        let conditional = parseExpression();
+        consume(")");
+        return new DoWhileLoop(origin, body, new CallExpression(conditional.origin, "bool", [], [conditional]));
+    }
     
     function parseVariableDecls()
     {
index a050b17..a859118 100644 (file)
@@ -35,6 +35,7 @@ function prepare(origin, lineNumberOffset, text)
     checkLiteralTypes(program);
     checkReturns(program);
     checkUnreachableCode(program);
+    checkLoops(program);
     checkRecursion(program);
     inline(program);
     return program;
index 472c0d0..2f048b7 100644 (file)
 "use strict";
 
 class ReturnChecker extends Visitor {
+    constructor(program)
+    {
+        super();
+        this.returnStyle = {
+            DefinitelyReturns: "Definitely Returns",
+            DefinitelyDoesntReturn: "Definitely Doesn't Return",
+            HasntReturnedYet: "Hasn't Returned Yet"
+        };
+        this._program = program;
+    }
+
     visitFuncDef(node)
     {
         if (node.returnType.equals(node.program.intrinsics.void))
             return;
         
-        if (!node.body.visit(this))
+        let bodyValue = node.body.visit(this);
+        if (bodyValue == this.returnStyle.DefinitelyDoesntReturn || bodyValue == this.returnStyle.HasntReturnedYet)
             throw new WTypeError(node.origin.originString, "Function does not return");
     }
     
     visitBlock(node)
     {
-        // FIXME: This isn't right for break/continue.
-        // https://bugs.webkit.org/show_bug.cgi?id=176263
-        return node.statements.reduce((result, statement) => result || statement.visit(this), false);
+        for (let statement of node.statements) {
+            switch (statement.visit(this)) {
+            case this.returnStyle.DefinitelyReturns:
+                return this.returnStyle.DefinitelyReturns;
+            case this.returnStyle.DefinitelyDoesntReturn:
+                return this.returnStyle.DefinitelyDoesntReturn;
+            case this.returnStyle.HasntReturnedYet:
+                continue;
+            }
+        }
+        return this.returnStyle.HasntReturnedYet;
     }
 
     visitIfStatement(node)
     {
-        return node.elseBody && node.body.visit(this) && node.elseBody.visit(this);
+        if (node.elseBody) {
+            let bodyValue = node.body.visit(this);
+            let elseValue = node.elseBody.visit(this);
+            if (bodyValue == this.returnStyle.DefinitelyReturns && elseValue == this.returnStyle.DefinitelyReturns)
+                return this.returnStyle.DefinitelyReturns;
+            if (bodyValue == this.returnStyle.DefinitelyDoesntReturn && elseValue == this.returnStyle.DefinitelyDoesntReturn)
+                return this.returnStyle.DefinitelyDoesntReturn;
+        }
+        return this.returnStyle.HasntReturnedYet;
     }
 
-    // If a loop returns, then it counts only if the loop is guaranteed to run at least once.
+    visitWhileLoop(node)
+    {
+        if (node.conditional instanceof CallExpression && node.conditional.isCast && node.conditional.returnType instanceof TypeRef && node.conditional.returnType.equals(this._program.intrinsics.bool) && node.conditional.argumentList.length == 1 && node.conditional.argumentList[0].list.length == 1 && node.conditional.argumentList[0].list[0] instanceof BoolLiteral && node.conditional.argumentList[0].list[0].value) {
+            switch (node.body.visit(this)) {
+            case this.returnStyle.DefinitelyReturns:
+                return this.returnStyle.DefinitelyReturns;
+            case this.returnStyle.DefinitelyDoesntReturn:
+            case this.returnStyle.HasntReturnedYet:
+                return this.returnStyle.HasntReturnedYet;
+            }
+        } else
+            node.conditional.visit(this);
+        return this.returnStyle.HasntReturnedYet;
+    }
+
+    visitDoWhileLoop(node)
+    {
+        let result;
+        switch (node.body.visit(this)) {
+        case this.returnStyle.DefinitelyReturns:
+            result = this.returnStyle.DefinitelyReturns;
+        case this.returnStyle.DefinitelyDoesntReturn:
+        case this.returnStyle.HasntReturnedYet:
+            result = this.returnStyle.HasntReturnedYet;
+        }
+        node.conditional.visit(this);
+        return result;
+    }
     
     visitReturn(node)
     {
-        return true;
+        return this.returnStyle.DefinitelyReturns;
+    }
+
+    visitBreak(node)
+    {
+        return this.returnStyle.DefinitelyDoesntReturn;
+    }
+
+    visitContinue(node)
+    {
+        return this.returnStyle.DefinitelyDoesntReturn;
     }
 }
index 21d5a1a..df25cd6 100644 (file)
@@ -200,6 +200,16 @@ class Rewriter extends VisitorBase {
         return new Return(node.origin, node.value ? node.value.visit(this) : null);
     }
     
+    visitContinue(node)
+    {
+        return new Continue(node.origin);
+    }
+    
+    visitBreak(node)
+    {
+        return new Break(node.origin);
+    }
+    
     visitIntLiteral(node)
     {
         let result = new IntLiteral(node.origin, node.value);
@@ -283,5 +293,15 @@ class Rewriter extends VisitorBase {
     {
         return new IfStatement(node.origin, node.conditional.visit(this), node.body.visit(this), node.elseBody ? node.elseBody.visit(this) : undefined);
     }
+
+    visitWhileLoop(node)
+    {
+        return new WhileLoop(node.origin, node.conditional.visit(this), node.body.visit(this));
+    }
+
+    visitDoWhileLoop(node)
+    {
+        return new DoWhileLoop(node.origin, node.body.visit(this), node.conditional.visit(this));
+    }
 }
 
index 603134d..047c5a9 100644 (file)
@@ -52,6 +52,14 @@ native uint operator/(uint, uint);
 native bool operator==(int, int);
 native bool operator==(uint, uint);
 native bool operator==(bool, bool);
+native bool operator<(int, int);
+native bool operator<(uint, uint);
+native bool operator<=(int, int);
+native bool operator<=(uint, uint);
+native bool operator>(int, int);
+native bool operator>(uint, uint);
+native bool operator>=(int, int);
+native bool operator>=(uint, uint);
 
 protocol Equatable {
     bool operator==(Equatable, Equatable);
index 2b41e28..e77ab19 100644 (file)
 <script src="Assignment.js"></script>
 <script src="Block.js"></script>
 <script src="BoolLiteral.js"></script>
+<script src="Break.js"></script>
 <script src="CallAssignment.js"></script>
 <script src="CallExpression.js"></script>
 <script src="CallFunction.js"></script>
 <script src="Check.js"></script>
 <script src="CheckLiteralTypes.js"></script>
+<script src="CheckLoops.js"></script>
 <script src="CheckRecursion.js"></script>
 <script src="CheckReturns.js"></script>
 <script src="CheckUnreachableCode.js"></script>
 <script src="Checker.js"></script>
 <script src="CommaExpression.js"></script>
 <script src="ConstexprTypeParameter.js"></script>
+<script src="Continue.js"></script>
+<script src="DoWhileLoop.js"></script>
 <script src="DotExpression.js"></script>
 <script src="DereferenceExpression.js"></script>
 <script src="EArrayRef.js"></script>
@@ -52,6 +56,7 @@
 <script src="LexerToken.js"></script>
 <script src="LiteralTypeChecker.js"></script>
 <script src="LogicalNot.js"></script>
+<script src="LoopChecker.js"></script>
 <script src="MakePtrExpression.js"></script>
 <script src="NameContext.js"></script>
 <script src="NameResolver.js"></script>
 <script src="WSyntaxError.js"></script>
 <script src="WTrapError.js"></script>
 <script src="WTypeError.js"></script>
+<script src="WhileLoop.js"></script>
 
 <script src="Test.js"></script>
 
index a642c44..f9cb1c4 100644 (file)
@@ -1334,6 +1334,29 @@ function TEST_returnIf()
             }
         `),
         (e) => e instanceof WTypeError);
+    program = doPrep(`
+        int foo(int x)
+        {
+            int y = 6;
+            if (x == 7)
+                int y = 8;
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 7)]), 6);
+}
+
+function TEST_simpleWhile()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            while (x < 13)
+                x = x * 2;
+            return x;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 16);
 }
 
 function TEST_protocolMonoPolySigDoublePolyDefExplicit()
@@ -1400,6 +1423,180 @@ function TEST_ambiguousOverloadTieBreak()
     `);
 }
 
+function TEST_break()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            while (true) {
+                x = x * 2;
+                if (x >= 7)
+                    break;
+            }
+            return x;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 10)]), 20);
+    program = doPrep(`
+        int foo(int x)
+        {
+            while (true) {
+                while (true) {
+                    x = x * 2;
+                    if (x >= 7)
+                        break;
+                }
+                x = x - 1;
+                break;
+            }
+            return x;
+            
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 7);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 10)]), 19);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                while (true) {
+                    {
+                        break;
+                    }
+                    x = x + 1;
+                }
+                return x;
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                break;
+                return x;
+            }
+        `),
+        (e) => e instanceof WTypeError);
+    program = doPrep(`
+            int foo(int x)
+            {
+                while (true) {
+                    if (x == 7) {
+                        break;
+                    }
+                    x = x + 1;
+                }
+                return x;
+            }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 7);
+    program = doPrep(`
+            int foo(int x)
+            {
+                while (true) {
+                    break;
+                }
+                return x;
+            }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 1);
+    program = doPrep(`
+            int foo()
+            {
+                while (true) {
+                    return 7;
+                }
+            }
+    `);
+    checkInt(program, callFunction(program, "foo", [], []), 7);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                while(true) {
+                    break;
+                    return 7;
+                }
+            }
+        `),
+        (e) => e instanceof WTypeError);
+}
+
+function TEST_continue()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            while (x < 10) {
+                if (x == 8) {
+                    x = x + 1;
+                    continue;
+                }
+                x = x * 2;
+            }
+            return x;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 18);
+    checkFail(
+        () => doPrep(`
+            int foo(int x)
+            {
+                continue;
+                return x;
+                
+            }
+        `),
+        (e) => e instanceof WTypeError);
+}
+
+function TEST_doWhile()
+{
+    let program = doPrep(`
+        int foo(int x)
+        {
+            int y = 7;
+            do {
+                y = 8;
+                break;
+            } while (x < 10);
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 8);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 11)]), 8);
+    program = doPrep(`
+        int foo(int x)
+        {
+            int y = 7;
+            do {
+                y = 8;
+                break;
+            } while (y == 7);
+            return y;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 1)]), 8);
+    program = doPrep(`
+        int foo(int x)
+        {
+            int sum = 0;
+            do {
+                if (x == 11) {
+                    x = 15;
+                    continue;
+                }
+                sum = sum + x;
+                x = x + 1;
+            } while (x < 13);
+            return sum;
+        }
+    `);
+    checkInt(program, callFunction(program, "foo", [], [makeInt(program, 9)]), 19);
+}
+
 let filter = /.*/; // run everything by default
 if (this["arguments"]) {
     for (let i = 0; i < arguments.length; i++) {
index 5ef1731..11c8e19 100644 (file)
 "use strict";
 
 class UnreachableCodeChecker extends Visitor {
-    constructor()
+    constructor(program)
     {
         super();
-        this._returnChecker = new ReturnChecker();
+        this._returnChecker = new ReturnChecker(program);
     }
     
     visitBlock(node)
     {
+        super.visitBlock(node);
         for (let i = 0; i < node.statements.length - 1; ++i) {
-            let statement = node.statements[i];
-            // FIXME: Need to also check if this statement can break or continue.
-            // https://bugs.webkit.org/show_bug.cgi?id=176263
-            if (statement.visit(this._returnChecker))
+            switch(node.statements[i].visit(this._returnChecker)) {
+            case this._returnChecker.returnStyle.DefinitelyReturns:
+            case this._returnChecker.returnStyle.DefinitelyDoesntReturn:
                 throw new WTypeError(
                     node.statements[i + 1].origin.originString,
                     "Unreachable code");
+            case this._returnChecker.returnStyle.HasntReturnedYet:
+                continue;
+            }
         }
     }
 }
index 95d15ce..1b5d416 100644 (file)
@@ -215,12 +215,32 @@ class Visitor extends VisitorBase {
         if (node.elseBody)
             node.elseBody.visit(this);
     }
+    
+    visitWhileLoop(node)
+    {
+        node.conditional.visit(this);
+        node.body.visit(this);
+    }
+    
+    visitDoWhileLoop(node)
+    {
+        node.body.visit(this);
+        node.conditional.visit(this);
+    }
 
     visitReturn(node)
     {
         if (node.value)
             node.value.visit(this);
     }
+
+    visitContinue(node)
+    {
+    }
+
+    visitBreak(node)
+    {
+    }
     
     visitIntLiteral(node)
     {
diff --git a/Tools/WebGPUShadingLanguageRI/WhileLoop.js b/Tools/WebGPUShadingLanguageRI/WhileLoop.js
new file mode 100644 (file)
index 0000000..4c19c34
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class WhileLoop extends Node {
+    constructor(origin, conditional, body)
+    {
+        super();
+        this._origin = origin;
+        this._conditional = conditional;
+        this._body = body;
+    }
+
+    get origin() { return this._origin; }
+    get conditional() { return this._conditional; }
+    get body() { return this._body; }
+
+    toString()
+    {
+        return "while (" + this.conditional + ") " + this.body;
+    }
+};
+