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, ", "))
|
||||
}
|
||||
|
||||
// PrefixExpression represents prefix operations like -x
|
||||
// PrefixExpression represents prefix operations like -x, not x
|
||||
type PrefixExpression struct {
|
||||
Operator string
|
||||
Right Expression
|
||||
@ -313,6 +313,10 @@ type PrefixExpression struct {
|
||||
|
||||
func (pe *PrefixExpression) expressionNode() {}
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ func NewParser(lexer *Lexer) *Parser {
|
||||
p.registerPrefix(LPAREN, p.parseGroupedExpression)
|
||||
p.registerPrefix(LBRACE, p.parseTableLiteral)
|
||||
p.registerPrefix(MINUS, p.parsePrefixExpression)
|
||||
p.registerPrefix(NOT, p.parsePrefixExpression)
|
||||
p.registerPrefix(FN, p.parseFunctionLiteral)
|
||||
|
||||
p.infixParseFns = make(map[TokenType]func(Expression) Expression)
|
||||
@ -68,6 +69,8 @@ func NewParser(lexer *Lexer) *Parser {
|
||||
p.registerInfix(GT, p.parseInfixExpression)
|
||||
p.registerInfix(LT_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(LBRACKET, p.parseIndexExpression)
|
||||
p.registerInfix(LPAREN, p.parseCallExpression)
|
||||
@ -320,7 +323,7 @@ func (p *Parser) parseReturnStatement() *ReturnStatement {
|
||||
// canStartExpression checks if a token type can start an expression
|
||||
func (p *Parser) canStartExpression(tokenType TokenType) bool {
|
||||
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
|
||||
default:
|
||||
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
|
||||
func (p *Parser) isKeyword(t TokenType) bool {
|
||||
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
|
||||
default:
|
||||
return false
|
||||
@ -1123,6 +1126,12 @@ func tokenTypeString(t TokenType) string {
|
||||
return "<="
|
||||
case GT_EQ:
|
||||
return ">="
|
||||
case AND:
|
||||
return "and"
|
||||
case OR:
|
||||
return "or"
|
||||
case NOT:
|
||||
return "not"
|
||||
case LPAREN:
|
||||
return "("
|
||||
case RPAREN:
|
||||
|
@ -16,6 +16,10 @@ func TestPrefixExpressions(t *testing.T) {
|
||||
{"-x", "-", "x"},
|
||||
{"-true", "-", true},
|
||||
{"-(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 {
|
||||
@ -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) {
|
||||
tests := []struct {
|
||||
input string
|
||||
@ -155,6 +209,25 @@ func TestOperatorPrecedence(t *testing.T) {
|
||||
{"-1 + 2", "((-1.00) + 2.00)"},
|
||||
{"-(1 + 2)", "(-(1.00 + 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
|
||||
{"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)"},
|
||||
{"obj.x == obj.y", "(obj.x == obj.y)"},
|
||||
{"-table.value", "(-table.value)"},
|
||||
{"not obj.active", "(not obj.active)"},
|
||||
{"obj.x and obj.y", "(obj.x and obj.y)"},
|
||||
|
||||
// Complex combinations
|
||||
{"-x + y * z == a.b", "(((-x) + (y * z)) == a.b)"},
|
||||
{"(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 {
|
||||
@ -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) {
|
||||
tests := []struct {
|
||||
input string
|
||||
@ -201,7 +312,7 @@ func TestComplexExpressionsWithComparisons(t *testing.T) {
|
||||
{"table.count > arr[0] + 5", "member access comparison"},
|
||||
{"-value <= max", "prefix 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 {
|
||||
@ -209,12 +320,6 @@ func TestComplexExpressionsWithComparisons(t *testing.T) {
|
||||
l := parser.NewLexer(tt.input)
|
||||
p := parser.NewParser(l)
|
||||
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)
|
||||
|
||||
if expr == nil {
|
||||
|
@ -28,6 +28,11 @@ const (
|
||||
LT_EQ // <=
|
||||
GT_EQ // >=
|
||||
|
||||
// Logical operators
|
||||
AND // and
|
||||
OR // or
|
||||
NOT // not
|
||||
|
||||
// Delimiters
|
||||
LPAREN // (
|
||||
RPAREN // )
|
||||
@ -73,17 +78,21 @@ type Precedence int
|
||||
const (
|
||||
_ Precedence = iota
|
||||
LOWEST
|
||||
PREC_OR // or
|
||||
PREC_AND // and
|
||||
EQUALS // ==, !=
|
||||
LESSGREATER // >, <, >=, <=
|
||||
SUM // +, -
|
||||
PRODUCT // *, /
|
||||
PREFIX // -x, !x
|
||||
PREFIX // -x, not x
|
||||
MEMBER // table[key], table.key
|
||||
CALL // function()
|
||||
)
|
||||
|
||||
// precedences maps token types to their precedence levels
|
||||
var precedences = map[TokenType]Precedence{
|
||||
OR: PREC_OR,
|
||||
AND: PREC_AND,
|
||||
EQ: EQUALS,
|
||||
NOT_EQ: EQUALS,
|
||||
LT: LESSGREATER,
|
||||
@ -105,6 +114,9 @@ func lookupIdent(ident string) TokenType {
|
||||
"true": TRUE,
|
||||
"false": FALSE,
|
||||
"nil": NIL,
|
||||
"and": AND,
|
||||
"or": OR,
|
||||
"not": NOT,
|
||||
"if": IF,
|
||||
"then": THEN,
|
||||
"elseif": ELSEIF,
|
||||
|
Loading…
x
Reference in New Issue
Block a user