logical operators
This commit is contained in:
parent
3ea58a55c9
commit
c691c90c69
@ -305,7 +305,7 @@ func (ce *CallExpression) String() string {
|
|||||||
return fmt.Sprintf("%s(%s)", ce.Function.String(), joinStrings(args, ", "))
|
return fmt.Sprintf("%s(%s)", ce.Function.String(), joinStrings(args, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrefixExpression represents prefix operations like -x
|
// PrefixExpression represents prefix operations like -x, not x
|
||||||
type PrefixExpression struct {
|
type PrefixExpression struct {
|
||||||
Operator string
|
Operator string
|
||||||
Right Expression
|
Right Expression
|
||||||
@ -313,6 +313,10 @@ type PrefixExpression struct {
|
|||||||
|
|
||||||
func (pe *PrefixExpression) expressionNode() {}
|
func (pe *PrefixExpression) expressionNode() {}
|
||||||
func (pe *PrefixExpression) String() string {
|
func (pe *PrefixExpression) String() string {
|
||||||
|
// Add space for word operators
|
||||||
|
if pe.Operator == "not" {
|
||||||
|
return fmt.Sprintf("(%s %s)", pe.Operator, pe.Right.String())
|
||||||
|
}
|
||||||
return fmt.Sprintf("(%s%s)", pe.Operator, pe.Right.String())
|
return fmt.Sprintf("(%s%s)", pe.Operator, pe.Right.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ func NewParser(lexer *Lexer) *Parser {
|
|||||||
p.registerPrefix(LPAREN, p.parseGroupedExpression)
|
p.registerPrefix(LPAREN, p.parseGroupedExpression)
|
||||||
p.registerPrefix(LBRACE, p.parseTableLiteral)
|
p.registerPrefix(LBRACE, p.parseTableLiteral)
|
||||||
p.registerPrefix(MINUS, p.parsePrefixExpression)
|
p.registerPrefix(MINUS, p.parsePrefixExpression)
|
||||||
|
p.registerPrefix(NOT, p.parsePrefixExpression)
|
||||||
p.registerPrefix(FN, p.parseFunctionLiteral)
|
p.registerPrefix(FN, p.parseFunctionLiteral)
|
||||||
|
|
||||||
p.infixParseFns = make(map[TokenType]func(Expression) Expression)
|
p.infixParseFns = make(map[TokenType]func(Expression) Expression)
|
||||||
@ -68,6 +69,8 @@ func NewParser(lexer *Lexer) *Parser {
|
|||||||
p.registerInfix(GT, p.parseInfixExpression)
|
p.registerInfix(GT, p.parseInfixExpression)
|
||||||
p.registerInfix(LT_EQ, p.parseInfixExpression)
|
p.registerInfix(LT_EQ, p.parseInfixExpression)
|
||||||
p.registerInfix(GT_EQ, p.parseInfixExpression)
|
p.registerInfix(GT_EQ, p.parseInfixExpression)
|
||||||
|
p.registerInfix(AND, p.parseInfixExpression)
|
||||||
|
p.registerInfix(OR, p.parseInfixExpression)
|
||||||
p.registerInfix(DOT, p.parseDotExpression)
|
p.registerInfix(DOT, p.parseDotExpression)
|
||||||
p.registerInfix(LBRACKET, p.parseIndexExpression)
|
p.registerInfix(LBRACKET, p.parseIndexExpression)
|
||||||
p.registerInfix(LPAREN, p.parseCallExpression)
|
p.registerInfix(LPAREN, p.parseCallExpression)
|
||||||
@ -320,7 +323,7 @@ func (p *Parser) parseReturnStatement() *ReturnStatement {
|
|||||||
// canStartExpression checks if a token type can start an expression
|
// canStartExpression checks if a token type can start an expression
|
||||||
func (p *Parser) canStartExpression(tokenType TokenType) bool {
|
func (p *Parser) canStartExpression(tokenType TokenType) bool {
|
||||||
switch tokenType {
|
switch tokenType {
|
||||||
case IDENT, NUMBER, STRING, TRUE, FALSE, NIL, LPAREN, LBRACE, MINUS, FN:
|
case IDENT, NUMBER, STRING, TRUE, FALSE, NIL, LPAREN, LBRACE, MINUS, NOT, FN:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -1003,7 +1006,7 @@ func (p *Parser) expectPeekIdent() bool {
|
|||||||
// isKeyword checks if a token type is a keyword that can be used as identifier
|
// isKeyword checks if a token type is a keyword that can be used as identifier
|
||||||
func (p *Parser) isKeyword(t TokenType) bool {
|
func (p *Parser) isKeyword(t TokenType) bool {
|
||||||
switch t {
|
switch t {
|
||||||
case TRUE, FALSE, NIL, IF, THEN, ELSEIF, ELSE, END, ECHO, FOR, WHILE, IN, DO, BREAK, EXIT, FN, RETURN:
|
case TRUE, FALSE, NIL, AND, OR, NOT, IF, THEN, ELSEIF, ELSE, END, ECHO, FOR, WHILE, IN, DO, BREAK, EXIT, FN, RETURN:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -1123,6 +1126,12 @@ func tokenTypeString(t TokenType) string {
|
|||||||
return "<="
|
return "<="
|
||||||
case GT_EQ:
|
case GT_EQ:
|
||||||
return ">="
|
return ">="
|
||||||
|
case AND:
|
||||||
|
return "and"
|
||||||
|
case OR:
|
||||||
|
return "or"
|
||||||
|
case NOT:
|
||||||
|
return "not"
|
||||||
case LPAREN:
|
case LPAREN:
|
||||||
return "("
|
return "("
|
||||||
case RPAREN:
|
case RPAREN:
|
||||||
|
@ -16,6 +16,10 @@ func TestPrefixExpressions(t *testing.T) {
|
|||||||
{"-x", "-", "x"},
|
{"-x", "-", "x"},
|
||||||
{"-true", "-", true},
|
{"-true", "-", true},
|
||||||
{"-(1 + 2)", "-", "(1.00 + 2.00)"},
|
{"-(1 + 2)", "-", "(1.00 + 2.00)"},
|
||||||
|
{"not true", "not", true},
|
||||||
|
{"not false", "not", false},
|
||||||
|
{"not x", "not", "x"},
|
||||||
|
{"not (a == b)", "not", "(a == b)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -53,6 +57,56 @@ func TestPrefixExpressions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLogicalExpressions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
leftValue any
|
||||||
|
operator string
|
||||||
|
rightValue any
|
||||||
|
}{
|
||||||
|
{"true and false", true, "and", false},
|
||||||
|
{"false or true", false, "or", true},
|
||||||
|
{"x and y", "x", "and", "y"},
|
||||||
|
{"a or b", "a", "or", "b"},
|
||||||
|
{"success and valid", "success", "and", "valid"},
|
||||||
|
{"error or fallback", "error", "or", "fallback"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
expr := p.ParseExpression(parser.LOWEST)
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
infix, ok := expr.(*parser.InfixExpression)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected InfixExpression, got %T", expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if infix.Operator != tt.operator {
|
||||||
|
t.Errorf("expected operator %s, got %s", tt.operator, infix.Operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test left operand
|
||||||
|
switch leftVal := tt.leftValue.(type) {
|
||||||
|
case string:
|
||||||
|
testIdentifier(t, infix.Left, leftVal)
|
||||||
|
case bool:
|
||||||
|
testBooleanLiteral(t, infix.Left, leftVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test right operand
|
||||||
|
switch rightVal := tt.rightValue.(type) {
|
||||||
|
case string:
|
||||||
|
testIdentifier(t, infix.Right, rightVal)
|
||||||
|
case bool:
|
||||||
|
testBooleanLiteral(t, infix.Right, rightVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestComparisonExpressions(t *testing.T) {
|
func TestComparisonExpressions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
@ -155,6 +209,25 @@ func TestOperatorPrecedence(t *testing.T) {
|
|||||||
{"-1 + 2", "((-1.00) + 2.00)"},
|
{"-1 + 2", "((-1.00) + 2.00)"},
|
||||||
{"-(1 + 2)", "(-(1.00 + 2.00))"},
|
{"-(1 + 2)", "(-(1.00 + 2.00))"},
|
||||||
{"-x * 2", "((-x) * 2.00)"},
|
{"-x * 2", "((-x) * 2.00)"},
|
||||||
|
{"not true", "(not true)"},
|
||||||
|
{"not x", "(not x)"},
|
||||||
|
|
||||||
|
// Logical operator precedence
|
||||||
|
{"true or false and true", "(true or (false and true))"},
|
||||||
|
{"false and true or false", "((false and true) or false)"},
|
||||||
|
{"a or b and c", "(a or (b and c))"},
|
||||||
|
{"x and y or z", "((x and y) or z)"},
|
||||||
|
|
||||||
|
// Logical with comparison
|
||||||
|
{"a == b and c != d", "((a == b) and (c != d))"},
|
||||||
|
{"x < y or z > w", "((x < y) or (z > w))"},
|
||||||
|
{"not a == b", "((not a) == b)"},
|
||||||
|
{"not x and y", "((not x) and y)"},
|
||||||
|
|
||||||
|
// Logical with arithmetic
|
||||||
|
{"a + b and c * d", "((a + b) and (c * d))"},
|
||||||
|
{"x or y + z", "(x or (y + z))"},
|
||||||
|
{"not a + b", "((not a) + b)"},
|
||||||
|
|
||||||
// Comparison precedence
|
// Comparison precedence
|
||||||
{"1 + 2 == 3", "((1.00 + 2.00) == 3.00)"},
|
{"1 + 2 == 3", "((1.00 + 2.00) == 3.00)"},
|
||||||
@ -172,10 +245,14 @@ func TestOperatorPrecedence(t *testing.T) {
|
|||||||
{"arr[0] * 2", "(arr[0.00] * 2.00)"},
|
{"arr[0] * 2", "(arr[0.00] * 2.00)"},
|
||||||
{"obj.x == obj.y", "(obj.x == obj.y)"},
|
{"obj.x == obj.y", "(obj.x == obj.y)"},
|
||||||
{"-table.value", "(-table.value)"},
|
{"-table.value", "(-table.value)"},
|
||||||
|
{"not obj.active", "(not obj.active)"},
|
||||||
|
{"obj.x and obj.y", "(obj.x and obj.y)"},
|
||||||
|
|
||||||
// Complex combinations
|
// Complex combinations
|
||||||
{"-x + y * z == a.b", "(((-x) + (y * z)) == a.b)"},
|
{"-x + y * z == a.b", "(((-x) + (y * z)) == a.b)"},
|
||||||
{"(a + b) * c <= d[0]", "(((a + b) * c) <= d[0.00])"},
|
{"(a + b) * c <= d[0]", "(((a + b) * c) <= d[0.00])"},
|
||||||
|
{"not success and attempts > 0 or fallback", "(((not success) and (attempts > 0.00)) or fallback)"},
|
||||||
|
{"a == b and c > d or e != f", "(((a == b) and (c > d)) or (e != f))"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -192,6 +269,40 @@ func TestOperatorPrecedence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComplexLogicalExpressions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"not (a and b)", "negated grouped logical"},
|
||||||
|
{"a and b or c and d", "mixed logical operators"},
|
||||||
|
{"success and valid or error and retry", "complex boolean logic"},
|
||||||
|
{"not failed and attempts < max_attempts", "logical with comparison"},
|
||||||
|
{"(ready or waiting) and not cancelled", "grouped logical with negation"},
|
||||||
|
{"x > 0 and y > 0 and z > 0", "multiple and conditions"},
|
||||||
|
{"error or warning or info", "multiple or conditions"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
expr := p.ParseExpression(parser.LOWEST)
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if expr == nil {
|
||||||
|
t.Error("expected non-nil expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the expression can be converted to string (basic sanity check)
|
||||||
|
result := expr.String()
|
||||||
|
if result == "" {
|
||||||
|
t.Error("expected non-empty string representation")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestComplexExpressionsWithComparisons(t *testing.T) {
|
func TestComplexExpressionsWithComparisons(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
@ -201,7 +312,7 @@ func TestComplexExpressionsWithComparisons(t *testing.T) {
|
|||||||
{"table.count > arr[0] + 5", "member access comparison"},
|
{"table.count > arr[0] + 5", "member access comparison"},
|
||||||
{"-value <= max", "prefix comparison"},
|
{"-value <= max", "prefix comparison"},
|
||||||
{"(a + b) != (c - d)", "grouped comparison"},
|
{"(a + b) != (c - d)", "grouped comparison"},
|
||||||
{"obj.x < obj.y && obj.y > obj.z", "multiple comparisons"}, // Note: && not implemented yet
|
{"obj.x < obj.y and obj.y > obj.z", "multiple comparisons with logical"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -209,12 +320,6 @@ func TestComplexExpressionsWithComparisons(t *testing.T) {
|
|||||||
l := parser.NewLexer(tt.input)
|
l := parser.NewLexer(tt.input)
|
||||||
p := parser.NewParser(l)
|
p := parser.NewParser(l)
|
||||||
expr := p.ParseExpression(parser.LOWEST)
|
expr := p.ParseExpression(parser.LOWEST)
|
||||||
|
|
||||||
// Skip && test since it's not implemented
|
|
||||||
if tt.input == "obj.x < obj.y && obj.y > obj.z" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
checkParserErrors(t, p)
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
if expr == nil {
|
if expr == nil {
|
||||||
|
@ -28,6 +28,11 @@ const (
|
|||||||
LT_EQ // <=
|
LT_EQ // <=
|
||||||
GT_EQ // >=
|
GT_EQ // >=
|
||||||
|
|
||||||
|
// Logical operators
|
||||||
|
AND // and
|
||||||
|
OR // or
|
||||||
|
NOT // not
|
||||||
|
|
||||||
// Delimiters
|
// Delimiters
|
||||||
LPAREN // (
|
LPAREN // (
|
||||||
RPAREN // )
|
RPAREN // )
|
||||||
@ -73,17 +78,21 @@ type Precedence int
|
|||||||
const (
|
const (
|
||||||
_ Precedence = iota
|
_ Precedence = iota
|
||||||
LOWEST
|
LOWEST
|
||||||
|
PREC_OR // or
|
||||||
|
PREC_AND // and
|
||||||
EQUALS // ==, !=
|
EQUALS // ==, !=
|
||||||
LESSGREATER // >, <, >=, <=
|
LESSGREATER // >, <, >=, <=
|
||||||
SUM // +, -
|
SUM // +, -
|
||||||
PRODUCT // *, /
|
PRODUCT // *, /
|
||||||
PREFIX // -x, !x
|
PREFIX // -x, not x
|
||||||
MEMBER // table[key], table.key
|
MEMBER // table[key], table.key
|
||||||
CALL // function()
|
CALL // function()
|
||||||
)
|
)
|
||||||
|
|
||||||
// precedences maps token types to their precedence levels
|
// precedences maps token types to their precedence levels
|
||||||
var precedences = map[TokenType]Precedence{
|
var precedences = map[TokenType]Precedence{
|
||||||
|
OR: PREC_OR,
|
||||||
|
AND: PREC_AND,
|
||||||
EQ: EQUALS,
|
EQ: EQUALS,
|
||||||
NOT_EQ: EQUALS,
|
NOT_EQ: EQUALS,
|
||||||
LT: LESSGREATER,
|
LT: LESSGREATER,
|
||||||
@ -105,6 +114,9 @@ func lookupIdent(ident string) TokenType {
|
|||||||
"true": TRUE,
|
"true": TRUE,
|
||||||
"false": FALSE,
|
"false": FALSE,
|
||||||
"nil": NIL,
|
"nil": NIL,
|
||||||
|
"and": AND,
|
||||||
|
"or": OR,
|
||||||
|
"not": NOT,
|
||||||
"if": IF,
|
"if": IF,
|
||||||
"then": THEN,
|
"then": THEN,
|
||||||
"elseif": ELSEIF,
|
"elseif": ELSEIF,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user