Browse Source

feat: Adding proper identifier parsing and func calls

Matthias Ladkau 3 years ago
parent
commit
1b17b25d71

+ 3 - 1
lang/ecal/README.md

@@ -72,6 +72,8 @@ func myfunc(a, b, c=1) {
 }
 ```
 
+Primitive values are passed by value, composition structures like maps and lists are passed by reference.
+
 Comments
 --
 Comments are defined with `#` as single line comments and `/*` `*/` for multiline comments.
@@ -143,7 +145,7 @@ Expression|Value
 
 Variable Assignments
 --
-A variable is a storage location for holding a value. Variables can hold single values (strings and numbers) or structures like an array or a map. Variables names can only contain [a-zA-Z] and [a-zA-Z0-9] from the second character.
+A variable is a storage bucket for holding a value. Variables can hold primitive values (strings and numbers) or composition structures like an array or a map. Variables names can only contain [a-zA-Z] and [a-zA-Z0-9] from the second character.
 
 A variable is assigned with the assign operator ':='
 ```

+ 5 - 3
lang/ecal/parser/const.go

@@ -71,9 +71,10 @@ const (
 	// Constructed tokens which are generated by the parser not the lexer
 
 	TokenSTATEMENTS // A code block
-	TokenLIST       // List value
-	TokenMAP        // MAP value
-	TokenGUARD      // Guard expressions for conditional statements
+	TokenFUNCCALL
+	TokenLIST  // List value
+	TokenMAP   // MAP value
+	TokenGUARD // Guard expressions for conditional statements
 
 	TOKENodeSYMBOLS // Used to separate symbols from other tokens in this list
 
@@ -192,6 +193,7 @@ const (
 	// Constructed tokens
 
 	NodeSTATEMENTS = "statements" // List of statements
+	NodeFUNCCALL   = "funccall"
 
 	// Condition operators
 

+ 2 - 2
lang/ecal/parser/lexer_test.go

@@ -69,14 +69,14 @@ func TestEquals(t *testing.T) {
 		return
 	}
 
-	if ok, msg := l[0].Equals(l[1], false); ok || msg != `ID is different 48 vs 7
+	if ok, msg := l[0].Equals(l[1], false); ok || msg != `ID is different 49 vs 7
 Pos is different 0 vs 5
 Val is different not vs test
 Identifier is different false vs true
 Lline is different 1 vs 2
 Lpos is different 1 vs 2
 {
-  "ID": 48,
+  "ID": 49,
   "Pos": 0,
   "Val": "not",
   "Identifier": false,

+ 55 - 13
lang/ecal/parser/parser.go

@@ -32,6 +32,7 @@ func init() {
 		// Constructed tokens
 
 		TokenSTATEMENTS: {NodeSTATEMENTS, nil, nil, nil, nil, 0, nil, nil},
+		TokenFUNCCALL:   {NodeFUNCCALL, nil, nil, nil, nil, 0, nil, nil},
 		/*
 			TokenLIST:       {NodeLIST, nil, nil, nil, 0, nil, nil},
 			TokenMAP:        {NodeMAP, nil, nil, nil, 0, nil, nil},
@@ -364,26 +365,67 @@ func ndImport(p *parser, self *ASTNode) (*ASTNode, error) {
 ndIdentifier is to parse identifiers and function calls.
 */
 func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
-	var err error
+	var parseMore, parseSegment, parseFuncCall func(parent *ASTNode) error
+
+	parseMore = func(current *ASTNode) error {
+		var err error
+
+		if p.node.Token.ID == TokenDOT {
+			err = parseSegment(current)
+		} else if p.node.Token.ID == TokenLPAREN {
+			err = parseFuncCall(current)
+		}
+
+		return err
+	}
 
-	// If the next token is a dot we got a dotted identifier
-	// collect all segments as children with a child being the
-	// parent of the next segment
+	parseSegment = func(current *ASTNode) error {
+		var err error
+		var next *ASTNode
 
-	if p.node.Token.ID == TokenDOT {
-		parent := self
-		for err == nil && p.node.Token.ID == TokenDOT {
-			if err = skipToken(p, TokenDOT); err == nil {
-				err = acceptChild(p, parent, TokenIDENTIFIER)
-				parent = parent.Children[0]
+		if err = skipToken(p, TokenDOT); err == nil {
+			next = p.node
+			if err = acceptChild(p, current, TokenIDENTIFIER); err == nil {
+				err = parseMore(next)
 			}
 		}
+
+		return err
 	}
 
-	// TODO Look at the next token p.node and determine if we need to build
-	// a function call or list/map access
+	parseFuncCall = func(current *ASTNode) error {
+		err := skipToken(p, TokenLPAREN)
 
-	return self, err
+		fc := astNodeMap[TokenFUNCCALL].instance(p, nil)
+		current.Children = append(current.Children, fc)
+
+		// Read in parameters
+
+		for err == nil && p.node.Token.ID != TokenRPAREN {
+
+			// Parse all the expressions inside the directives
+
+			exp, err := p.run(0)
+			if err == nil {
+				fc.Children = append(fc.Children, exp)
+
+				if p.node.Token.ID == TokenCOMMA {
+					err = skipToken(p, TokenCOMMA)
+				}
+			}
+		}
+
+		if err == nil {
+			err = skipToken(p, TokenRPAREN)
+			if err == nil {
+				err = parseMore(current)
+			}
+		}
+
+		return err
+	}
+
+	return self, parseMore(self)
 }
 
 // Standard left denotation functions

+ 72 - 0
lang/ecal/parser/parser_main_test.go

@@ -67,6 +67,78 @@ statements
 		t.Error("Unexpected parser output:\n", res, "expected was:\n", expectedOutput, "Error:", err)
 		return
 	}
+
+	input = `a := 1
+a().foo := x2.foo()
+a.b.c().foo := a()
+	`
+	expectedOutput = `
+statements
+  :=
+    identifier: a
+    number: 1
+  :=
+    identifier: a
+      funccall
+      identifier: foo
+    identifier: x2
+      identifier: foo
+        funccall
+  :=
+    identifier: a
+      identifier: b
+        identifier: c
+          funccall
+          identifier: foo
+    identifier: a
+      funccall
+`[1:]
+
+	if res, err := UnitTestParse("mytest", input); err != nil || fmt.Sprint(res) != expectedOutput {
+		t.Error("Unexpected parser output:\n", res, "expected was:\n", expectedOutput, "Error:", err)
+		return
+	}
+
+	input = `a(1+2).foo := x2.foo(foo)
+a.b.c(x()).foo := a(1,a(),3, x, y) + 1
+	`
+	expectedOutput = `
+statements
+  :=
+    identifier: a
+      funccall
+        plus
+          number: 1
+          number: 2
+      identifier: foo
+    identifier: x2
+      identifier: foo
+        funccall
+          identifier: foo
+  :=
+    identifier: a
+      identifier: b
+        identifier: c
+          funccall
+            identifier: x
+              funccall
+          identifier: foo
+    plus
+      identifier: a
+        funccall
+          number: 1
+          identifier: a
+            funccall
+          number: 3
+          identifier: x
+          identifier: y
+      number: 1
+`[1:]
+
+	if res, err := UnitTestParseWithPPResult("mytest", input, ""); err != nil || fmt.Sprint(res) != expectedOutput {
+		t.Error("Unexpected parser output:\n", res, "expected was:\n", expectedOutput, "Error:", err)
+		return
+	}
 }
 
 func TestCommentParsing(t *testing.T) {

+ 55 - 21
lang/ecal/parser/prettyprinter.go

@@ -32,14 +32,14 @@ var bracketPrecedenceMap map[string]bool
 func init() {
 	prettyPrinterMap = map[string]*template.Template{
 
-		NodeSTRING:            template.Must(template.New(NodeTRUE).Parse("{{.qval}}")),
-		NodeNUMBER:            template.Must(template.New(NodeTRUE).Parse("{{.val}}")),
-		NodeIDENTIFIER:        template.Must(template.New(NodeTRUE).Parse("{{.val}}")),
-		NodeIDENTIFIER + "_1": template.Must(template.New(NodeTRUE).Parse("{{.val}}.{{.c1}}")),
+		NodeSTRING: template.Must(template.New(NodeTRUE).Parse("{{.qval}}")),
+		NodeNUMBER: template.Must(template.New(NodeTRUE).Parse("{{.val}}")),
+		// NodeIDENTIFIER - Special case (handled in code)
 
 		// Constructed tokens
 
 		// NodeSTATEMENTS - Special case (handled in code)
+		// NodeFUNCCALL - Special case (handled in code)
 
 		/*
 
@@ -112,6 +112,24 @@ PrettyPrint produces pretty printed code from a given AST.
 func PrettyPrint(ast *ASTNode) (string, error) {
 	var visit func(ast *ASTNode, level int) (string, error)
 
+	ppMetaData := func(ast *ASTNode, ppString string) string {
+		ret := ppString
+
+		// Add meta data
+
+		if len(ast.Meta) > 0 {
+			for _, meta := range ast.Meta {
+				if meta.Type() == MetaDataPreComment {
+					ret = fmt.Sprintf("/*%v*/ %v", meta.Value(), ret)
+				} else if meta.Type() == MetaDataPostComment {
+					ret = fmt.Sprintf("%v #%v", ret, meta.Value())
+				}
+			}
+		}
+
+		return ret
+	}
+
 	visit = func(ast *ASTNode, level int) (string, error) {
 		var buf bytes.Buffer
 		var numChildren = len(ast.Children)
@@ -142,7 +160,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			tempKey += fmt.Sprint("_", len(tempParam))
 		}
 
-		// Handle special cases requiring children
+		// Handle special cases - children in tempParam have been resolved
 
 		if ast.Name == NodeSTATEMENTS {
 
@@ -154,8 +172,38 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 				buf.WriteString("\n")
 			}
 
-			return buf.String(), nil
+			return ppMetaData(ast, buf.String()), nil
+
+		} else if ast.Name == NodeFUNCCALL {
+
+			// For statements just concat all children
+
+			for i := 0; i < numChildren; i++ {
+				buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+				if i < numChildren-1 {
+					buf.WriteString(", ")
+				}
+			}
+
+			return ppMetaData(ast, buf.String()), nil
+
+		} else if ast.Name == NodeIDENTIFIER {
 
+			buf.WriteString(ast.Token.Val)
+
+			for i := 0; i < numChildren; i++ {
+				if ast.Children[i].Name == NodeIDENTIFIER {
+					buf.WriteString(".")
+					buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+				}
+				if ast.Children[i].Name == NodeFUNCCALL {
+					buf.WriteString("(")
+					buf.WriteString(tempParam[fmt.Sprint("c", i+1)])
+					buf.WriteString(")")
+				}
+			}
+
+			return ppMetaData(ast, buf.String()), nil
 		}
 
 		if ast.Token != nil {
@@ -178,21 +226,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 
 		errorutil.AssertOk(temp.Execute(&buf, tempParam))
 
-		ret := buf.String()
-
-		// Add meta data
-
-		if len(ast.Meta) > 0 {
-			for _, meta := range ast.Meta {
-				if meta.Type() == MetaDataPreComment {
-					ret = fmt.Sprintf("/*%v*/ %v", meta.Value(), ret)
-				} else if meta.Type() == MetaDataPostComment {
-					ret = fmt.Sprintf("%v #%v", ret, meta.Value())
-				}
-			}
-		}
-
-		return ret, nil
+		return ppMetaData(ast, buf.String()), nil
 	}
 
 	return visit(ast, 0)