diff --git a/ast/expr.go b/ast/expr.go index a673f4b..e4327d7 100644 --- a/ast/expr.go +++ b/ast/expr.go @@ -2,6 +2,8 @@ package ast type Expr interface { Accept(visitor ExprVisitor) interface{} + StartLine() int + EndLine() int } type AssignExpr struct { @@ -9,6 +11,16 @@ type AssignExpr struct { Value Expr } +func (b AssignExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b AssignExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b AssignExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitAssignExpr(b) } @@ -19,6 +31,14 @@ type BinaryExpr struct { Right Expr } +func (b BinaryExpr) StartLine() int { + return b.Left.StartLine() +} + +func (b BinaryExpr) EndLine() int { + return b.Right.EndLine() +} + func (b BinaryExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitBinaryExpr(b) } @@ -29,6 +49,16 @@ type CallExpr struct { Arguments []Expr } +func (b CallExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b CallExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b CallExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitCallExpr(b) } @@ -40,6 +70,16 @@ type FunctionExpr struct { ReturnType Type } +func (b FunctionExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b FunctionExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b FunctionExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitFunctionExpr(b) } @@ -49,6 +89,16 @@ type GetExpr struct { Name Token } +func (b GetExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b GetExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b GetExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitGetExpr(b) } @@ -57,12 +107,30 @@ type GroupingExpr struct { Expression Expr } +func (b GroupingExpr) StartLine() int { + return b.Expression.StartLine() +} + +func (b GroupingExpr) EndLine() int { + return b.Expression.EndLine() +} + func (b GroupingExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitGroupingExpr(b) } type LiteralExpr struct { - Value interface{} + Value interface{} + LineStart int + LineEnd int +} + +func (b LiteralExpr) StartLine() int { + return b.LineStart +} + +func (b LiteralExpr) EndLine() int { + return b.LineEnd } func (b LiteralExpr) Accept(visitor ExprVisitor) interface{} { @@ -75,6 +143,16 @@ type LogicalExpr struct { Right Expr } +func (b LogicalExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b LogicalExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b LogicalExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitLogicalExpr(b) } @@ -85,6 +163,16 @@ type SetExpr struct { Value Expr } +func (b SetExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b SetExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b SetExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitSetExpr(b) } @@ -94,6 +182,16 @@ type SuperExpr struct { Method Token } +func (b SuperExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b SuperExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b SuperExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitSuperExpr(b) } @@ -102,6 +200,16 @@ type ThisExpr struct { Keyword Token } +func (b ThisExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b ThisExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b ThisExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitThisExpr(b) } @@ -112,6 +220,16 @@ type TernaryExpr struct { Alternate Expr } +func (b TernaryExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b TernaryExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b TernaryExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitTernaryExpr(b) } @@ -121,6 +239,16 @@ type UnaryExpr struct { Right Expr } +func (b UnaryExpr) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b UnaryExpr) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b UnaryExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitUnaryExpr(b) } @@ -129,6 +257,14 @@ type VariableExpr struct { Name Token } +func (b VariableExpr) StartLine() int { + return b.Name.Line +} + +func (b VariableExpr) EndLine() int { + return b.Name.Line +} + func (b VariableExpr) Accept(visitor ExprVisitor) interface{} { return visitor.VisitVariableExpr(b) } diff --git a/ast/stmt.go b/ast/stmt.go index da87f06..95ce671 100644 --- a/ast/stmt.go +++ b/ast/stmt.go @@ -2,12 +2,24 @@ package ast type Stmt interface { Accept(visitor StmtVisitor) interface{} + StartLine() int + EndLine() int } type BlockStmt struct { Statements []Stmt } +func (b BlockStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b BlockStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b BlockStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitBlockStmt(b) } @@ -16,6 +28,16 @@ type ClassStmt struct { Name Token Superclass *VariableExpr Methods []FunctionStmt + LineStart int + LineEnd int +} + +func (b ClassStmt) StartLine() int { + return b.LineStart +} + +func (b ClassStmt) EndLine() int { + return b.LineEnd } func (b ClassStmt) Accept(visitor StmtVisitor) interface{} { @@ -26,6 +48,16 @@ type ExpressionStmt struct { Expr Expr } +func (b ExpressionStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b ExpressionStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b ExpressionStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitExpressionStmt(b) } @@ -37,6 +69,16 @@ type FunctionStmt struct { ReturnType Type } +func (b FunctionStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b FunctionStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b FunctionStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitFunctionStmt(b) } @@ -47,6 +89,16 @@ type IfStmt struct { ElseBranch Stmt } +func (b IfStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b IfStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b IfStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitIfStmt(b) } @@ -55,6 +107,16 @@ type PrintStmt struct { Expr Expr } +func (b PrintStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b PrintStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b PrintStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitPrintStmt(b) } @@ -64,6 +126,16 @@ type ReturnStmt struct { Value Expr } +func (b ReturnStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b ReturnStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b ReturnStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitReturnStmt(b) } @@ -73,6 +145,16 @@ type WhileStmt struct { Body Stmt } +func (b WhileStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b WhileStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b WhileStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitWhileStmt(b) } @@ -80,6 +162,16 @@ func (b WhileStmt) Accept(visitor StmtVisitor) interface{} { type ContinueStmt struct { } +func (b ContinueStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b ContinueStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b ContinueStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitContinueStmt(b) } @@ -87,6 +179,16 @@ func (b ContinueStmt) Accept(visitor StmtVisitor) interface{} { type BreakStmt struct { } +func (b BreakStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b BreakStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b BreakStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitBreakStmt(b) } @@ -97,6 +199,16 @@ type VarStmt struct { TypeDecl Type } +func (b VarStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b VarStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b VarStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitVarStmt(b) } @@ -106,6 +218,16 @@ type TypeDeclStmt struct { Base Type } +func (b TypeDeclStmt) StartLine() int { + // TODO implement me + panic("implement me") +} + +func (b TypeDeclStmt) EndLine() int { + // TODO implement me + panic("implement me") +} + func (b TypeDeclStmt) Accept(visitor StmtVisitor) interface{} { return visitor.VisitTypeDeclStmt(b) } diff --git a/cmd/ast.go b/cmd/ast.go deleted file mode 100644 index bf5b348..0000000 --- a/cmd/ast.go +++ /dev/null @@ -1,109 +0,0 @@ -// Generates AST nodes -package main - -import ( - "fmt" - "go/format" - "io/ioutil" - "strings" -) - -func main() { - writeAst("Expr", []string{ - "Assign : Name Token, Value Expr", - "Binary : Left Expr, Operator Token, Right Expr", - "Call : Callee Expr, Paren Token, Arguments []Expr", - "Function : Name *Token, Params []Param, Body []Stmt, ReturnType Type", - "Get : Object Expr, Name Token", - "Grouping : Expression Expr", - "Literal : Value interface{}", - "Logical : Left Expr, Operator Token, Right Expr", - "Set : Object Expr, Name Token, Value Expr", - "Super : Keyword Token, Method Token", - "This : Keyword Token", - "Ternary : Cond Expr, Consequent Expr, Alternate Expr", - "Unary : Operator Token, Right Expr", - "Variable : Name Token", - }) - - writeAst("Stmt", []string{ - "Block : Statements []Stmt", - "Class : Name Token, Superclass *VariableExpr, Methods []FunctionStmt", - "Expression : Expr Expr", - "Function : Name Token, Params []Param, Body []Stmt, ReturnType Type", - "If : Condition Expr, ThenBranch Stmt, ElseBranch Stmt", - "Print : Expr Expr", - "Return : Keyword Token, Value Expr", - "While : Condition Expr, Body Stmt", - "Continue : ", - "Break : ", - "Var : Name Token, Initializer Expr, TypeDecl Type", - "TypeDecl : Name Token, Base Type", - }) -} - -func writeAst(name string, types []string) { - ast, err := defineAst(name, types) - if err != nil { - panic(err) - } - - filename := "ast/" + strings.ToLower(name) + ".go" - err = ioutil.WriteFile(filename, ast, 0655) - if err != nil { - panic(err) - } -} - -func defineAst(name string, types []string) ([]byte, error) { - var str string - - str += "package ast\n" - str += defineInterface(name) - str += defineTypes(name, types) - str += defineVisitor(name, types) - - // Format code with go fmt - return format.Source([]byte(str)) -} - -func defineInterface(name string) string { - return fmt.Sprintf(` -type %s interface { - Accept(visitor %sVisitor) interface{} -} -`, name, name) -} - -func defineTypes(name string, types []string) (str string) { - for _, t := range types { - splitType := strings.Split(t, ":") - fullTypeName := strings.Trim(splitType[0], " ") + strings.ToUpper(name[:1]) + name[1:] - str += fmt.Sprintf("\ntype %s struct {\n", fullTypeName) - - fields := strings.Split(splitType[1], ", ") - for _, field := range fields { - str += fmt.Sprintf("\t%s\n", strings.Trim(field, " ")) - } - - str += "}\n" - - str += fmt.Sprintf(` -func (b %s) Accept(visitor %sVisitor) interface{} { - return visitor.Visit%s(b) -} -`, fullTypeName, name, fullTypeName) - } - return str -} - -func defineVisitor(name string, types []string) (str string) { - str += fmt.Sprintf("\ntype %sVisitor interface {\n", name) - for _, t := range types { - splitType := strings.Split(t, ":") - fullTypeName := strings.Trim(splitType[0], " ") + strings.ToUpper(name[:1]) + name[1:] - str += fmt.Sprintf("\tVisit%s(%s %s) interface{}\n", fullTypeName, strings.ToLower(name), fullTypeName) - } - str += "}\n" - return str -} diff --git a/parse/parser.go b/parse/parser.go index 64bda01..12a0394 100644 --- a/parse/parser.go +++ b/parse/parser.go @@ -118,6 +118,7 @@ func (p *Parser) declaration() ast.Stmt { } func (p *Parser) classDeclaration() ast.Stmt { + lineStart := p.previous().Line name := p.consume(ast.TokenIdentifier, "Expect class name.") var superclass *ast.VariableExpr @@ -135,7 +136,14 @@ func (p *Parser) classDeclaration() ast.Stmt { } p.consume(ast.TokenRightBrace, "Expect '}' after class body.") - return ast.ClassStmt{Name: name, Methods: methods, Superclass: superclass} + + return ast.ClassStmt{ + Name: name, + Methods: methods, + Superclass: superclass, + LineStart: lineStart, + LineEnd: p.previous().Line, + } } func (p *Parser) varDeclaration() ast.Stmt { @@ -543,13 +551,13 @@ func (p *Parser) finishCall(callee ast.Expr) ast.Expr { func (p *Parser) primary() ast.Expr { switch { case p.match(ast.TokenFalse): - return ast.LiteralExpr{Value: false} + return p.literalAtCurrentLine(false) case p.match(ast.TokenTrue): - return ast.LiteralExpr{Value: true} + return p.literalAtCurrentLine(true) case p.match(ast.TokenNil): - return ast.LiteralExpr{} + return p.literalAtCurrentLine(nil) case p.match(ast.TokenNumber, ast.TokenString): - return ast.LiteralExpr{Value: p.previous().Literal} + return p.literalAtCurrentLine(p.previous().Literal) case p.match(ast.TokenLeftParen): expr := p.expression() p.consume(ast.TokenRightParen, "Expect ')' after expression.") @@ -571,6 +579,11 @@ func (p *Parser) primary() ast.Expr { return nil } +func (p *Parser) literalAtCurrentLine(value interface{}) ast.LiteralExpr { + // TODO: How about multi-line strings? Is that supported? If yes, get the correct value for LineEnd + return ast.LiteralExpr{Value: value, LineStart: p.previous().Line, LineEnd: p.previous().Line} +} + // functionExpression parses a function expression. // A function expression may be a named or anonymous function. func (p *Parser) functionExpression() ast.Expr { diff --git a/typechecker/testdata/binary-expr.golden b/typechecker/testdata/binary-expr.golden index 2901373..7cd2cbe 100644 --- a/typechecker/testdata/binary-expr.golden +++ b/typechecker/testdata/binary-expr.golden @@ -1 +1 @@ -expected 'string' type for {{{23 x %!s()} 12 * %!s() {%!s(float64=10)}} 9 + %!s() {23 y %!s()}} in {{{23 x %!s()} 12 * %!s() {%!s(float64=10)}} 9 + %!s() {23 y %!s()}}, but got 'number' +error on line 3: expected 'string' type, but got 'number' diff --git a/typechecker/testdata/fn-call-with-wrong-arg-types.golden b/typechecker/testdata/fn-call-with-wrong-arg-types.golden index a4dcb09..2059824 100644 --- a/typechecker/testdata/fn-call-with-wrong-arg-types.golden +++ b/typechecker/testdata/fn-call-with-wrong-arg-types.golden @@ -1 +1 @@ -expected 'number' type for {hello} in {{23 addThree %!s()} 1 ) %!s() [{%!s(float64=1)} {hello} {%!s(float64=3)}]}, but got 'string' +error on line 5: expected 'number' type, but got 'string' diff --git a/typechecker/testdata/variable-re-assignment.golden b/typechecker/testdata/variable-re-assignment.golden index 58940c2..79701a5 100644 --- a/typechecker/testdata/variable-re-assignment.golden +++ b/typechecker/testdata/variable-re-assignment.golden @@ -1 +1 @@ -expected 'number' type for {hello} in {23 x %!s() {hello}}, but got 'string' +error on line 2: expected 'number' type, but got 'string' diff --git a/typechecker/testdata/while-stmt.golden b/typechecker/testdata/while-stmt.golden index b1c49ad..79701a5 100644 --- a/typechecker/testdata/while-stmt.golden +++ b/typechecker/testdata/while-stmt.golden @@ -1 +1 @@ -expected 'number' type for {hello} in {{23 x %!s()} 20 >= %!s() {hello}}, but got 'string' +error on line 2: expected 'number' type, but got 'string' diff --git a/typechecker/typechecker.go b/typechecker/typechecker.go index 65bd2f6..9079cfa 100644 --- a/typechecker/typechecker.go +++ b/typechecker/typechecker.go @@ -482,7 +482,7 @@ func (c *TypeChecker) typeFromParsed(parsedType ast.Type) Type { func (c *TypeChecker) expect(actual Type, expected Type, value ast.Expr, expr ast.Expr) Type { if !actual.Equals(expected) { - c.error(fmt.Sprintf("expected '%s' type for %s in %s, but got '%s'", expected.String(), value, expr, actual.String())) + c.error(fmt.Sprintf("error on line %d: expected '%s' type, but got '%s'", value.StartLine()+1, expected.String(), actual.String())) } return actual } diff --git a/typechecker/typechecker_test.go b/typechecker/typechecker_test.go index aca0baa..91b7baa 100644 --- a/typechecker/typechecker_test.go +++ b/typechecker/typechecker_test.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "testing" "github.com/chidiwilliams/glox/ast" @@ -39,10 +40,10 @@ func TestTypeChecker_CheckStmts(t *testing.T) { if err != nil { t.Fatal("error reading golden file", err) } - wantErr := string(want) + wantErr := strings.Trim(string(want), "\n") if typeErr != nil && wantErr == "" || - typeErr != nil && typeErr.Error()+"\n" != wantErr || + typeErr != nil && typeErr.Error() != wantErr || typeErr == nil && wantErr != "" { t.Errorf("Check() error = %v, want %v", strconv.Quote(typeErr.Error()), strconv.Quote(wantErr)) }