Operators '??' and '?=' added

This commit is contained in:
Celestino Amoroso 2024-04-03 13:15:25 +02:00
parent 088e347c95
commit fccfd2f971
5 changed files with 110 additions and 68 deletions

95
operator-coalesce.go Normal file
View File

@ -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)
}

View File

@ -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

View File

@ -172,7 +172,13 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymExclamation, ch)
}
case '?':
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

View File

@ -55,6 +55,8 @@ const (
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion
SymQuestionEqual
SymChangeSign
SymUnchangeSign
SymIdentifier

View File

@ -43,6 +43,7 @@ const (
priProduct
priSign
priFact
priCoalesce
priValue
)