From 90dd58262bb93adee67b23b85da4568af4d405dd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 11 Apr 2018 15:31:30 -0700 Subject: [PATCH] Parse variable declaration and reference syntax Gamelan currently playing: Sabi Lulungan co-authored-by: Connor Walsh --- evil.go | 11 +++ forest.go | 8 +-- lexer_test.go | 1 + parser.go | 115 +++++++++++++++++++++++++++--- symbols.go | 6 +- test/test_identifiers_keyword.doc | 1 + test/variable.doc | 5 +- 7 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 test/test_identifiers_keyword.doc diff --git a/evil.go b/evil.go index 60c710a..d1c66f8 100644 --- a/evil.go +++ b/evil.go @@ -2,6 +2,8 @@ package dockerlang import ( "context" + "encoding/json" + "fmt" "github.com/docker/docker/api/types/container" ) @@ -18,6 +20,15 @@ import ( // Local, Args, Global func (c *Compterpreter) Evaluate() error { + b, _ := json.MarshalIndent(c.StackTree, " ", " ") + fmt.Println(string(b)) + + /* + for _, operand := range c.StackTree.Operands { + b, _ := json.MarshalIndent(operand, " ", " ") + fmt.Println(string(b)) + } + */ r, err := c.StackTree.Operands[0].Eval() wait, errChan := executer.Docker.ContainerWait( diff --git a/forest.go b/forest.go index 9111ecc..de4eead 100644 --- a/forest.go +++ b/forest.go @@ -60,16 +60,16 @@ func (e *Expr) Eval() (string, error) { // but is should overwrite the Eval function since it does that differently. type IfConditional struct{} -type Variable struct { - Literal +type Identifier struct { + Type string Name string Bound bool } -func (v *Variable) Eval() (string, error) { +func (i *Identifier) Eval() (string, error) { return executer.Run( &ExecutionData{ - ComputationType: VARIABLE_IDENTIFIER, + ComputationType: i.Type, }, ) } diff --git a/lexer_test.go b/lexer_test.go index d4ee3c4..e8892ed 100644 --- a/lexer_test.go +++ b/lexer_test.go @@ -140,6 +140,7 @@ func (s *LexerSuite) TestTokenizeIdentifier_Keyword() { break } s.EqualValues(compt.CurrentToken.Value, op) + s.EqualValues(compt.CurrentToken.Type, KEYWORD) } } diff --git a/parser.go b/parser.go index 687638c..01e3f18 100644 --- a/parser.go +++ b/parser.go @@ -1,5 +1,7 @@ package dockerlang +import "fmt" + type Stack struct { Elements []AST } @@ -38,23 +40,39 @@ func (s *Stack) Length() int { func (c *Compterpreter) Parse() error { // build the global StackTree, for all expressions in the global scope as part of an implicit anonymous function var ( - opsStack = NewStack() - exprStack = NewStack() + opsStack = NewStack() + exprStack = NewStack() + parenCount = 0 ) // TODO make the root expression an EXIT_OPERATOR which will operate // on all program roots. c.StackTree = &Expr{} for _, token := range c.Tokens { + if parenCount < 0 { + fmt.Println("UNBALANCED PARENS") + // TODO (cw,mr|4.11.2018) be more specific -____- + return DockerlangSyntaxError + } + switch token.Type { case OPERATOR: opsStack.Push(&Expr{Op: token.Value, Arity: OP_TO_ARITY[token.Value]}) case INT: exprStack.Push(&Literal{Type: INT, Value: token.Value}) + case IDENTIFIER: + // is this a declaration or a reference? + identifier, err := c.StackTree.ParseIdentifier(token, opsStack) + if err != nil { + return err + } + + exprStack.Push(identifier) case PUNCTUATION: switch token.Value { case "(": // TODO: eventually check a puntaution stack for syntax checking + parenCount++ case ")": // shit gets real var opsExpr = opsStack.Pop().(*Expr) @@ -68,23 +86,102 @@ func (c *Compterpreter) Parse() error { } // push modified ops expr onto the expr stack exprStack.Push(opsExpr) + + parenCount-- default: // whatever } } + // if there is only 1 element in the expressionStack, we have successfully parsed + // a single expression + if exprStack.Length() == 1 && parenCount == 0 { + // there should be nothing on the operations stack! + if opsStack.Peek() != nil { + fmt.Println("error in loop") + fmt.Println(opsStack.Peek()) + fmt.Println(exprStack.Peek()) + // oh noooo! + return DockerlangSyntaxError + } + + // add this expression to the sequential list of expressions in the + // programs execution + c.StackTree.Operands = append( + c.StackTree.Operands, + exprStack.Pop().(*Expr), + ) + } } - // there should only be one expr in exprStack - if exprStack.Length() != 1 { + // there should be nothing on the expression stack or operation stack + if exprStack.Length() != 0 || opsStack.Peek() != nil { + fmt.Println("SOMETHING IS AWRY") + fmt.Println(opsStack.Peek()) + fmt.Println(exprStack.Peek()) // oh no! return DockerlangSyntaxError } - if opsStack.Peek() != nil { - // oh noooo! - return DockerlangSyntaxError + + return nil +} + +// TODO (cw,mr|4.11.2018) refactor this so that there is better separation of concerns +// i.e. it *would* be useful to have a function for adding local, global, args, etc. to +// an expression, but we might not want to do all this parsing in that function. +// NOTE: once we implement functions, we are going to want to check globals and args! +func (e *Expr) ParseIdentifier(token Token, opsStack *Stack) (*Identifier, error) { + var ( + isDefined bool = false + knownIdentifier *Identifier + ) + + // check all locals to see if we've already defined this identifier + for name, ast := range e.Locals { + if token.Value == name { + // this means we have already defined this identifer + isDefined = true + knownIdentifier = ast.(*Identifier) + break + } } - c.StackTree.Operands = []AST{exprStack.Pop().(*Expr)} + prev := opsStack.Peek().(*Expr) - return nil + // this is an identifier reference + if prev.Op != VARIABLE_INITIALIZATION && prev.Op != FUNCTION_KEYWORD { + if !isDefined { + // TODO (cw,mr|4.11.2018) make this error more informative + fmt.Println("TRYING TO USE AN UNDEFINED THING") + return nil, DockerlangSyntaxError + } + + // we are assuming that if an identifier is defined, then it is also bounded (or whatever) + return knownIdentifier, nil + } + + // if we are here, this is an identifier definition + + // we are trying to re-define this identifier + if isDefined { + fmt.Println("") + return nil, DockerlangSyntaxError + } + + // actually define this identifier + switch prev.Op { + case VARIABLE_INITIALIZATION: + knownIdentifier = &Identifier{Type: VARIABLE_IDENTIFIER, Name: token.Value, Bound: true} + case FUNCTION_KEYWORD: + knownIdentifier = &Identifier{Type: FUNCTION_IDENTIFIER, Name: token.Value, Bound: true} + } + + // TODO (cw, mr|4.11.2018) maybe put this in the Expr constructor once one exists + if e.Locals == nil { + e.Locals = map[string]AST{} + } + + // add this to the local scope of the current expression + e.Locals[knownIdentifier.Name] = knownIdentifier + + return knownIdentifier, nil } diff --git a/symbols.go b/symbols.go index 6e37f2d..bb7f9af 100644 --- a/symbols.go +++ b/symbols.go @@ -30,8 +30,8 @@ var ( MULTIPLICATION_OPERATOR: 2, DIVISION_OPERATOR: 2, MODULO_OPERATOR: 2, - VARIABLE_INITIALIZATION: 1, - VARIABLE_ASSIGNMENT: 1, + VARIABLE_INITIALIZATION: 2, + VARIABLE_ASSIGNMENT: 2, EXIT_OPERATOR: 1, NOOP: 1, } @@ -55,6 +55,8 @@ func PopulateSymbols() *Symbols { MULTIPLICATION_OPERATOR, DIVISION_OPERATOR, MODULO_OPERATOR, + VARIABLE_INITIALIZATION, + VARIABLE_ASSIGNMENT, EXIT_OPERATOR, NOOP, }, diff --git a/test/test_identifiers_keyword.doc b/test/test_identifiers_keyword.doc new file mode 100644 index 0000000..f14a9e4 --- /dev/null +++ b/test/test_identifiers_keyword.doc @@ -0,0 +1 @@ +if diff --git a/test/variable.doc b/test/variable.doc index 437c0e4..3b90f6c 100644 --- a/test/variable.doc +++ b/test/variable.doc @@ -1,4 +1,3 @@ (≡ aVariable 0) - -if asdf -ifIamKewl +(= aVariable 23) +(ꙮ (+ aVariable 2))