Browse Source

feat: Adding conditional and loop statements to ECAL

Matthias Ladkau 3 years ago
parent
commit
5bd4fcb22c

+ 6 - 10
lang/ecal/parser/const.go

@@ -79,7 +79,7 @@ const (
 	TokenLIST       // List value
 	TokenMAP        // MAP value
 	TokenPARAMS     // Function parameters
-	TokenGUARD      // Guard expressions for conditional statements
+	TokenGUARD      // Conditional statements
 
 	TOKENodeSYMBOLS // Used to separate symbols from other tokens in this list
 
@@ -206,6 +206,7 @@ const (
 	NodeLIST       = "list"       // List value
 	NodeMAP        = "map"        // Map value
 	NodePARAMS     = "params"     // Function parameters
+	NodeGUARD      = "guard"      // Guard expressions for conditional statements
 
 	// Condition operators
 
@@ -272,18 +273,13 @@ const (
 	NodeFALSE = "false"
 	NodeNULL  = "null"
 
-/*
-
-	NodeGUARD      = "guard"      // Guard expressions for conditional statements
-
-	// Block statements
+	// Conditional statements
 
-	NodeCOND = "cond"
-	NodeLOOP = "loop"
+	NodeIF = "if"
 
-	// Single statements
+	// Loop statements
 
+	NodeLOOP     = "loop"
 	NodeBREAK    = "break"
 	NodeCONTINUE = "continue"
-*/
 )

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

@@ -37,9 +37,7 @@ func init() {
 		TokenLIST:       {NodeLIST, nil, nil, nil, nil, 0, nil, nil},
 		TokenMAP:        {NodeMAP, nil, nil, nil, nil, 0, nil, nil},
 		TokenPARAMS:     {NodePARAMS, nil, nil, nil, nil, 0, nil, nil},
-		/*
-			TokenGUARD:      {NodeGUARD, nil, nil, nil, 0, nil, nil},
-		*/
+		TokenGUARD:      {NodeGUARD, nil, nil, nil, nil, 0, nil, nil},
 
 		// Condition operators
 
@@ -122,19 +120,17 @@ func init() {
 		TokenTRUE:  {NodeTRUE, nil, nil, nil, nil, 0, ndTerm, nil},
 		TokenNULL:  {NodeNULL, nil, nil, nil, nil, 0, ndTerm, nil},
 
-		/*
-			// Conditional statements
+		// Conditional statements
 
-			TokenIF
-			TokenELIF
-			TokenELSE
+		TokenIF:   {NodeIF, nil, nil, nil, nil, 0, ndGuard, nil},
+		TokenELIF: {"", nil, nil, nil, nil, 0, nil, nil},
+		TokenELSE: {"", nil, nil, nil, nil, 0, nil, nil},
 
-			// Loop statements
+		// Loop statements
 
-			TokenFOR
-			TokenBREAK
-			TokenCONTINUE
-		*/
+		TokenFOR:      {NodeLOOP, nil, nil, nil, nil, 0, ndLoop, nil},
+		TokenBREAK:    {NodeBREAK, nil, nil, nil, nil, 0, ndTerm, nil},
+		TokenCONTINUE: {NodeCONTINUE, nil, nil, nil, nil, 0, ndTerm, nil},
 	}
 }
 
@@ -659,6 +655,92 @@ func ndMap(p *parser, self *ASTNode) (*ASTNode, error) {
 	return st, skipToken(p, TokenRBRACE)
 }
 
+/*
+ndGuard is used to parse a conditional statement.
+*/
+func ndGuard(p *parser, self *ASTNode) (*ASTNode, error) {
+	var err error
+
+	parseGuardAndStatements := func() error {
+
+		// The brace starts statements while parsing the expression of an if statement
+
+		nodeMapEntryBak := astNodeMap[TokenLBRACE]
+		astNodeMap[TokenLBRACE] = &ASTNode{"", nil, nil, nil, nil, 0, parseInnerStatements, nil}
+
+		exp, err := p.run(0)
+
+		astNodeMap[TokenLBRACE] = nodeMapEntryBak
+
+		if err == nil {
+			g := astNodeMap[TokenGUARD].instance(p, nil)
+			g.Children = append(g.Children, exp)
+			self.Children = append(self.Children, g)
+
+			_, err = parseInnerStatements(p, self)
+		}
+
+		return err
+	}
+
+	if err = parseGuardAndStatements(); err == nil {
+
+		for err == nil && p.node.Token.ID == TokenELIF {
+
+			// Parse an elif
+
+			if err = skipToken(p, TokenELIF); err == nil {
+				err = parseGuardAndStatements()
+			}
+		}
+
+		if err == nil && p.node.Token.ID == TokenELSE {
+
+			// Parse else
+
+			if err = skipToken(p, TokenELSE); err == nil {
+				g := astNodeMap[TokenGUARD].instance(p, nil)
+				g.Children = append(g.Children, astNodeMap[TokenTRUE].instance(p, nil))
+				self.Children = append(self.Children, g)
+
+				_, err = parseInnerStatements(p, self)
+			}
+		}
+	}
+
+	return self, err
+}
+
+/*
+ndLoop is used to parse a loop statement.
+*/
+func ndLoop(p *parser, self *ASTNode) (*ASTNode, error) {
+
+	// The brace starts statements while parsing the expression of a for statement
+
+	nodeMapEntryBak := astNodeMap[TokenLBRACE]
+	astNodeMap[TokenLBRACE] = &ASTNode{"", nil, nil, nil, nil, 0, parseInnerStatements, nil}
+
+	exp, err := p.run(0)
+
+	astNodeMap[TokenLBRACE] = nodeMapEntryBak
+
+	if err == nil {
+		g := exp
+
+		if exp.Token.ID != TokenIN {
+			g = astNodeMap[TokenGUARD].instance(p, nil)
+			g.Children = append(g.Children, exp)
+		}
+
+		self.Children = append(self.Children, g)
+
+		_, err = parseInnerStatements(p, self)
+	}
+
+	return self, err
+}
+
 // Standard left denotation functions
 // ==================================
 

+ 294 - 0
lang/ecal/parser/parser_statement_test.go

@@ -0,0 +1,294 @@
+/*
+ * Public Domain Software
+ *
+ * I (Matthias Ladkau) am the author of the source code in this file.
+ * I have placed the source code in this file in the public domain.
+ *
+ * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+package parser
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestLoopParsing(t *testing.T) {
+
+	input := `
+for a != null {
+	print(1);
+	print(2);
+	break
+	continue
+}
+`
+	expectedOutput := `
+loop
+  guard
+    !=
+      identifier: a
+      null
+  statements
+    identifier: print
+      funccall
+        number: 1
+    identifier: print
+      funccall
+        number: 2
+    break
+    continue
+`[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 = `
+for a in range(1,2) {
+	print(1);
+	print(2)
+}
+`
+	expectedOutput = `
+loop
+  in
+    identifier: a
+    identifier: range
+      funccall
+        number: 1
+        number: 2
+  statements
+    identifier: print
+      funccall
+        number: 1
+    identifier: print
+      funccall
+        number: 2
+`[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 = `
+for a < 1 and b > 2 {
+	print(1)
+	print(2)
+}
+`
+	expectedOutput = `
+loop
+  guard
+    and
+      <
+        identifier: a
+        number: 1
+      >
+        identifier: b
+        number: 2
+  statements
+    identifier: print
+      funccall
+        number: 1
+    identifier: print
+      funccall
+        number: 2
+`[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 = `
+for a in range(1,2,3) {
+	==
+}
+`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (==) (Line:3 Pos:2)" {
+		t.Error(err)
+		return
+	}
+
+	input = `
+for a in == {
+	@print(1)
+}
+`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (==) (Line:2 Pos:10)" {
+		t.Error(err)
+		return
+	}
+}
+
+func TestConditionalParsing(t *testing.T) {
+
+	input := `
+if a == b or c < d {
+    print(1);
+	foo := 1
+} elif x or y {
+	x := 1; y := 2; p := {
+		1:2
+	}
+} elif true {
+	x := 1; y := 2
+} else {
+	x := 1
+}
+`
+	expectedOutput := `
+if
+  guard
+    or
+      ==
+        identifier: a
+        identifier: b
+      <
+        identifier: c
+        identifier: d
+  statements
+    identifier: print
+      funccall
+        number: 1
+    :=
+      identifier: foo
+      number: 1
+  guard
+    or
+      identifier: x
+      identifier: y
+  statements
+    :=
+      identifier: x
+      number: 1
+    :=
+      identifier: y
+      number: 2
+    :=
+      identifier: p
+      map
+        kvp
+          number: 1
+          number: 2
+  guard
+    true
+  statements
+    :=
+      identifier: x
+      number: 1
+    :=
+      identifier: y
+      number: 2
+  guard
+    true
+  statements
+    :=
+      identifier: x
+      number: 1
+`[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 = `
+if a {
+    print(1)
+} elif b {
+	print(2)
+}
+`
+	expectedOutput = `
+if
+  guard
+    identifier: a
+  statements
+    identifier: print
+      funccall
+        number: 1
+  guard
+    identifier: b
+  statements
+    identifier: print
+      funccall
+        number: 2
+`[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 = `
+if a {
+    print(1)
+} else {
+	print(2)
+}
+`
+	expectedOutput = `
+if
+  guard
+    identifier: a
+  statements
+    identifier: print
+      funccall
+        number: 1
+  guard
+    true
+  statements
+    identifier: print
+      funccall
+        number: 2
+`[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
+	}
+
+	// Test error output
+
+	input = `else { b }`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (<ELSE>) (Line:1 Pos:1)" {
+		t.Error(err)
+		return
+	}
+
+	input = `elif { b }`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (<ELIF>) (Line:1 Pos:1)" {
+		t.Error(err)
+		return
+	}
+
+	input = `if { b }`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Unexpected end (Line:1 Pos:8)" {
+		t.Error(err)
+		return
+	}
+
+	input = `if == { b }`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (==) (Line:1 Pos:4)" {
+		t.Error(err)
+		return
+	}
+
+	input = `if x { b } elif == { c }`
+	if _, err := UnitTestParse("mytest", input); err.Error() !=
+		"Parse error in mytest: Term cannot start an expression (==) (Line:1 Pos:17)" {
+		t.Error(err)
+		return
+	}
+}

+ 70 - 25
lang/ecal/parser/prettyprinter.go

@@ -44,6 +44,32 @@ func init() {
 		// TokenLIST - Special case (handled in code)
 		// TokenMAP - Special case (handled in code)
 		// TokenPARAMS - Special case (handled in code)
+		NodeGUARD + "_1": template.Must(template.New(NodeGUARD).Parse("{{.c1}}")),
+
+		// Condition operators
+
+		NodeGEQ + "_2": template.Must(template.New(NodeGEQ).Parse("{{.c1}} >= {{.c2}}")),
+		NodeLEQ + "_2": template.Must(template.New(NodeLEQ).Parse("{{.c1}} <= {{.c2}}")),
+		NodeNEQ + "_2": template.Must(template.New(NodeNEQ).Parse("{{.c1}} != {{.c2}}")),
+		NodeEQ + "_2":  template.Must(template.New(NodeEQ).Parse("{{.c1}} == {{.c2}}")),
+		NodeGT + "_2":  template.Must(template.New(NodeGT).Parse("{{.c1}} > {{.c2}}")),
+		NodeLT + "_2":  template.Must(template.New(NodeLT).Parse("{{.c1}} < {{.c2}}")),
+
+		// Separators
+
+		NodeKVP + "_2":    template.Must(template.New(NodeKVP).Parse("{{.c1}} : {{.c2}}")),
+		NodePRESET + "_2": template.Must(template.New(NodePRESET).Parse("{{.c1}}={{.c2}}")),
+
+		// Arithmetic operators
+
+		NodePLUS + "_1":   template.Must(template.New(NodePLUS).Parse("+{{.c1}}")),
+		NodePLUS + "_2":   template.Must(template.New(NodePLUS).Parse("{{.c1}} + {{.c2}}")),
+		NodeMINUS + "_1":  template.Must(template.New(NodeMINUS).Parse("-{{.c1}}")),
+		NodeMINUS + "_2":  template.Must(template.New(NodeMINUS).Parse("{{.c1}} - {{.c2}}")),
+		NodeTIMES + "_2":  template.Must(template.New(NodeTIMES).Parse("{{.c1}} * {{.c2}}")),
+		NodeDIV + "_2":    template.Must(template.New(NodeDIV).Parse("{{.c1}} / {{.c2}}")),
+		NodeMODINT + "_2": template.Must(template.New(NodeMODINT).Parse("{{.c1}} % {{.c2}}")),
+		NodeDIVINT + "_2": template.Must(template.New(NodeDIVINT).Parse("{{.c1}} // {{.c2}}")),
 
 		// Assignment statement
 
@@ -62,17 +88,6 @@ func init() {
 		NodePRIORITY + "_1":   template.Must(template.New(NodePRIORITY).Parse("priority {{.c1}}")),
 		NodeSUPPRESSES + "_1": template.Must(template.New(NodeSUPPRESSES).Parse("suppresses {{.c1}}")),
 
-		// Arithmetic operators
-
-		NodePLUS + "_1":   template.Must(template.New(NodePLUS).Parse("+{{.c1}}")),
-		NodePLUS + "_2":   template.Must(template.New(NodePLUS).Parse("{{.c1}} + {{.c2}}")),
-		NodeMINUS + "_1":  template.Must(template.New(NodeMINUS).Parse("-{{.c1}}")),
-		NodeMINUS + "_2":  template.Must(template.New(NodeMINUS).Parse("{{.c1}} - {{.c2}}")),
-		NodeTIMES + "_2":  template.Must(template.New(NodeTIMES).Parse("{{.c1}} * {{.c2}}")),
-		NodeDIV + "_2":    template.Must(template.New(NodeDIV).Parse("{{.c1}} / {{.c2}}")),
-		NodeMODINT + "_2": template.Must(template.New(NodeMODINT).Parse("{{.c1}} % {{.c2}}")),
-		NodeDIVINT + "_2": template.Must(template.New(NodeDIVINT).Parse("{{.c1}} // {{.c2}}")),
-
 		// Function definition
 
 		NodeFUNC + "_3":   template.Must(template.New(NodeFUNC).Parse("func {{.c1}}{{.c2}} {\n{{.c3}}}")),
@@ -93,23 +108,23 @@ func init() {
 		NodeHASSUFFIX + "_2": template.Must(template.New(NodeHASSUFFIX).Parse("{{.c1}} hassuffix {{.c2}}")),
 		NodeNOTIN + "_2":     template.Must(template.New(NodeNOTIN).Parse("{{.c1}} notin {{.c2}}")),
 
-		NodeGEQ + "_2": template.Must(template.New(NodeGEQ).Parse("{{.c1}} >= {{.c2}}")),
-		NodeLEQ + "_2": template.Must(template.New(NodeLEQ).Parse("{{.c1}} <= {{.c2}}")),
-		NodeNEQ + "_2": template.Must(template.New(NodeNEQ).Parse("{{.c1}} != {{.c2}}")),
-		NodeEQ + "_2":  template.Must(template.New(NodeEQ).Parse("{{.c1}} == {{.c2}}")),
-		NodeGT + "_2":  template.Must(template.New(NodeGT).Parse("{{.c1}} > {{.c2}}")),
-		NodeLT + "_2":  template.Must(template.New(NodeLT).Parse("{{.c1}} < {{.c2}}")),
-
-		// Separators
-
-		NodeKVP + "_2":    template.Must(template.New(NodeKVP).Parse("{{.c1}} : {{.c2}}")),
-		NodePRESET + "_2": template.Must(template.New(NodePRESET).Parse("{{.c1}}={{.c2}}")),
-
-		// Constants
+		// Constant terminals
 
 		NodeTRUE:  template.Must(template.New(NodeTRUE).Parse("true")),
 		NodeFALSE: template.Must(template.New(NodeFALSE).Parse("false")),
 		NodeNULL:  template.Must(template.New(NodeNULL).Parse("null")),
+
+		// Conditional statements
+
+		// TokenIF - Special case (handled in code)
+		// TokenELIF - Special case (handled in code)
+		// TokenELSE - Special case (handled in code)
+
+		// Loop statements
+
+		NodeLOOP + "_2": template.Must(template.New(NodeLOOP).Parse("for {{.c1}} {\n{{.c2}}}\n")),
+		NodeBREAK:       template.Must(template.New(NodeBREAK).Parse("break")),
+		NodeCONTINUE:    template.Must(template.New(NodeCONTINUE).Parse("continue")),
 	}
 
 	bracketPrecedenceMap = map[string]bool{
@@ -204,7 +219,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			buf.WriteString(tempParam[fmt.Sprint("c", len(ast.Children))])
 			buf.WriteString("}\n")
 
-			return buf.String(), nil
+			return ppMetaData(ast, buf.String()), nil
 
 		} else if ast.Name == NodeFUNCCALL {
 
@@ -237,6 +252,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			}
 
 			return ppMetaData(ast, buf.String()), nil
+
 		} else if ast.Name == NodeLIST {
 
 			buf.WriteString("[")
@@ -262,6 +278,7 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			buf.WriteString("}")
 
 			return ppMetaData(ast, buf.String()), nil
+
 		} else if ast.Name == NodePARAMS {
 
 			buf.WriteString("(")
@@ -273,6 +290,34 @@ func PrettyPrint(ast *ASTNode) (string, error) {
 			buf.WriteString(tempParam[fmt.Sprint("c", i)])
 			buf.WriteString(")")
 
+			return ppMetaData(ast, buf.String()), nil
+
+		} else if ast.Name == NodeIF {
+
+			writeGUARD := func(child int) {
+				buf.WriteString(tempParam[fmt.Sprint("c", child)])
+				buf.WriteString(" {\n")
+				buf.WriteString(tempParam[fmt.Sprint("c", child+1)])
+				buf.WriteString("}")
+			}
+
+			buf.WriteString("if ")
+
+			writeGUARD(1)
+
+			for i := 0; i < len(ast.Children); i += 2 {
+				if i+2 == len(ast.Children) && ast.Children[i].Children[0].Name == NodeTRUE {
+					buf.WriteString(" else {\n")
+					buf.WriteString(tempParam[fmt.Sprint("c", i+2)])
+					buf.WriteString("}")
+				} else if i > 0 {
+					buf.WriteString(" elif ")
+					writeGUARD(i + 1)
+				}
+			}
+
+			buf.WriteString("\n")
+
 			return ppMetaData(ast, buf.String()), nil
 		}