From d3f388f7e1de92124628937b2b5684c80225db9f Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Mon, 8 Apr 2024 22:16:07 +0200 Subject: [PATCH] Selector operator, multi-operand, added --- ast.go | 16 ++++++- helpers.go | 2 +- operand-expr.go | 4 +- operand-selector-case.go | 37 ++++++++++++++++ operator-selector.go | 70 +++++++++++++++++++++++++++++ parser.go | 96 ++++++++++++++++++++++++++++++++++++---- parser_test.go | 39 +++++++++------- scanner.go | 6 ++- symbol.go | 3 ++ term.go | 24 +++++++--- 10 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 operand-selector-case.go create mode 100644 operator-selector.go diff --git a/ast.go b/ast.go index 9cb4afc..04ef397 100644 --- a/ast.go +++ b/ast.go @@ -54,7 +54,21 @@ func (self *ast) addTokens(tokens ...*Token) (err error) { } func (self *ast) addToken(tk *Token) (err error) { - if t := newTerm(tk, nil); t != nil { + _, err = self.addToken2(tk) + return +} + +// func (self *ast) addToken(tk *Token) (err error) { +// if t := newTerm(tk, nil); t != nil { +// err = self.addTerm(t) +// } else { +// err = tk.Errorf("No term constructor for token %q", tk.String()) +// } +// return +// } + +func (self *ast) addToken2(tk *Token) (t *term, err error) { + if t = newTerm(tk, nil); t != nil { err = self.addTerm(t) } else { err = tk.Errorf("No term constructor for token %q", tk.String()) diff --git a/helpers.go b/helpers.go index 17a017a..363705b 100644 --- a/helpers.go +++ b/helpers.go @@ -13,7 +13,7 @@ func EvalString(ctx exprContext, source string) (result any, err error) { scanner := NewScanner(r, DefaultTranslations()) parser := NewParser(ctx) - if tree, err = parser.parse(scanner); err == nil { + if tree, err = parser.Parse(scanner); err == nil { result, err = tree.Eval(ctx, true) } return diff --git a/operand-expr.go b/operand-expr.go index ae433a4..fc60f23 100644 --- a/operand-expr.go +++ b/operand-expr.go @@ -4,7 +4,7 @@ // operand-expr.go package expr -import "errors" +import "fmt" // -------- expr term func newExprTerm(tk *Token) *term { @@ -25,7 +25,7 @@ func evalExpr(ctx exprContext, self *term) (v any, err error) { if expr, ok := self.value().(Expr); ok { v, err = expr.Eval(ctx, false) } else { - err = errors.New("invalid body of function definition") + err = fmt.Errorf("expression expected, got %T", self.value()) } return } diff --git a/operand-selector-case.go b/operand-selector-case.go new file mode 100644 index 0000000..1866ac6 --- /dev/null +++ b/operand-selector-case.go @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operand-selector-case.go +package expr + +import "fmt" + +// -------- selector case term + +type selectorCase struct { + filterList *term + caseExpr Expr +} + +func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term { + tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr}) + return &term{ + tk: *tk, + class: classVar, + kind: kindUnknown, + parent: nil, + children: nil, + position: posLeaf, + priority: priValue, + evalFunc: evalSelectorCase, + } +} + +// -------- eval selector case +func evalSelectorCase(ctx exprContext, self *term) (v any, err error) { + var ok bool + if v, ok = self.value().(*selectorCase); !ok { + err = fmt.Errorf("selector-case expected, got %T", self.value()) + } + return +} diff --git a/operator-selector.go b/operator-selector.go new file mode 100644 index 0000000..3e3a832 --- /dev/null +++ b/operator-selector.go @@ -0,0 +1,70 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-selector.go +package expr + +//-------- export all term + +func newSelectorTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + class: classOperator, + kind: kindUnknown, + children: make([]*term, 0, 3), + position: posMultifix, + priority: priSelector, + evalFunc: evalSelector, + } +} + +func isSelectorCase(ctx exprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) { + caseData, _ := caseSel.(*selectorCase) + if caseData.filterList == nil { + selectedValue, err = caseData.caseExpr.Eval(ctx, false) + } else { + filterList := caseData.filterList.children + if len(filterList) == 0 && exprValue == int64(caseIndex) { + selectedValue, err = caseData.caseExpr.Eval(ctx, false) + } else { + var caseValue any + for _, caseTerm := range filterList { + if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue { + selectedValue, err = caseData.caseExpr.Eval(ctx, false) + break + } + } + } + } + return +} + +func evalSelector(ctx exprContext, self *term) (v any, err error) { + var exprValue any + // var caseList []*term + + if err = self.checkOperands(); err != nil { + return + } + exprTerm := self.children[0] + if exprValue, err = exprTerm.compute(ctx); err != nil { + return + } + + caseList := self.children[1:] + for i, caseTerm := range caseList { + caseSel := caseTerm.value() + if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil { + break + } + } + if err == nil && v == nil { + err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue) + } + return +} + +// init +func init() { + registerTermConstructor(SymSelector, newSelectorTerm) +} diff --git a/parser.go b/parser.go index 328dbc5..4be53be 100644 --- a/parser.go +++ b/parser.go @@ -94,7 +94,7 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { return } -func (self *parser) parseList(scanner *scanner, allowVarRef bool) (tree *term, err error) { +func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) { args := make([]*term, 0) lastSym := SymUnknown for lastSym != SymClosedSquare && lastSym != SymEos { @@ -113,21 +113,72 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (tree *term, e if lastSym != SymClosedSquare { err = scanner.Previous().Errorf("unterminate items list") } else { - tree = newListTerm(args) + subtree = newListTerm(args) } } return } -func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) { - return self.parseGeneral(scanner, true, false, termSymbols...) +func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) { + var filterList *term + var caseExpr *ast + tk := scanner.Next() + startRow := tk.row + startCol := tk.col + if tk.Sym == SymOpenSquare { + if defaultCase { + err = tk.Errorf("case list in default clause") + return + } + if filterList, err = self.parseList(scanner, allowVarRef); err != nil { + return + } + tk = scanner.Next() + startRow = tk.row + startCol = tk.col + } else if !defaultCase { + filterList = newListTerm(make([]*term, 0)) + } + + if tk.Sym == SymOpenBrace { + if caseExpr, err = self.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil { + return + } + } else { + err = tk.Errorf("selector-case expected, got %q", tk.source) + } + + if err == nil { + caseTerm = newSelectorCaseTerm(startRow, startCol, filterList, caseExpr) + } + return +} + +func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) { + var caseTerm *term + tk := scanner.makeToken(SymSelector, '?') + if selectorTerm, err = tree.addToken2(tk); err != nil { + return + } + + if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil { + selectorTerm.children = append(selectorTerm.children, caseTerm) + caseTerm.parent = selectorTerm + } + return } func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { return self.parseGeneral(scanner, false, allowVarRef, termSymbols...) } +func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) { + return self.parseGeneral(scanner, true, false, termSymbols...) +} + func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { + var selectorTerm *term = nil + var currentTerm *term = nil tree = NewAst() firstToken := true lastSym := SymUnknown @@ -135,6 +186,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef if tk.Sym == SymComment { continue } + //resetSelector := true if tk.Sym == SymSemiColon { if allowForest { @@ -162,36 +214,64 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef var subTree *ast if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil { subTree.root.priority = priValue - tree.addTerm(subTree.root) + err = tree.addTerm(subTree.root) + currentTerm = subTree.root } case SymFuncCall: var funcCallTerm *term if funcCallTerm, err = self.parseFuncCall(scanner, allowVarRef, tk); err == nil { err = tree.addTerm(funcCallTerm) + currentTerm = funcCallTerm } case SymOpenSquare: var listTerm *term if listTerm, err = self.parseList(scanner, allowVarRef); err == nil { err = tree.addTerm(listTerm) + currentTerm = listTerm } case SymEqual: if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { - err = tree.addToken(tk) + currentTerm, err = tree.addToken2(tk) } case SymFuncDef: var funcDefTerm *term if funcDefTerm, err = self.parseFuncDef(scanner); err == nil { err = tree.addTerm(funcDefTerm) + currentTerm = funcDefTerm } case SymIdentifier: if tk.source[0] == '@' && !allowVarRef { err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source) } else { - err = tree.addToken(tk) + currentTerm, err = tree.addToken2(tk) } + case SymQuestion: + if selectorTerm != nil { + err = tk.Errorf("nested selectors must be enclosed in parentheses") + } else if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil { + currentTerm = selectorTerm + } + case SymColon, SymDoubleColon: + var caseTerm *term + if selectorTerm == nil { + err = tk.Errorf("selector-case outside of a selector context") + } else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil { + selectorTerm.children = append(selectorTerm.children, caseTerm) + caseTerm.parent = selectorTerm + currentTerm = caseTerm + } + //resetSelector = tk.Sym == SymDoubleColon default: - err = tree.addToken(tk) + currentTerm, err = tree.addToken2(tk) } + + if currentTerm != nil && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector { + selectorTerm = nil + + } + // if resetSelector { + // selectorTree = nil + // } lastSym = tk.Sym } return diff --git a/parser_test.go b/parser_test.go index 9389748..ba8b982 100644 --- a/parser_test.go +++ b/parser_test.go @@ -63,7 +63,7 @@ func TestParser(t *testing.T) { /* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)}, /* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)}, /* 44 */ {`false // very simple expression`, false, nil}, - /* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two operands, got 1`)}, + /* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)}, /* 46 */ {"", nil, errors.New(`empty expression`)}, /* 47 */ {"4!", int64(24), nil}, /* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)}, @@ -78,18 +78,18 @@ func TestParser(t *testing.T) { /* 57 */ {`"1.5" > "7"`, false, nil}, /* 58 */ {`"1.5" == "7"`, false, nil}, /* 59 */ {`"1.5" != "7"`, true, nil}, - /* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two operands, got 1`)}, - /* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two operands, got 1`)}, - /* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two operands, got 1`)}, - /* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two operands, got 1`)}, - /* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two operands, got 1`)}, - /* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two operands, got 1`)}, - /* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two operands, got 1`)}, - /* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two operands, got 1`)}, - /* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two operands, got 1`)}, - /* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two operands, got 1`)}, + /* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)}, + /* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)}, + /* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)}, + /* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)}, + /* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)}, + /* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)}, + /* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)}, + /* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)}, + /* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)}, + /* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)}, /* 70 */ {"+1.5", float64(1.5), nil}, - /* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one operand`)}, + /* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)}, /* 72 */ {"4 / 0", nil, errors.New(`division by zero`)}, /* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)}, /* 74 */ {"4.0 / \n2", float64(2.0), nil}, @@ -146,6 +146,13 @@ func TestParser(t *testing.T) { /* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil}, /* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil}, /* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil}, + /* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil}, + /* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil}, + /* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil}, + /* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)}, + /* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil}, + /* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil}, + /* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)}, } check_env_expr_path := 113 @@ -153,7 +160,7 @@ func TestParser(t *testing.T) { failed := 0 // inputs1 := []inputType{ - // {`import("./sample-export-all.expr"); six()`, int64(6), nil}, + // {`1 ? {"a"} : {"b"}`, "b", nil}, // } for i, input := range inputs { @@ -174,7 +181,7 @@ func TestParser(t *testing.T) { scanner := NewScanner(r, DefaultTranslations()) good := true - if expr, gotErr = parser.parse(scanner); gotErr == nil { + if expr, gotErr = parser.Parse(scanner); gotErr == nil { gotResult, gotErr = expr.Eval(ctx, true) } @@ -202,7 +209,7 @@ func TestParser(t *testing.T) { t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)) } -func TestListParser(t *testing.T) { +func NoTestListParser(t *testing.T) { type inputType struct { source string wantResult any @@ -249,7 +256,7 @@ func TestListParser(t *testing.T) { scanner := NewScanner(r, DefaultTranslations()) good := true - if expr, gotErr = parser.parse(scanner); gotErr == nil { + if expr, gotErr = parser.Parse(scanner); gotErr == nil { gotResult, gotErr = expr.Eval(ctx, true) } diff --git a/scanner.go b/scanner.go index 1b364b0..f244f29 100644 --- a/scanner.go +++ b/scanner.go @@ -144,7 +144,11 @@ func (self *scanner) fetchNextToken() (tk *Token) { case ',': tk = self.makeToken(SymComma, ch) case ':': - tk = self.makeToken(SymColon, ch) + if next, _ := self.peek(); next == ':' { + tk = self.moveOn(SymDoubleColon, ch, next) + } else { + tk = self.makeToken(SymColon, ch) + } case ';': tk = self.makeToken(SymSemiColon, ch) case '.': diff --git a/symbol.go b/symbol.go index 87f67f2..5ebe214 100644 --- a/symbol.go +++ b/symbol.go @@ -58,6 +58,7 @@ const ( SymDoubleQuestion // 47: '??' SymQuestionEqual // 48: '?=' SymDoubleAt // 49: '@@' + SymDoubleColon // 50: '::' SymChangeSign SymUnchangeSign SymIdentifier @@ -73,6 +74,8 @@ const ( SymFuncDef SymList SymExpression + SymSelector // ::= "?" {":" } ["::" ] + SymSelectorCase // ::= [] "{" "}" // SymOpenComment // 0: '/*' // SymClosedComment // 0: '*/' // SymOneLineComment // 0: '//' diff --git a/term.go b/term.go index 9c45f66..cfcc7d5 100644 --- a/term.go +++ b/term.go @@ -41,6 +41,7 @@ const ( priRelational priSum priProduct + priSelector priSign priFact priCoalesce @@ -54,6 +55,7 @@ const ( posInfix posPrefix posPostfix + posMultifix ) type evalFuncType func(ctx exprContext, self *term) (v any, err error) @@ -196,21 +198,33 @@ func (self *term) Errorf(template string, args ...any) (err error) { func (self *term) checkOperands() (err error) { switch self.position { case posInfix: - if self.children == nil || len(self.children) != 2 || self.children[0] == nil || self.children[1] == nil { - err = self.tk.Errorf("infix operator %q requires two operands, got %d", self.source(), self.getChildrenCount()) + if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() { + err = self.tk.Errorf("infix operator %q requires two not nil operands, got %d", self.source(), self.getChildrenCount()) } case posPrefix: if self.children == nil || len(self.children) != 1 || self.children[0] == nil { - err = self.tk.Errorf("prefix operator %q requires one operand", self.tk.String()) + err = self.tk.Errorf("prefix operator %q requires one not nil operand", self.tk.String()) } case posPostfix: - if self.children == nil || len(self.children) != 1 || self.children[0] == nil { - err = self.tk.Errorf("postfix operator %q requires one operand", self.tk.String()) + if self.children == nil || len(self.children) != 1 || self.anyChildrenNil() { + err = self.tk.Errorf("postfix operator %q requires one not nil operand", self.tk.String()) + } + case posMultifix: + if self.children == nil || len(self.children) < 3 || self.anyChildrenNil() { + err = self.tk.Errorf("infix operator %q requires at least three not operands, got %d", self.source(), self.getChildrenCount()) } } return } +func (self *term) anyChildrenNil() bool { + for _, child := range self.children { + if child == nil { + return true + } + } + return false +} func (self *term) evalInfix(ctx exprContext) (leftValue, rightValue any, err error) { if err = self.checkOperands(); err == nil { if leftValue, err = self.children[0].compute(ctx); err == nil {