add break and exit, remove var
This commit is contained in:
parent
d8318789e1
commit
e7a7d05e64
@ -22,6 +22,7 @@ type Expression interface {
|
|||||||
// Program represents the root of the AST
|
// Program represents the root of the AST
|
||||||
type Program struct {
|
type Program struct {
|
||||||
Statements []Statement
|
Statements []Statement
|
||||||
|
ExitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) String() string {
|
func (p *Program) String() string {
|
||||||
@ -53,6 +54,27 @@ func (es *EchoStatement) String() string {
|
|||||||
return fmt.Sprintf("echo %s", es.Value.String())
|
return fmt.Sprintf("echo %s", es.Value.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BreakStatement represents break statements to exit loops
|
||||||
|
type BreakStatement struct{}
|
||||||
|
|
||||||
|
func (bs *BreakStatement) statementNode() {}
|
||||||
|
func (bs *BreakStatement) String() string {
|
||||||
|
return "break"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitStatement represents exit statements to quit the script
|
||||||
|
type ExitStatement struct {
|
||||||
|
Value Expression // optional, can be nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *ExitStatement) statementNode() {}
|
||||||
|
func (es *ExitStatement) String() string {
|
||||||
|
if es.Value == nil {
|
||||||
|
return "exit"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("exit %s", es.Value.String())
|
||||||
|
}
|
||||||
|
|
||||||
// ElseIfClause represents an elseif condition
|
// ElseIfClause represents an elseif condition
|
||||||
type ElseIfClause struct {
|
type ElseIfClause struct {
|
||||||
Condition Expression
|
Condition Expression
|
||||||
|
@ -117,6 +117,10 @@ func (p *Parser) parseStatement() Statement {
|
|||||||
return p.parseWhileStatement()
|
return p.parseWhileStatement()
|
||||||
case ECHO:
|
case ECHO:
|
||||||
return p.parseEchoStatement()
|
return p.parseEchoStatement()
|
||||||
|
case BREAK:
|
||||||
|
return p.parseBreakStatement()
|
||||||
|
case EXIT:
|
||||||
|
return p.parseExitStatement()
|
||||||
case ASSIGN:
|
case ASSIGN:
|
||||||
p.addError("assignment operator '=' without left-hand side identifier")
|
p.addError("assignment operator '=' without left-hand side identifier")
|
||||||
return nil
|
return nil
|
||||||
@ -187,6 +191,39 @@ func (p *Parser) parseEchoStatement() *EchoStatement {
|
|||||||
return stmt
|
return stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseBreakStatement parses break statements
|
||||||
|
func (p *Parser) parseBreakStatement() *BreakStatement {
|
||||||
|
return &BreakStatement{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExitStatement parses exit statements
|
||||||
|
func (p *Parser) parseExitStatement() *ExitStatement {
|
||||||
|
stmt := &ExitStatement{}
|
||||||
|
|
||||||
|
// Check if there's an optional expression after 'exit'
|
||||||
|
// Only parse expression if next token can start an expression
|
||||||
|
if p.canStartExpression(p.peekToken.Type) {
|
||||||
|
p.nextToken() // move past 'exit'
|
||||||
|
stmt.Value = p.ParseExpression(LOWEST)
|
||||||
|
if stmt.Value == nil {
|
||||||
|
p.addError("expected expression after 'exit'")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parseWhileStatement parses while loops: while condition do ... end
|
// parseWhileStatement parses while loops: while condition do ... end
|
||||||
func (p *Parser) parseWhileStatement() *WhileStatement {
|
func (p *Parser) parseWhileStatement() *WhileStatement {
|
||||||
stmt := &WhileStatement{}
|
stmt := &WhileStatement{}
|
||||||
@ -739,7 +776,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, VAR, IF, THEN, ELSEIF, ELSE, END, ECHO, FOR, WHILE, IN, DO:
|
case TRUE, FALSE, NIL, IF, THEN, ELSEIF, ELSE, END, ECHO, FOR, WHILE, IN, DO, BREAK, EXIT:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -873,8 +910,6 @@ func tokenTypeString(t TokenType) string {
|
|||||||
return "]"
|
return "]"
|
||||||
case COMMA:
|
case COMMA:
|
||||||
return ","
|
return ","
|
||||||
case VAR:
|
|
||||||
return "var"
|
|
||||||
case IF:
|
case IF:
|
||||||
return "if"
|
return "if"
|
||||||
case THEN:
|
case THEN:
|
||||||
@ -895,6 +930,10 @@ func tokenTypeString(t TokenType) string {
|
|||||||
return "in"
|
return "in"
|
||||||
case DO:
|
case DO:
|
||||||
return "do"
|
return "do"
|
||||||
|
case BREAK:
|
||||||
|
return "break"
|
||||||
|
case EXIT:
|
||||||
|
return "exit"
|
||||||
case EOF:
|
case EOF:
|
||||||
return "end of file"
|
return "end of file"
|
||||||
case ILLEGAL:
|
case ILLEGAL:
|
||||||
|
428
parser/tests/breakexit_test.go
Normal file
428
parser/tests/breakexit_test.go
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
package parser_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sharkk/Mako/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBreakStatement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"break", "standalone break"},
|
||||||
|
{"while true do\nbreak\nend", "break in while loop"},
|
||||||
|
{"for i = 1, 10 do\nbreak\nend", "break in numeric for loop"},
|
||||||
|
{"for k, v in table do\nbreak\nend", "break in for-in loop"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, stmt := range program.Statements {
|
||||||
|
if _, ok := stmt.(*parser.BreakStatement); ok {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Check nested statements in loops
|
||||||
|
if whileStmt, ok := stmt.(*parser.WhileStatement); ok {
|
||||||
|
for _, bodyStmt := range whileStmt.Body {
|
||||||
|
if _, ok := bodyStmt.(*parser.BreakStatement); ok {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if forStmt, ok := stmt.(*parser.ForStatement); ok {
|
||||||
|
for _, bodyStmt := range forStmt.Body {
|
||||||
|
if _, ok := bodyStmt.(*parser.BreakStatement); ok {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if forInStmt, ok := stmt.(*parser.ForInStatement); ok {
|
||||||
|
for _, bodyStmt := range forInStmt.Body {
|
||||||
|
if _, ok := bodyStmt.(*parser.BreakStatement); ok {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Error("expected to find BreakStatement")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBreakStringRepresentation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"break", "break", "simple break"},
|
||||||
|
{"while true do\nbreak\nend", "while true do\n\tbreak\nend", "break in while"},
|
||||||
|
{"for i = 1, 10 do\necho i\nbreak\nend", "for i = 1.00, 10.00 do\n\techo i\n\tbreak\nend", "break with other statements"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
result := strings.TrimSpace(program.String())
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("expected %q, got %q", tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExitStatement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
hasValue bool
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"exit", false, "exit without value"},
|
||||||
|
{"exit 0", true, "exit with number"},
|
||||||
|
{"exit 1", true, "exit with error code"},
|
||||||
|
{"exit \"success\"", true, "exit with string"},
|
||||||
|
{"exit x", true, "exit with variable"},
|
||||||
|
{"exit 1 + 2", true, "exit with expression"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 1 {
|
||||||
|
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, ok := program.Statements[0].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected ExitStatement, got %T", program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.hasValue && stmt.Value == nil {
|
||||||
|
t.Error("expected exit value but got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.hasValue && stmt.Value != nil {
|
||||||
|
t.Error("expected no exit value but got one")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExitStringRepresentation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"exit", "exit", "simple exit"},
|
||||||
|
{"exit 0", "exit 0.00", "exit with number"},
|
||||||
|
{"exit \"message\"", "exit \"message\"", "exit with string"},
|
||||||
|
{"exit x + 1", "exit (x + 1.00)", "exit with expression"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
result := strings.TrimSpace(program.String())
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("expected %q, got %q", tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedBreakInComplexLoops(t *testing.T) {
|
||||||
|
input := `for i = 1, 10 do
|
||||||
|
while condition do
|
||||||
|
if found then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
x = x + 1
|
||||||
|
end
|
||||||
|
for k, v in data do
|
||||||
|
if v == target then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end`
|
||||||
|
|
||||||
|
l := parser.NewLexer(input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 1 {
|
||||||
|
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
outerFor, ok := program.Statements[0].(*parser.ForStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected ForStatement, got %T", program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outerFor.Body) != 2 {
|
||||||
|
t.Fatalf("expected 2 body statements, got %d", len(outerFor.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First: while loop with break
|
||||||
|
whileStmt, ok := outerFor.Body[0].(*parser.WhileStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected WhileStatement, got %T", outerFor.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that while body contains if with break
|
||||||
|
ifStmt, ok := whileStmt.Body[0].(*parser.IfStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected IfStatement in while body, got %T", whileStmt.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = ifStmt.Body[0].(*parser.BreakStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected BreakStatement in if body, got %T", ifStmt.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second: for-in loop with break
|
||||||
|
forInStmt, ok := outerFor.Body[1].(*parser.ForInStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected ForInStatement, got %T", outerFor.Body[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that for-in body contains if with break
|
||||||
|
ifStmt2, ok := forInStmt.Body[0].(*parser.IfStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected IfStatement in for-in body, got %T", forInStmt.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = ifStmt2.Body[0].(*parser.BreakStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected BreakStatement in if body, got %T", ifStmt2.Body[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExitInMixedProgram(t *testing.T) {
|
||||||
|
input := `x = 42
|
||||||
|
if x > 50 then
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "continuing"
|
||||||
|
end
|
||||||
|
y = "done"
|
||||||
|
exit "success"`
|
||||||
|
|
||||||
|
l := parser.NewLexer(input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 4 {
|
||||||
|
t.Fatalf("expected 4 statements, got %d", len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First: assignment
|
||||||
|
_, ok := program.Statements[0].(*parser.AssignStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 0: expected AssignStatement, got %T", program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second: if statement with exit in body
|
||||||
|
ifStmt, ok := program.Statements[1].(*parser.IfStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 1: expected IfStatement, got %T", program.Statements[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = ifStmt.Body[0].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("if body: expected ExitStatement, got %T", ifStmt.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third: assignment
|
||||||
|
_, ok = program.Statements[2].(*parser.AssignStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 2: expected AssignStatement, got %T", program.Statements[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth: final exit
|
||||||
|
_, ok = program.Statements[3].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 3: expected ExitStatement, got %T", program.Statements[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExitWithComplexExpressions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"exit table.errorCode", "exit with member access"},
|
||||||
|
{"exit arr[0]", "exit with index access"},
|
||||||
|
{"exit status + 1", "exit with arithmetic"},
|
||||||
|
{"exit {code = 1, msg = \"error\"}", "exit with table"},
|
||||||
|
{"exit obj.nested.value", "exit with chained access"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 1 {
|
||||||
|
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, ok := program.Statements[0].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected ExitStatement, got %T", program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if stmt.Value == nil {
|
||||||
|
t.Error("expected exit value but got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControlFlowErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expectedError string
|
||||||
|
desc string
|
||||||
|
}{
|
||||||
|
{"break x", "unexpected identifier", "break with argument"},
|
||||||
|
{"exit +", "unexpected token '+'", "exit with invalid expression"},
|
||||||
|
{"exit (", "unexpected end of input", "exit with incomplete expression"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
l := parser.NewLexer(tt.input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
p.ParseProgram()
|
||||||
|
|
||||||
|
if !p.HasErrors() {
|
||||||
|
t.Fatal("expected parsing errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := p.Errors()
|
||||||
|
found := false
|
||||||
|
for _, err := range errors {
|
||||||
|
if strings.Contains(err.Message, tt.expectedError) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
errorMsgs := make([]string, len(errors))
|
||||||
|
for i, err := range errors {
|
||||||
|
errorMsgs[i] = err.Message
|
||||||
|
}
|
||||||
|
t.Errorf("expected error containing %q, got %v", tt.expectedError, errorMsgs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProgramExitCode(t *testing.T) {
|
||||||
|
input := `x = 42
|
||||||
|
echo x`
|
||||||
|
|
||||||
|
l := parser.NewLexer(input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
// Program should have default exit code of 0
|
||||||
|
if program.ExitCode != 0 {
|
||||||
|
t.Errorf("expected default exit code 0, got %d", program.ExitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBreakAndExitInSameProgram(t *testing.T) {
|
||||||
|
input := `for i = 1, 100 do
|
||||||
|
if i == 10 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if i > 50 then
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
echo i
|
||||||
|
end
|
||||||
|
exit "completed"`
|
||||||
|
|
||||||
|
l := parser.NewLexer(input)
|
||||||
|
p := parser.NewParser(l)
|
||||||
|
program := p.ParseProgram()
|
||||||
|
checkParserErrors(t, p)
|
||||||
|
|
||||||
|
if len(program.Statements) != 2 {
|
||||||
|
t.Fatalf("expected 2 statements, got %d", len(program.Statements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First: for loop with break and exit
|
||||||
|
forStmt, ok := program.Statements[0].(*parser.ForStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 0: expected ForStatement, got %T", program.Statements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(forStmt.Body) != 3 {
|
||||||
|
t.Fatalf("expected 3 body statements, got %d", len(forStmt.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for break in first if
|
||||||
|
if1, ok := forStmt.Body[0].(*parser.IfStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("body[0]: expected IfStatement, got %T", forStmt.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = if1.Body[0].(*parser.BreakStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("if1 body: expected BreakStatement, got %T", if1.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for exit in second if
|
||||||
|
if2, ok := forStmt.Body[1].(*parser.IfStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("body[1]: expected IfStatement, got %T", forStmt.Body[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = if2.Body[0].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("if2 body: expected ExitStatement, got %T", if2.Body[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second: final exit
|
||||||
|
_, ok = program.Statements[1].(*parser.ExitStatement)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("statement 1: expected ExitStatement, got %T", program.Statements[1])
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,6 @@ const (
|
|||||||
COMMA // ,
|
COMMA // ,
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
VAR
|
|
||||||
IF
|
IF
|
||||||
THEN
|
THEN
|
||||||
ELSEIF
|
ELSEIF
|
||||||
@ -49,6 +48,8 @@ const (
|
|||||||
WHILE
|
WHILE
|
||||||
IN
|
IN
|
||||||
DO
|
DO
|
||||||
|
BREAK
|
||||||
|
EXIT
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
EOF
|
EOF
|
||||||
@ -97,7 +98,6 @@ var precedences = map[TokenType]Precedence{
|
|||||||
// lookupIdent checks if an identifier is a keyword
|
// lookupIdent checks if an identifier is a keyword
|
||||||
func lookupIdent(ident string) TokenType {
|
func lookupIdent(ident string) TokenType {
|
||||||
keywords := map[string]TokenType{
|
keywords := map[string]TokenType{
|
||||||
"var": VAR,
|
|
||||||
"true": TRUE,
|
"true": TRUE,
|
||||||
"false": FALSE,
|
"false": FALSE,
|
||||||
"nil": NIL,
|
"nil": NIL,
|
||||||
@ -111,6 +111,8 @@ func lookupIdent(ident string) TokenType {
|
|||||||
"while": WHILE,
|
"while": WHILE,
|
||||||
"in": IN,
|
"in": IN,
|
||||||
"do": DO,
|
"do": DO,
|
||||||
|
"break": BREAK,
|
||||||
|
"exit": EXIT,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tok, ok := keywords[ident]; ok {
|
if tok, ok := keywords[ident]; ok {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user