Operators '??' and '?=' added
This commit is contained in:
parent
088e347c95
commit
fccfd2f971
95
operator-coalesce.go
Normal file
95
operator-coalesce.go
Normal 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)
|
||||||
|
}
|
@ -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},
|
/* 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},
|
/* 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},
|
/* 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
|
check_env_expr_path := 113
|
||||||
|
|
||||||
|
73
scanner.go
73
scanner.go
@ -172,7 +172,13 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
tk = self.makeToken(SymExclamation, ch)
|
tk = self.makeToken(SymExclamation, ch)
|
||||||
}
|
}
|
||||||
case '?':
|
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)
|
tk = self.makeToken(SymQuestion, ch)
|
||||||
|
}
|
||||||
case '&':
|
case '&':
|
||||||
if next, _ := self.peek(); next == '&' {
|
if next, _ := self.peek(); next == '&' {
|
||||||
tk = self.moveOn(SymDoubleAmpersand, ch, next)
|
tk = self.moveOn(SymDoubleAmpersand, ch, next)
|
||||||
@ -371,73 +377,6 @@ func (self *scanner) fetchOnLineComment() *Token {
|
|||||||
return self.fetchUntil(SymComment, true, '\n')
|
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) {
|
func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch byte
|
var ch byte
|
||||||
|
@ -55,6 +55,8 @@ const (
|
|||||||
SymOpenBrace // 44: '{'
|
SymOpenBrace // 44: '{'
|
||||||
SymClosedBrace // 45: '}'
|
SymClosedBrace // 45: '}'
|
||||||
SymTilde // 46: '~'
|
SymTilde // 46: '~'
|
||||||
|
SymDoubleQuestion
|
||||||
|
SymQuestionEqual
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
|
Loading…
Reference in New Issue
Block a user