瀏覽代碼

feat: Adding dotted identifiers

Matthias Ladkau 5 年之前
父節點
當前提交
4a0a82b286

File diff suppressed because it is too large
+ 16 - 3
lang/ecal/README.md


+ 22 - 15
lang/ecal/parser/const.go

@@ -115,14 +115,15 @@ const (
 
 	TokenASSIGN
 
-	// Data structure access
-
-	TokenACCESS
-
 	// The colon '' has a context specific meaning and is checked by the parser
 
 	TOKENodeKEYWORDS // Used to separate keywords from other tokens in this list
 
+	// Import statement
+
+	TokenIMPORT
+	TokenAS
+
 	// Sink definition
 
 	TokenSINK
@@ -192,9 +193,14 @@ const (
 
 	NodeSTATEMENTS = "statements" // List of statements
 
-	// Assignment statement
+	// Condition operators
 
-	NodeASSIGN = ":="
+	NodeGEQ = ">="
+	NodeLEQ = "<="
+	NodeNEQ = "!="
+	NodeEQ  = "=="
+	NodeGT  = ">"
+	NodeLT  = "<"
 
 	// Arithmetic operators
 
@@ -205,10 +211,18 @@ const (
 	NodeMODINT = "modint"
 	NodeDIVINT = "divint"
 
+	// Assignment statement
+
+	NodeASSIGN = ":="
+
+	// Import statement
+
+	NodeIMPORT = "import"
+
 	// Boolean operators
 
-	NodeOR  = "or"
 	NodeAND = "and"
+	NodeOR  = "or"
 	NodeNOT = "not"
 
 	// Condition operators
@@ -219,14 +233,7 @@ const (
 	NodeHASSUFFIX = "hassuffix"
 	NodeNOTIN     = "notin"
 
-	NodeGEQ = ">="
-	NodeLEQ = "<="
-	NodeNEQ = "!="
-	NodeEQ  = "=="
-	NodeGT  = ">"
-	NodeLT  = "<"
-
-	// Constants
+	// Constant terminals
 
 	NodeTRUE  = "true"
 	NodeFALSE = "false"

+ 5 - 0
lang/ecal/parser/lexer.go

@@ -174,6 +174,11 @@ KeywordMap is a map of keywords - these require spaces between them
 */
 var KeywordMap = map[string]LexTokenID{
 
+	// Import statement
+
+	"import": TokenIMPORT,
+	"as":     TokenAS,
+
 	// Sink definition
 
 	"sink":       TokenSINK,

+ 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 47 vs 7
+	if ok, msg := l[0].Equals(l[1], false); ok || msg != `ID is different 48 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": 47,
+  "ID": 48,
   "Pos": 0,
   "Val": "not",
   "Identifier": false,

+ 77 - 16
lang/ecal/parser/parser.go

@@ -27,18 +27,26 @@ func init() {
 
 		TokenSTRING:     {NodeSTRING, nil, nil, nil, nil, 0, ndTerm, nil},
 		TokenNUMBER:     {NodeNUMBER, nil, nil, nil, nil, 0, ndTerm, nil},
-		TokenIDENTIFIER: {NodeIDENTIFIER, nil, nil, nil, nil, 0, ndTerm, nil},
+		TokenIDENTIFIER: {NodeIDENTIFIER, nil, nil, nil, nil, 0, ndIdentifier, nil},
 
 		// Constructed tokens
 
 		TokenSTATEMENTS: {NodeSTATEMENTS, nil, nil, nil, nil, 0, nil, nil},
-		TokenSEMICOLON:  {"", nil, nil, nil, nil, 0, nil, nil},
 		/*
 			TokenLIST:       {NodeLIST, nil, nil, nil, 0, nil, nil},
 			TokenMAP:        {NodeMAP, nil, nil, nil, 0, nil, nil},
 			TokenGUARD:      {NodeGUARD, nil, nil, nil, 0, nil, nil},
 		*/
 
+		// Condition operators
+
+		TokenGEQ: {NodeGEQ, nil, nil, nil, nil, 60, nil, ldInfix},
+		TokenLEQ: {NodeLEQ, nil, nil, nil, nil, 60, nil, ldInfix},
+		TokenNEQ: {NodeNEQ, nil, nil, nil, nil, 60, nil, ldInfix},
+		TokenEQ:  {NodeEQ, nil, nil, nil, nil, 60, nil, ldInfix},
+		TokenGT:  {NodeGT, nil, nil, nil, nil, 60, nil, ldInfix},
+		TokenLT:  {NodeLT, nil, nil, nil, nil, 60, nil, ldInfix},
+
 		// Grouping symbols
 
 		TokenLPAREN: {"", nil, nil, nil, nil, 150, ndInner, nil},
@@ -46,13 +54,11 @@ func init() {
 
 		// Separators
 
-		TokenCOMMA: {"", nil, nil, nil, nil, 0, nil, nil},
+		TokenDOT:       {"", nil, nil, nil, nil, 0, nil, nil},
+		TokenCOMMA:     {"", nil, nil, nil, nil, 0, nil, nil},
+		TokenSEMICOLON: {"", nil, nil, nil, nil, 0, nil, nil},
 
-		// Assignment statement
-
-		TokenASSIGN: {NodeASSIGN, nil, nil, nil, nil, 10, nil, ldInfix},
-
-		// Simple arithmetic expressions
+		// Arithmetic operators
 
 		TokenPLUS:   {NodePLUS, nil, nil, nil, nil, 110, ndPrefix, ldInfix},
 		TokenMINUS:  {NodeMINUS, nil, nil, nil, nil, 110, ndPrefix, ldInfix},
@@ -61,6 +67,15 @@ func init() {
 		TokenDIVINT: {NodeDIVINT, nil, nil, nil, nil, 120, nil, ldInfix},
 		TokenMODINT: {NodeMODINT, nil, nil, nil, nil, 120, nil, ldInfix},
 
+		// Assignment statement
+
+		TokenASSIGN: {NodeASSIGN, nil, nil, nil, nil, 10, nil, ldInfix},
+
+		// Import statement
+
+		TokenIMPORT: {NodeIMPORT, nil, nil, nil, nil, 0, ndImport, nil},
+		TokenAS:     {"", nil, nil, nil, nil, 0, ndImport, nil},
+
 		// Boolean operators
 
 		TokenOR:  {NodeOR, nil, nil, nil, nil, 30, nil, ldInfix},
@@ -75,14 +90,7 @@ func init() {
 		TokenHASSUFFIX: {NodeHASSUFFIX, nil, nil, nil, nil, 60, nil, ldInfix},
 		TokenNOTIN:     {NodeNOTIN, nil, nil, nil, nil, 60, nil, ldInfix},
 
-		TokenGEQ: {NodeGEQ, nil, nil, nil, nil, 60, nil, ldInfix},
-		TokenLEQ: {NodeLEQ, nil, nil, nil, nil, 60, nil, ldInfix},
-		TokenNEQ: {NodeNEQ, nil, nil, nil, nil, 60, nil, ldInfix},
-		TokenEQ:  {NodeEQ, nil, nil, nil, nil, 60, nil, ldInfix},
-		TokenGT:  {NodeGT, nil, nil, nil, nil, 60, nil, ldInfix},
-		TokenLT:  {NodeLT, nil, nil, nil, nil, 60, nil, ldInfix},
-
-		// Constants
+		// Constant terminals
 
 		TokenFALSE: {NodeFALSE, nil, nil, nil, nil, 0, ndTerm, nil},
 		TokenTRUE:  {NodeTRUE, nil, nil, nil, nil, 0, ndTerm, nil},
@@ -325,6 +333,59 @@ func ndPrefix(p *parser, self *ASTNode) (*ASTNode, error) {
 	return self, nil
 }
 
+// Null denotation functions for specific expressions
+// ==================================================
+
+/*
+ndImport is used to parse imports.
+*/
+func ndImport(p *parser, self *ASTNode) (*ASTNode, error) {
+
+	// Must specify a file path
+
+	err := acceptChild(p, self, TokenSTRING)
+
+	if err == nil {
+
+		// Must specify AS
+
+		if err = skipToken(p, TokenAS); err == nil {
+
+			// Must specify an identifier
+
+			err = acceptChild(p, self, TokenIDENTIFIER)
+		}
+	}
+
+	return self, err
+}
+
+/*
+ndIdentifier is to parse identifiers and function calls.
+*/
+func ndIdentifier(p *parser, self *ASTNode) (*ASTNode, error) {
+	var err error
+
+	// 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
+
+	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]
+			}
+		}
+	}
+
+	// TODO Look at the next token p.node and determine if we need to build
+	// a function call or list/map access
+
+	return self, err
+}
+
 // Standard left denotation functions
 // ==================================
 

+ 59 - 0
lang/ecal/parser/parser_func_test.go

@@ -0,0 +1,59 @@
+/*
+ * 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 TestImportParsing(t *testing.T) {
+
+	input := `import "foo/bar.ecal" as foobar
+	i := foobar`
+	expectedOutput := `
+statements
+  import
+    string: 'foo/bar.ecal'
+    identifier: foobar
+  :=
+    identifier: i
+    identifier: foobar
+`[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
+	}
+}
+
+/*
+TODO:
+
+func TestFunctionCalling(t *testing.T) {
+
+	input := `import "foo/bar.ecal" as foobar
+	foobar.test()`
+	expectedOutput := `
+statements
+  import
+    string: 'foo/bar.ecal'
+    identifier: foobar
+  :=
+    identifier: i
+    identifier: foobar
+`[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
+	}
+}
+*/

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

@@ -39,6 +39,36 @@ statements
 	}
 }
 
+func TestIdentifierParsing(t *testing.T) {
+
+	input := `a := 1
+	a.foo := 2
+	a.b.c.foo := a.b
+	`
+	expectedOutput := `
+statements
+  :=
+    identifier: a
+    number: 1
+  :=
+    identifier: a
+      identifier: foo
+    number: 2
+  :=
+    identifier: a
+      identifier: b
+        identifier: c
+          identifier: foo
+    identifier: a
+      identifier: b
+`[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
+	}
+}
+
 func TestCommentParsing(t *testing.T) {
 
 	// Comment parsing without statements

+ 10 - 3
lang/ecal/parser/prettyprinter.go

@@ -32,9 +32,10 @@ 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}}")),
+		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}}")),
 
 		// Constructed tokens
 
@@ -50,8 +51,14 @@ func init() {
 			NodeASSIGN = ":="
 		*/
 
+		// Assignment statement
+
 		NodeASSIGN + "_2": template.Must(template.New(NodeMINUS).Parse("{{.c1}} := {{.c2}}")),
 
+		// Import statement
+
+		NodeIMPORT + "_2": template.Must(template.New(NodeMINUS).Parse("import {{.c1}} as {{.c2}}")),
+
 		// Arithmetic operators
 
 		NodePLUS + "_1":   template.Must(template.New(NodeMINUS).Parse("+{{.c1}}")),