#FEATURE by exept
authorClaus Gittinger <cg@exept.de>
Fri, 18 Oct 2019 21:48:14 +0200
changeset 1207 e152ab21db25
parent 1206 0682bf6ff98e
child 1208 d3ffb183c8f9
#FEATURE by exept class: JavaScriptParser added: #objectLiteralOrDescructuringExpression changed: #constOrVarDeclarationFor:isConst: #primaryExpression class: JavaScriptParser class comment/format in: #examples class: JavaScriptParser::JavaScriptMultiVariableNode class definition added: #slots #slots: class: JavaScriptParser::JavaScriptMultiVariableNode class added: #documentation
JavaScriptParser.st
--- a/JavaScriptParser.st	Fri Oct 18 16:42:23 2019 +0200
+++ b/JavaScriptParser.st	Fri Oct 18 21:48:14 2019 +0200
@@ -151,6 +151,13 @@
 	privateIn:JavaScriptParser
 !
 
+ParseNode subclass:#JavaScriptMultiVariableNode
+	instanceVariableNames:'slots'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:JavaScriptParser
+!
+
 ReturnNode subclass:#JavaScriptReturnNode
 	instanceVariableNames:'environmentToReturnFrom'
 	classVariableNames:''
@@ -465,6 +472,15 @@
         }
 '
                                                                                         [exEnd]
+                                                                                        [exBegin]
+    JavaScriptParser parseExpression:'{a:10 , b:20}'  
+                                                                                        [exEnd]
+                                                                                        [exBegin]
+    JavaScriptParser parseFunction:'function foo() { var foo = {a:10 , b:20}; }'  
+                                                                                        [exEnd]
+                                                                                        [exBegin]
+    JavaScriptParser parseExpression:'function foo() { var {a,b} = foo(); }'  
+                                                                                        [exEnd]
 "
 !
 
@@ -2265,7 +2281,7 @@
 
     |isConst isStatic isLet var varIndex initValueExpression locals value 
      firstInitializer lastInitializer initializer varNode pos1 pos2 
-     declareVariable varName|
+     declareVariable varName vars|
 
     isStatic := isLet := false.
     isConst := isConstIn.
@@ -2301,90 +2317,105 @@
     
     [true] whileTrue:[
         (tokenType == #Identifier) ifFalse:[
-            self parseError:'''identifier'' expected.'.
-            ^ nil.
-        ].
-
-        varName := tokenName.
-        declareVariable := 
-            [
-                isConst ifTrue:[
-                    isStatic ifTrue:[
-                        var := self declareStaticConstant:varName.
-                    ] ifFalse:[
-                        var := self declareConstant:varName inEnvironment:anEnvironment.
-                    ].
-                ] ifFalse:[
-                    isStatic ifTrue:[
-                        var := self declareStaticVariable:varName.
-                    ] ifFalse:[
-                        var := self declareVariable:varName inEnvironment:anEnvironment.
-                    ].
-                ].    
+            tokenType == ${ ifFalse:[
+                self parseError:'''identifier'' expected.'.
+                ^ nil.
             ].
-            
-        pos1 := tokenPosition.
-        pos2 := tokenPosition + tokenName size - 1.
-        self nextToken.
-
-        tokenType == $= ifTrue:[
+            self nextToken.
+            vars := self objectLiteralOrDescructuringExpression.
+            vars class == JavaScriptMultiVariableNode ifFalse:[
+                self parseError:'''identifier'' or destruct expression expected.'.
+            ].
+            tokenType == $= ifFalse:[
+                self parseError:'''='' expected.'.
+            ].
             self nextToken.
             initValueExpression := self nonCommaExpression.
-            declareVariable value.
-            var expressionForSetup:initValueExpression.
-            ((tokenType == $;) or:[tokenType == $,]) ifFalse:[
-                self parseError:'Expected '';'' or '','' after init expression.'.
+            self isSyntaxHighlighter ifFalse:[
+                self parseError:'destruct expressions are not yet supported'
             ].
-            (interactiveMode or:[isDoIt]) ifTrue:[
-                "/ perform the assignment
-                value := initValueExpression evaluateIn:anEnvironment.
-                var value:value.
-                locals isEmptyOrNil ifTrue:[
-                    anEnvironment _localVariables:(locals := IdentityDictionary new).
+        ] ifTrue:[
+            varName := tokenName.
+            declareVariable := 
+                [
+                    isConst ifTrue:[
+                        isStatic ifTrue:[
+                            var := self declareStaticConstant:varName.
+                        ] ifFalse:[
+                            var := self declareConstant:varName inEnvironment:anEnvironment.
+                        ].
+                    ] ifFalse:[
+                        isStatic ifTrue:[
+                            var := self declareStaticVariable:varName.
+                        ] ifFalse:[
+                            var := self declareVariable:varName inEnvironment:anEnvironment.
+                        ].
+                    ].    
                 ].
-                locals at:var name put:var.
-            ] ifFalse:[
-                "/ if already in the real statements section, do the assignment here...
-                firstInitializer isNil ifTrue:[
-                    firstInitializer := lastInitializer := JavaScriptStatementNode new.
-                ] ifFalse:[
-                    lastInitializer nextStatement: JavaScriptStatementNode new.
-                    lastInitializer := lastInitializer nextStatement.
+
+            pos1 := tokenPosition.
+            pos2 := tokenPosition + tokenName size - 1.
+            self nextToken.
+
+            tokenType == $= ifTrue:[
+                self nextToken.
+                initValueExpression := self nonCommaExpression.
+                declareVariable value.
+                var expressionForSetup:initValueExpression.
+                ((tokenType == $;) or:[tokenType == $,]) ifFalse:[
+                    self parseError:'Expected '';'' or '','' after init expression.'.
                 ].
-                (anEnvironment notNil and:[anEnvironment isInnerFunction]) ifTrue:[
-                    varNode := JavaScriptVariableNode
-                                    type:#BlockVariable
-                                    name:var name
-                                    token:var
-                                    index:var index
-                                    block:anEnvironment 
-                                    from:anEnvironment    
+                (interactiveMode or:[isDoIt]) ifTrue:[
+                    "/ perform the assignment
+                    value := initValueExpression evaluateIn:anEnvironment.
+                    var value:value.
+                    locals isEmptyOrNil ifTrue:[
+                        anEnvironment _localVariables:(locals := IdentityDictionary new).
+                    ].
+                    locals at:var name put:var.
                 ] ifFalse:[
-                    (anEnvironment notNil and:[anEnvironment isJavaScriptClassNode]) ifTrue:[
-                        varNode := JavaScriptVariableNode 
-                                        type:(isStatic ifTrue:[#ClassVariable] ifFalse:[#InstanceVariable])
+                    "/ if already in the real statements section, do the assignment here...
+                    firstInitializer isNil ifTrue:[
+                        firstInitializer := lastInitializer := JavaScriptStatementNode new.
+                    ] ifFalse:[
+                        lastInitializer nextStatement: JavaScriptStatementNode new.
+                        lastInitializer := lastInitializer nextStatement.
+                    ].
+                    (anEnvironment notNil and:[anEnvironment isInnerFunction]) ifTrue:[
+                        varNode := JavaScriptVariableNode
+                                        type:#BlockVariable
                                         name:var name
                                         token:var
-                                        index:nil.
+                                        index:var index
+                                        block:anEnvironment 
+                                        from:anEnvironment    
                     ] ifFalse:[
-                        varNode := JavaScriptVariableNode 
-                                        type:#MethodVariable
-                                        name:var name
-                                        token:var
-                                        index:var index.
+                        (anEnvironment notNil and:[anEnvironment isJavaScriptClassNode]) ifTrue:[
+                            varNode := JavaScriptVariableNode 
+                                            type:(isStatic ifTrue:[#ClassVariable] ifFalse:[#InstanceVariable])
+                                            name:var name
+                                            token:var
+                                            index:nil.
+                        ] ifFalse:[
+                            varNode := JavaScriptVariableNode 
+                                            type:#MethodVariable
+                                            name:var name
+                                            token:var
+                                            index:var index.
+                        ].
+                    ].
+                    varNode startPosition: pos1 endPosition: pos2.
+                    lastInitializer expression:(initializer := (JavaScriptAssignmentNode variable:varNode expression:initValueExpression) parent: lastInitializer).
+                    anEnvironment isNil ifTrue:[
+                    ] ifFalse:[
+                        anEnvironment isJavaScriptClassNode ifFalse:[
+                            var expressionForSetup:nil.
+                        ].
                     ].
                 ].
-                varNode startPosition: pos1 endPosition: pos2.
-                lastInitializer expression:(initializer := (JavaScriptAssignmentNode variable:varNode expression:initValueExpression) parent: lastInitializer).
-                anEnvironment isNil ifTrue:[
-                ] ifFalse:[
-                    anEnvironment isJavaScriptClassNode ifFalse:[
-                        var expressionForSetup:nil.
-                    ].
-                ].
+            ] ifFalse:[
+                declareVariable value.
             ].
-        ] ifFalse:[
-            declareVariable value.
         ].
 
         tokenType == $; ifTrue:[
@@ -4013,6 +4044,112 @@
     "Modified: / 22-06-2019 / 01:45:50 / Claus Gittinger"
 !
 
+objectLiteralOrDescructuringExpression
+    "opening brace has already been read
+
+        objectLiteral -> '{' [ slotName ':' literal { , slotName ':' literal } ] '}'
+        desctruct     -> '{' [ slotName , slotName , ... '}'
+    "
+
+    |slots name anyNonConstant expr obj 
+     jso jc mkarr pos1 pos2 node isLiteral isDestruct|
+
+    anyNonConstant := isLiteral := isDestruct := false.
+    pos1 := tokenLastEndPosition.
+
+    slots := OrderedCollection new.
+    [ tokenType == $} ] whileFalse:[
+        (tokenType == #Identifier) ifTrue:[
+            name := tokenName.
+        ] ifFalse:[
+            (tokenType == #String) ifTrue:[
+                name := tokenValue asSymbol.
+            ] ifFalse:[
+                self parseError:'Identifier expected'.
+            ]
+        ].
+        self nextToken.
+
+        tokenType == $: ifTrue:[
+            isDestruct ifTrue:[
+                self parseError:'"," expected in destruct'.
+            ].
+            isLiteral := true
+        ] ifFalse:[
+            tokenType == $, ifTrue:[
+                isLiteral ifTrue:[
+                    self parseError:'":" expected in object literal'.
+                ].
+                isDestruct := true
+            ] ifFalse:[
+                tokenType == $} ifFalse:[
+                    self parseError:(isLiteral 
+                                        ifTrue:['":" expected'] 
+                                        ifFalse:[
+                                            isDestruct 
+                                                ifTrue:['"," expected']
+                                                ifFalse:['":" or "," expected']]).
+                ].
+                isLiteral ifTrue:[
+                    self parseError:'":" expected in object literal'.
+                ].
+                isDestruct := true
+            ].
+        ].
+
+        isLiteral ifTrue:[
+            self nextToken.
+            expr := self nonCommaExpression.
+            slots add:(name asSymbol -> expr).
+            expr isConstant ifFalse:[
+                anyNonConstant := true
+            ].
+        ] ifFalse:[
+            slots add:(name asSymbol).
+        ].
+
+        tokenType == $} ifFalse:[
+            tokenType == $, ifFalse:[
+                self parseError:'"," expected'.
+            ].
+            self nextToken.
+        ].
+    ].
+
+    self nextToken.
+    pos2 := tokenLastEndPosition.
+
+    isDestruct ifTrue:[
+        ^ JavaScriptMultiVariableNode new
+            slots:slots;
+            startPosition:pos1 endPosition:pos2;
+            yourself
+    ].
+
+    anyNonConstant ifTrue:[
+        "/ generate a structure dynamically
+        jso := VariableNode globalNamed:#JavaScriptObject.
+        jso startPosition:-1 endPosition:-1.
+
+        jc := JavaScriptConstantNode type:#Array value:(slots map:#key) asArray.
+        jc startPosition:-1 endPosition:-1.
+
+        mkarr := Parser genMakeArrayWith:(slots map:#value).
+        mkarr startPosition:pos1 endPosition:pos2.
+
+        node := MessageNode receiver:jso selector:#newWith:values: arg1:jc arg2:mkarr.
+    ] ifFalse:[
+        "/ generate a structure
+        obj := JavaScriptObject newWith:(slots map:#key) values:((slots map:#value) map:#value).
+        node := JavaScriptConstantNode type:#Object value:obj.
+    ].
+    node startPosition:pos1 endPosition:pos2.
+    ^ node
+
+    "Modified: / 16-07-2012 / 21:15:24 / cg"
+    "Modified: / 22-06-2019 / 01:45:50 / Claus Gittinger"
+!
+
 powerExpression
     "powerExpr -> unaryExpr ** unaryExpr
     "
@@ -4134,7 +4271,10 @@
     ].
     tokenType == ${ ifTrue:[
         self nextToken.
-        expr := self objectLiteral.
+        expr := self objectLiteralOrDescructuringExpression.
+        expr class == JavaScriptMultiVariableNode ifTrue:[
+            self halt.
+        ].
         ^ expr
     ].
 
@@ -8026,6 +8166,25 @@
     ^ visitor visitJavaScriptBinaryNode:self 
 ! !
 
+!JavaScriptParser::JavaScriptMultiVariableNode class methodsFor:'documentation'!
+
+documentation
+"
+    the left side of a destructuring expression;
+    eg. { foo, bar } = ...
+"
+! !
+
+!JavaScriptParser::JavaScriptMultiVariableNode methodsFor:'accessing'!
+
+slots
+    ^ slots
+!
+
+slots:something
+    slots := something.
+! !
+
 !JavaScriptParser::JavaScriptReturnNode methodsFor:'accessing'!
 
 environmentToReturnFrom