From ac79785b59fc354d57cbe332f6306ede4459ce4d Mon Sep 17 00:00:00 2001 From: Jun Nishimura Date: Sun, 29 Sep 2024 21:48:54 +0900 Subject: [PATCH] handle with array in parser (#19) --- parser/parser.go | 121 +++++++++++------------------- parser/parser_test.go | 169 ++++++++++++++++++++++++++---------------- 2 files changed, 151 insertions(+), 139 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 2d67f28..e4ba68a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1,7 +1,6 @@ package parser import ( - "errors" "fmt" "strconv" @@ -30,94 +29,26 @@ func New(l *lexer.Lexer) *Parser { return p } -func (p *Parser) Errors() []string { - out := []string{} - - if errs, ok := p.errors.(interface{ Unwrap() []error }); ok { - for _, err := range errs.Unwrap() { - out = append(out, err.Error()) - } - } else { - out = append(out, p.errors.Error()) - } - - return out -} - -func (p *Parser) appendError(err error) { - p.errors = errors.Join(p.errors, err) -} - func (p *Parser) nextToken() { p.curToken = p.peekToken p.peekToken = p.l.NextToken() } -func (p *Parser) ParseProgram() (*ast.Program, error) { +func (p *Parser) ParseProgram() (ast.Expression, error) { if p.curTokenIs(token.EOF) { return nil, nil } - var err error - program := &ast.Program{ - Expressions: []ast.Expression{}, - } - - if p.curTokenIs(token.LBRACKET) { - program, err = p.parseMultipleExpressions() - if err != nil { - p.appendError(err) - } - } else { - exp, err := p.parseExpression() - if err != nil { - p.appendError(err) - } - - program.Expressions = append(program.Expressions, exp) - } - - if !p.curTokenIs(token.EOF) { - p.appendError(fmt.Errorf("expected EOF, got %s instead", p.curToken.Type)) - } - - if p.errors != nil { - return nil, p.errors - } - - return program, nil -} - -func (p *Parser) parseMultipleExpressions() (*ast.Program, error) { - if err := p.expectCurToken(token.LBRACKET); err != nil { - p.appendError(err) - } - - program := &ast.Program{ - Expressions: []ast.Expression{}, + exp, err := p.parseExpression() + if err != nil { + return nil, err } - for { - exp, err := p.parseExpression() - if err != nil { - p.appendError(err) - } - - program.Expressions = append(program.Expressions, exp) - - if p.curTokenIs(token.COMMA) { - // if the program still has more expressions, skip the comma and continue - p.nextToken() - } else { - // if the program has no more expressions, break the loop - if err := p.expectCurToken(token.RBRACKET); err != nil { - p.appendError(err) - } - break - } + if err := p.expectCurToken(token.EOF); err != nil { + return nil, err } - return program, nil + return exp, nil } func (p *Parser) curTokenIs(t token.TokenType) bool { @@ -252,6 +183,7 @@ func (p *Parser) parseCommand() (*ast.CommandObject, error) { }, nil } +// TODO: parseArgs should be able to parse only one argument without brackets func (p *Parser) parseArgs() ([]ast.Expression, error) { args := []ast.Expression{} @@ -426,6 +358,8 @@ func (p *Parser) parseAtom() (ast.Expression, error) { return p.parseBoolean() case token.DOUBLE_QUOTE: return p.parseDoubleQuotedString() + case token.LBRACKET: + return p.parseArray() default: err := fmt.Errorf("unexpected token type %s", p.curToken.Type) p.nextToken() @@ -495,6 +429,41 @@ func (p *Parser) parseDoubleQuotedString() (ast.Expression, error) { return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type) } +func (p *Parser) parseArray() (*ast.Array, error) { + if !p.curTokenIs(token.LBRACKET) { + return nil, fmt.Errorf("expected LBRACKET, got %s instead", p.curToken.Type) + } + arrayToken := p.curToken + p.nextToken() + + elements := []ast.Expression{} + + // empty array + if p.curTokenIs(token.RBRACKET) { + p.nextToken() + return &ast.Array{Token: arrayToken, Elements: elements}, nil + } + + for { + element, err := p.parseExpression() + if err != nil { + return nil, err + } + elements = append(elements, element) + + if p.curTokenIs(token.COMMA) { + p.nextToken() + } else { + if err := p.expectCurToken(token.RBRACKET); err != nil { + return nil, err + } + break + } + } + + return &ast.Array{Token: arrayToken, Elements: elements}, nil +} + func (p *Parser) parseSymbol() (*ast.Symbol, error) { if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil { return nil, err diff --git a/parser/parser_test.go b/parser/parser_test.go index a4b7a63..c67be4e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -8,19 +8,6 @@ import ( "github.com/JunNishimura/jsop/token" ) -func checkParserErrors(t *testing.T, p *Parser) { - errors := p.Errors() - if len(errors) == 0 { - return - } - - t.Errorf("parser has %d errors", len(errors)) - for _, msg := range errors { - t.Errorf("parser error: %q", msg) - } - t.FailNow() -} - func TestIntegerAtom(t *testing.T) { tests := []struct { name string @@ -46,15 +33,12 @@ func TestIntegerAtom(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - intAtom, ok := program.Expressions[0].(*ast.IntegerLiteral) + intAtom, ok := program.(*ast.IntegerLiteral) if !ok { - t.Fatalf("exp not *ast.Integer. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.Integer. got=%T", program) } if intAtom.Value != tt.expected { t.Fatalf("intAtom.Value not %d. got=%d", tt.expected, intAtom.Value) @@ -94,15 +78,12 @@ func TestBooleanAtom(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - boolean, ok := program.Expressions[0].(*ast.Boolean) + boolean, ok := program.(*ast.Boolean) if !ok { - t.Fatalf("exp not *ast.Boolean. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.Boolean. got=%T", program) } if boolean.String() != tt.expected.String() { t.Fatalf("boolean.String() not %q. got=%q", tt.expected.String(), boolean.String()) @@ -162,15 +143,12 @@ func TestPrefixAtom(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - prefixAtom, ok := program.Expressions[0].(*ast.PrefixAtom) + prefixAtom, ok := program.(*ast.PrefixAtom) if !ok { - t.Fatalf("exp not *ast.PrefixAtom. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.PrefixAtom. got=%T", program) } if prefixAtom.String() != tt.expected.String() { t.Fatalf("prefixAtom.String() not %q. got=%q", tt.expected.String(), prefixAtom.String()) @@ -179,6 +157,77 @@ func TestPrefixAtom(t *testing.T) { } } +func TestArrayAtom(t *testing.T) { + tests := []struct { + name string + input string + expected *ast.Array + }{ + { + name: "empty array", + input: "[]", + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{}, + }, + }, + { + name: "array with 1 element", + input: "[1]", + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{ + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + }, + }, + }, + { + name: "array with multiple elements", + input: "[1, 2, 3]", + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{ + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "1"}, + Value: 1, + }, + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "2"}, + Value: 2, + }, + &ast.IntegerLiteral{ + Token: token.Token{Type: token.INT, Literal: "3"}, + Value: 3, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := lexer.New(tt.input) + p := New(l) + + program, err := p.ParseProgram() + if err != nil { + t.Fatalf("ParseProgram() error: %v", err) + } + + array, ok := program.(*ast.Array) + if !ok { + t.Fatalf("exp not *ast.Array. got=%T", program) + } + if array.String() != tt.expected.String() { + t.Fatalf("array.String() not %q. got=%q", tt.expected.String(), array.String()) + } + }) + } +} + func TestCommand(t *testing.T) { tests := []struct { name string @@ -356,15 +405,12 @@ func TestCommand(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - command, ok := program.Expressions[0].(*ast.CommandObject) + command, ok := program.(*ast.CommandObject) if !ok { - t.Fatalf("exp not *ast.Command. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.Command. got=%T", program) } if command.String() != tt.expected.String() { t.Fatalf("command.String() not %q. got=%q", tt.expected.String(), command.String()) @@ -516,15 +562,12 @@ func TestIfExpression(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - ifExp, ok := program.Expressions[0].(*ast.IfExpression) + ifExp, ok := program.(*ast.IfExpression) if !ok { - t.Fatalf("exp not *ast.IfExpression. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.IfExpression. got=%T", program) } if ifExp.String() != tt.expected.String() { t.Fatalf("ifExp.String() not %q. got=%q", tt.expected.String(), ifExp.String()) @@ -608,15 +651,12 @@ func TestSetExpression(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) - } - if len(program.Expressions) != 1 { - t.Fatalf("program.Expressions does not contain 1 expression. got=%d", len(program.Expressions)) + t.Fatalf("ParseProgram() error: %v", err) } - setExp, ok := program.Expressions[0].(*ast.SetExpression) + setExp, ok := program.(*ast.SetExpression) if !ok { - t.Fatalf("exp not *ast.SetExpression. got=%T", program.Expressions[0]) + t.Fatalf("exp not *ast.SetExpression. got=%T", program) } if setExp.String() != tt.expected.String() { t.Fatalf("setExp.String() not %q. got=%q", tt.expected.String(), setExp.String()) @@ -629,7 +669,7 @@ func TestPrograms(t *testing.T) { tests := []struct { name string input string - expected *ast.Program + expected ast.Expression }{ { name: "multiple atoms", @@ -639,8 +679,9 @@ func TestPrograms(t *testing.T) { true ] `, - expected: &ast.Program{ - Expressions: []ast.Expression{ + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{ &ast.IntegerLiteral{ Token: token.Token{Type: token.INT, Literal: "1"}, Value: 1, @@ -670,8 +711,9 @@ func TestPrograms(t *testing.T) { } ] `, - expected: &ast.Program{ - Expressions: []ast.Expression{ + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{ &ast.CommandObject{ Token: token.Token{Type: token.COMMAND, Literal: "command"}, Symbol: &ast.Symbol{ @@ -717,8 +759,9 @@ func TestPrograms(t *testing.T) { "$x" ] `, - expected: &ast.Program{ - Expressions: []ast.Expression{ + expected: &ast.Array{ + Token: token.Token{Type: token.LBRACKET, Literal: "["}, + Elements: []ast.Expression{ &ast.SetExpression{ Token: token.Token{Type: token.SET, Literal: "set"}, Name: &ast.Symbol{ @@ -746,16 +789,16 @@ func TestPrograms(t *testing.T) { program, err := p.ParseProgram() if err != nil { - checkParserErrors(t, p) + t.Fatalf("ParseProgram() error: %v", err) } - if len(program.Expressions) != len(tt.expected.Expressions) { - t.Fatalf("program.Expressions does not contain %d expressions. got=%d", len(tt.expected.Expressions), len(program.Expressions)) + + array, ok := program.(*ast.Array) + if !ok { + t.Fatalf("exp not *ast.Array. got=%T", program) } - for i, exp := range tt.expected.Expressions { - if program.Expressions[i].String() != exp.String() { - t.Fatalf("exp.String() not %q. got=%q", exp.String(), program.Expressions[i].String()) - } + if array.String() != tt.expected.String() { + t.Fatalf("array.String() not %q. got=%q", tt.expected.String(), array.String()) } }) }