From fccfd2f971b60e2c5b8d9fa8b6cd6b42463d54bd Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Wed, 3 Apr 2024 13:15:25 +0200 Subject: [PATCH] Operators '??' and '?=' added --- operator-coalesce.go | 95 ++++++++++++++++++++++++++++++++++++++++++++ parser_test.go | 5 +++ scanner.go | 75 ++++------------------------------ symbol.go | 2 + term.go | 1 + 5 files changed, 110 insertions(+), 68 deletions(-) create mode 100644 operator-coalesce.go diff --git a/operator-coalesce.go b/operator-coalesce.go new file mode 100644 index 0000000..0a22ecc --- /dev/null +++ b/operator-coalesce.go @@ -0,0 +1,95 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-coalesce.go +package expr + +//-------- null coalesce term + +func newNullCoalesceTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + class: classOperator, + kind: kindUnknown, + children: make([]*term, 0, 2), + position: posInfix, + priority: priCoalesce, + evalFunc: evalNullCoalesce, + } +} + +func evalNullCoalesce(ctx exprContext, self *term) (v any, err error) { + var rightValue any + + if err = self.checkOperands(); err != nil { + return + } + + leftTerm := self.children[0] + if leftTerm.tk.Sym != SymIdentifier { + err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source) + return + } + + if leftValue, exists := ctx.GetVar(leftTerm.source()); exists { + v = leftValue + } else if rightValue, err = self.children[1].compute(ctx); err == nil { + if _, ok := rightValue.(Functor); ok { + err = errCoalesceNoFunc(self.children[1]) + } else { + v = rightValue + } + } + return +} + +//-------- coalesce assign term + +func newCoalesceAssignTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + class: classOperator, + kind: kindUnknown, + children: make([]*term, 0, 2), + position: posInfix, + priority: priCoalesce, + evalFunc: evalAssignCoalesce, + } +} + +func evalAssignCoalesce(ctx exprContext, self *term) (v any, err error) { + var rightValue any + + if err = self.checkOperands(); err != nil { + return + } + + leftTerm := self.children[0] + if leftTerm.tk.Sym != SymIdentifier { + err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source) + return + } + + if leftValue, exists := ctx.GetVar(leftTerm.source()); exists { + v = leftValue + } else if rightValue, err = self.children[1].compute(ctx); err == nil { + if _, ok := rightValue.(Functor); ok { + err = errCoalesceNoFunc(self.children[1]) + } else { + v = rightValue + ctx.SetVar(leftTerm.source(), rightValue) + } + } + return +} + +// utils +func errCoalesceNoFunc(t *term) error { + return t.Errorf("the right operand of a coalescing operation cannot be a function definition") +} + +// init +func init() { + registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm) + registerTermConstructor(SymQuestionEqual, newCoalesceAssignTerm) +} diff --git a/parser_test.go b/parser_test.go index 6b09b6e..3fdc404 100644 --- a/parser_test.go +++ b/parser_test.go @@ -132,6 +132,11 @@ func TestParser(t *testing.T) { /* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil}, /* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, /* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, + /* 114 */ {`x ?? "default"`, "default", nil}, + /* 115 */ {`x="hello"; x ?? "default"`, "hello", nil}, + /* 116 */ {`x ?? func(){}"`, nil, errors.New(`[1:15] the right operand of a coalescing operation cannot be a function definition`)}, + /* 117 */ {`x ?= "default"; x`, "default", nil}, + /* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil}, } check_env_expr_path := 113 diff --git a/scanner.go b/scanner.go index b45a542..fd3fb7c 100644 --- a/scanner.go +++ b/scanner.go @@ -172,7 +172,13 @@ func (self *scanner) fetchNextToken() (tk *Token) { tk = self.makeToken(SymExclamation, ch) } case '?': - tk = self.makeToken(SymQuestion, ch) + if next, _ := self.peek(); next == '?' { + tk = self.moveOn(SymDoubleQuestion, ch, next) + } else if next, _ := self.peek(); next == '=' { + tk = self.moveOn(SymQuestionEqual, ch, next) + } else { + tk = self.makeToken(SymQuestion, ch) + } case '&': if next, _ := self.peek(); next == '&' { tk = self.moveOn(SymDoubleAmpersand, ch, next) @@ -371,73 +377,6 @@ func (self *scanner) fetchOnLineComment() *Token { return self.fetchUntil(SymComment, true, '\n') } -// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) { -// var err error -// var ch byte -// var sb strings.Builder -// var value string -// end := string(endings) -// endReached := false -// for ch, err = self.readChar(); err == nil && !endReached; { -// sb.WriteByte(ch) -// if sb.Len() >= len(end) && strings.HasSuffix(sb.String(), end) { -// value = sb.String()[0 : sb.Len()-len(end)] -// endReached = true -// } else { -// ch, err = self.readChar() -// } -// } -// if !endReached && allowEos { -// value = sb.String() -// endReached = true -// } - -// if endReached { -// tk = self.makeValueToken(sym, "", value) -// } else { -// tk = self.makeErrorToken(err) -// } -// return -// } - -// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) { -// var err error -// var ch byte -// var sb strings.Builder -// var value string -// end := make([]byte, len(endings)) -// length := 0 -// endReached := false -// for ch, err = self.readChar(); err == nil && !endReached; { -// sb.WriteByte(ch) -// if length == len(endings) { -// for i := 0; i < length-1; i++ { -// end[i] = end[i+1] -// } -// length-- -// } -// end[length] = ch -// length++ -// if bytes.Equal(endings, end) { -// value = sb.String()[0 : sb.Len()-len(end)] -// endReached = true -// } else { -// ch, err = self.readChar() -// } -// } -// if !endReached && allowEos { -// value = sb.String() -// endReached = true -// } - -// if endReached { -// tk = self.makeValueToken(sym, "", value) -// } else { -// tk = self.makeErrorToken(err) -// } -// return -// } - func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) { var err error var ch byte diff --git a/symbol.go b/symbol.go index d003a70..49547b9 100644 --- a/symbol.go +++ b/symbol.go @@ -55,6 +55,8 @@ const ( SymOpenBrace // 44: '{' SymClosedBrace // 45: '}' SymTilde // 46: '~' + SymDoubleQuestion + SymQuestionEqual SymChangeSign SymUnchangeSign SymIdentifier diff --git a/term.go b/term.go index 52e0082..9c45f66 100644 --- a/term.go +++ b/term.go @@ -43,6 +43,7 @@ const ( priProduct priSign priFact + priCoalesce priValue )