diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml new file mode 100644 index 0000000..f399640 --- /dev/null +++ b/.github/workflows/go.yaml @@ -0,0 +1,91 @@ +name: go +on: + push: + branches: + - master + pull_request: +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: stable + cache-dependency-path: go.sum + + - name: Generate code + run: go generate ./... + working-directory: go + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + install-mode: goinstall + version: latest + working-directory: go + args: --config="${GITHUB_WORKSPACE}/go/.golangci.yaml" + + test: + name: test + runs-on: ubuntu-latest + defaults: + run: + working-directory: go + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: stable + cache-dependency-path: go.sum + + - name: Install dependencies + run: go mod tidy + + - name: Generate code + run: go generate ./... + + - name: Compile + run: go build -v ./... + + - name: Run tests + run: go test -v ./... + + benchmark: + name: benchmark + runs-on: ubuntu-latest + defaults: + run: + working-directory: go + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: stable + cache-dependency-path: go.sum + + - name: Generate code + run: go generate ./... + + - name: Run benchmark + run: go test ./cmd/glox -bench=. -count 5 -run=^# -timeout 30m | tee output.txt + + - name: Download previous benchmark data + uses: actions/cache@v4 + with: + path: ./cache + key: ${{ runner.os }}-benchmark + + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: 'go' + output-file-path: go/output.txt + external-data-json-path: ./cache/benchmark-data.json + fail-on-alert: true + github-token: ${{ secrets.GITHUB_TOKEN }} + comment-on-alert: true diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml deleted file mode 100644 index ae7b71b..0000000 --- a/.github/workflows/golangci-lint.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: golangci-lint -on: - push: - branches: - - master -permissions: - contents: read -jobs: - golangci: - name: golangci-lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: stable - cache-dependency-path: go/go.sum - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - install-mode: "goinstall" - version: v1.59 - working-directory: go - args: --config="${GITHUB_WORKSPACE}/go/.golangci.yaml" diff --git a/README.md b/README.md index b3c7e76..a14393e 100644 --- a/README.md +++ b/README.md @@ -14,37 +14,37 @@ The test suite in `test/official_tests` comes from the [original implementation] | Feature | Implementation | | -------------------- | ------------------ | -| assignment | :x: | -| benchmark | :x: | +| assignment | :white_check_mark: | +| benchmark | :white_check_mark: | | block | :white_check_mark: | | bool | :white_check_mark: | -| call | :x: | -| class | :x: | -| closure | :x: | +| call | :white_check_mark: | +| class | :white_check_mark: | +| closure | :white_check_mark: | | comments | :white_check_mark: | -| constructor | :x: | -| field | :x: | -| for | :x: | -| function | :x: | -| if | :x: | -| inheritance | :x: | +| constructor | :white_check_mark: | +| field | :white_check_mark: | +| for | :white_check_mark: | +| function | :white_check_mark: | +| if | :white_check_mark: | +| inheritance | :white_check_mark: | | limit | :x: | | logical operator | :white_check_mark: | -| method | :x: | +| method | :white_check_mark: | | nil | :white_check_mark: | | number | :x: | -| operator | :x: | -| print | :x: | -| regression | :x: | -| return | :x: | -| scanning | :x: | -| string | :x: | -| super | :x: | -| this | :x: | -| variable | :x: | -| while | :x: | +| operator | :white_check_mark: | +| print | :white_check_mark: | +| regression | :white_check_mark: | +| return | :white_check_mark: | +| scanning | :white_check_mark: | +| string | :white_check_mark: | +| super | :white_check_mark: | +| this | :white_check_mark: | +| variable | :white_check_mark: | +| while | :white_check_mark: | | empty file | :white_check_mark: | | precedence | :white_check_mark: | -| unexpected character | :x: | +| unexpected character | :white_check_mark: | \ No newline at end of file diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 0000000..8eefba5 --- /dev/null +++ b/go/.gitignore @@ -0,0 +1 @@ +generated* diff --git a/go/.golangci.yaml b/go/.golangci.yaml index 2b0ecd7..4ed54eb 100644 --- a/go/.golangci.yaml +++ b/go/.golangci.yaml @@ -2,7 +2,9 @@ run: timeout: 30m tests: true output: - format: colored-line-number + formats: + - format: colored-line-number + path: stderr print-issued-lines: true linters-settings: gomnd: @@ -27,4 +29,14 @@ linters: - nlreturn - nonamedreturns - varnamelen - - wsl \ No newline at end of file + - wrapcheck + - wsl +issues: + exclude-rules: + - path: .*generated-errors\.go + linters: + - goconst + - gofmt + - goimports + - gosimple + - perfsprint diff --git a/go/cmd/code-generator/errors_template.go.tmpl b/go/cmd/code-generator/errors_template.go.tmpl new file mode 100644 index 0000000..1cd2e6f --- /dev/null +++ b/go/cmd/code-generator/errors_template.go.tmpl @@ -0,0 +1,38 @@ +package {{ .Package }} + +{{- if eq (len .Imports) 1 }} +import "{{ index .Imports 0 }}" +{{- else }} +import ( + {{- range .Imports }} + "{{ . }}" + {{- end }} +) +{{- end }} + +{{ range .Types}} +type {{ .Name }} struct { + line int + {{ range .Fields }}{{ .Name }} {{ .Type }} + {{ end }} +} + +func New{{ .Name }}(line int{{ range .Fields }}, {{ .Name }} {{ .Type }}{{ end }}) *{{ .Name }} { + return &{{ .Name }}{ + line: line, + {{ range .Fields }}{{ .Name }}: {{ .Name }}, {{ end }} + } +} + +func (e {{ .Name }}) Line() int { + return e.line +} + +func (e {{ .Name }}) Kind() string { + return "{{ $.ErrorKind }}" +} + +func (e {{ .Name }}) Message() string { + return fmt.Sprintf("{{ .Message }}", {{ range .Fields }}e.{{ .Name }}, {{ end }}) +} +{{ end }} diff --git a/go/cmd/code-generator/generate_errors.go b/go/cmd/code-generator/generate_errors.go new file mode 100644 index 0000000..baa6bb1 --- /dev/null +++ b/go/cmd/code-generator/generate_errors.go @@ -0,0 +1,193 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "text/template" +) + +type Field struct { + Name string + Type string +} + +type ErrorType struct { + Name string + Fields []Field + Message string +} + +type Data struct { + Package string + ErrorKind string + Imports []string + Types []ErrorType +} + +var lexingErrors = Data{ + Package: "lexer", + ErrorKind: "LexingError", + Imports: []string{"fmt"}, + Types: []ErrorType{ + { + Name: "UnexpectedCharacter", + Fields: []Field{ + {Name: "character", Type: "byte"}, + }, + Message: "Unexpected character '%c'", + }, + { + Name: "UnterminatedString", + Fields: []Field{}, + Message: "Unterminated string literal", + }, + { + Name: "InvalidFloat", + Fields: []Field{ + {Name: "text", Type: "string"}, + }, + Message: "Error converting %s to float", + }, + }, +} + +var runtimeErrors = Data{ + Package: "runtime", + ErrorKind: "RuntimeError", + Imports: []string{"fmt"}, + Types: []ErrorType{ + { + Name: "UndefinedVariable", + Fields: []Field{ + {Name: "identifier", Type: "string"}, + }, + Message: "Undefined variable '%s'", + }, + { + Name: "UndefinedProperty", + Fields: []Field{ + {Name: "propertyName", Type: "string"}, + {Name: "className", Type: "string"}, + }, + Message: "Undefined property '%s' for class '%s'", + }, + { + Name: "InvalidSuper", + Fields: []Field{ + {Name: "location", Type: "string"}, + }, + Message: "Can't use 'super' %s", + }, + { + Name: "InvalidThis", + Fields: []Field{ + {Name: "location", Type: "string"}, + }, + Message: "Can't use 'this' %s", + }, + { + Name: "UninitializedRead", + Fields: []Field{}, + Message: "Can't read local variable in its own initializer", + }, + { + Name: "InvalidInheritance", + Fields: []Field{ + {Name: "errorMsg", Type: "string"}, + }, + Message: "%s", + }, + { + Name: "InvalidReturn", + Fields: []Field{ + {Name: "location", Type: "string"}, + }, + Message: "Can't return from %s", + }, + { + Name: "VariableRedeclaration", + Fields: []Field{ + {Name: "identifier", Type: "string"}, + }, + Message: "Variable '%s' is already declared in this scope", + }, + { + Name: "UnsupportedBinaryOperation", + Fields: []Field{ + {Name: "operator", Type: "string"}, + {Name: "lhs", Type: "string"}, + {Name: "rhs", Type: "string"}, + }, + Message: "Operator '%s': incompatible types '%v' and '%v'", + }, + { + Name: "UnsupportedUnaryOperation", + Fields: []Field{ + {Name: "operator", Type: "string"}, + {Name: "rhs", Type: "string"}, + }, + Message: "Operator '%s': incompatible type '%v'", + }, + { + Name: "InvalidSetGet", + Fields: []Field{}, + Message: "Only class instances have properties", + }, + { + Name: "NotCallable", + Fields: []Field{}, + Message: "Only classes and functions are callable", + }, + { + Name: "BadArity", + Fields: []Field{ + {Name: "functionName", Type: "string"}, + {Name: "expected", Type: "int"}, + {Name: "got", Type: "int"}, + }, + Message: "Function '%s' expected %d arguments but got %d", + }, + }, +} + +const ( + nbArgsRequired = 2 + filePerm = 0644 +) + +func main() { + if len(os.Args) != nbArgsRequired { + fmt.Fprintf(os.Stderr, "Missing argument") + return + } + + var data Data + prefix := os.Args[1] + switch prefix { + case "lexer": + data = lexingErrors + case "runtime": + data = runtimeErrors + default: + fmt.Fprintf(os.Stderr, "Unknown argument %s", os.Args[1]) + return + } + + tmpl, err := template.ParseFiles("../../cmd/code-generator/errors_template.go.tmpl") + if err != nil { + panic(err) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + panic(err) + } + + filename := "generated-errors" + ".go" + err = os.WriteFile(filename, buf.Bytes(), filePerm) + if err != nil { + panic(err) + } +} diff --git a/go/cmd/glox/glox.go b/go/cmd/glox/glox.go index c5e4d68..e168a60 100644 --- a/go/cmd/glox/glox.go +++ b/go/cmd/glox/glox.go @@ -8,30 +8,33 @@ import ( "os" "github.com/fpotier/lox/go/pkg/lexer" + "github.com/fpotier/lox/go/pkg/loxerror" "github.com/fpotier/lox/go/pkg/parser" "github.com/fpotier/lox/go/pkg/runtime" "github.com/sean-/sysexits" ) type Lox struct { - hadError bool - lexer *lexer.Lexer - parser *parser.Parser - resolver *runtime.Resolver - interpreter *runtime.Interpreter - stdout io.Writer - stderr io.Writer + hadError bool + lexer *lexer.Lexer + parser *parser.Parser + resolver *runtime.Resolver + interpreter *runtime.Interpreter + stdout io.Writer + stderr io.Writer + errorFormatter loxerror.ErrorFormatter } func NewLox(fds ...io.Writer) *Lox { lox := Lox{ - hadError: false, - lexer: nil, - parser: nil, - resolver: nil, - interpreter: nil, - stdout: os.Stdout, - stderr: os.Stderr, + hadError: false, + lexer: nil, + parser: nil, + resolver: nil, + interpreter: nil, + stdout: os.Stdout, + stderr: os.Stderr, + errorFormatter: loxerror.NewJSONErrorFormatter(), } for i, fd := range fds { switch i { @@ -41,7 +44,7 @@ func NewLox(fds ...io.Writer) *Lox { lox.stderr = fd } } - lox.interpreter = runtime.NewInterpreter(lox.stdout, &lox) + lox.interpreter = runtime.NewInterpreter(lox.stdout, lox.errorFormatter) return &lox } @@ -78,33 +81,42 @@ func (l *Lox) RunFile(filepath string) int { func (l *Lox) run(sourceCode string) { // TODO: avoid to recreate all components each time - l.lexer = lexer.NewLexer(l, sourceCode) + l.lexer = lexer.NewLexer(l.errorFormatter, sourceCode) tokens := l.lexer.Tokens() + l.PrintAll() - l.parser = parser.NewParser(l, tokens) + l.parser = parser.NewParser(l.errorFormatter, tokens) statements := l.parser.Parse() - if l.hadError { + if l.errorFormatter.HasErrors() { + l.PrintAll() return } - l.resolver = runtime.NewResolver(l, l.interpreter) + // printer := ast.NewAstPrinter(os.Stdout, 2) + // printer.Dump(statements) + + l.resolver = runtime.NewResolver(l.errorFormatter, l.interpreter) l.resolver.ResolveProgram(statements) - if l.hadError { + if l.errorFormatter.HasErrors() { + l.PrintAll() return } l.interpreter.Eval(statements) + l.PrintAll() } -func (l *Lox) Error(line int, message string) { - fmt.Fprintf(l.stderr, "[line %d] Error: %s\n", line, message) - l.hadError = true +func (l *Lox) PrintAll() { + for l.errorFormatter.HasErrors() { + loxError, _ := l.errorFormatter.PopError() + fmt.Fprint(l.stderr, l.errorFormatter.Format(loxError)) + } } func main() { const maxArgs = 2 nbArgs := len(os.Args) - lox := NewLox(os.Stdout) + lox := NewLox(os.Stdout, os.Stderr) switch { case nbArgs > maxArgs: fmt.Println("Usage: glox [script]") diff --git a/go/cmd/glox/glox_test.go b/go/cmd/glox/glox_test.go index 1a8e066..c29d780 100644 --- a/go/cmd/glox/glox_test.go +++ b/go/cmd/glox/glox_test.go @@ -1,8 +1,7 @@ package main import ( - "fmt" - "io/fs" + "io" "os" "path/filepath" "regexp" @@ -12,55 +11,157 @@ import ( "github.com/pkg/diff" ) -const TestDirectory = "../../../test/" +const TestDirectory = "../../../test/official_tests" +const BenchmarkDirectory = "../../../benchmark/official_benchmarks" + +var testedDirectories = [...]string{ + ".", + "assignment", + "block", + "bool", + "call", + "class", + "closure", + "comments", + "constructor", + "field", + "for", + "function", + "if", + "inheritance", + // "limit", + "logical_operator", + "method", + "nil", + "number", + "operator", + "print", + "regression", + "return", + "string", + "super", + "this", + "variable", + "while", +} + +func loxFilesInDir(path string) ([]string, error) { + return filepath.Glob(filepath.Join(path, "/*.lox")) +} func TestRunFile(t *testing.T) { t.Parallel() + for _, dir := range testedDirectories { + absolutePath, err := filepath.Abs(TestDirectory + "/" + dir) + if err != nil { + t.Fatal(err.Error()) + } - err := filepath.WalkDir(TestDirectory, func(path string, _ fs.DirEntry, _ error) error { - pattern := regexp.MustCompile("^.*expect: (.*)$") - - t.Run(path, func(t *testing.T) { - if filepath.Ext(path) != ".lox" { - return - } + t.Run(dir, func(t *testing.T) { + t.Parallel() + runFilesInDir(t, absolutePath) + }) + } +} - if filepath.Base(filepath.Dir(path)) == "limit" { - t.Skip() - } +func BenchmarkRunFile(b *testing.B) { + runFilesInDirBench(b, BenchmarkDirectory) +} - builder := strings.Builder{} - lox := NewLox(&builder, &builder) - lox.RunFile(path) - programOutput := builder.String() +func runFilesInDir(t *testing.T, dirPath string) { + t.Helper() + loxFiles, err := loxFilesInDir(dirPath) + if err != nil || len(loxFiles) == 0 { + t.Logf("No .lox files in %s", dirPath) + t.Skip() + } - expectedBytes, err := os.ReadFile(path) - if err != nil { - t.Fatal("Failed to read ", path) + for _, file := range loxFiles { + t.Run(file, func(t *testing.T) { + diffReport := strings.Builder{} + err := checkRunOutput(file, &diffReport) + if diffReport.Len() > 0 { + t.Fatal(diffReport.String()) } - builder = strings.Builder{} - for _, line := range strings.Split(string(expectedBytes), "\n") { - match := pattern.FindStringSubmatch(line) - if len(match) > 1 { - builder.WriteString(match[1]) - builder.WriteByte('\n') - } + if err != nil { + t.Fatal(err) } - expectedOutput := builder.String() - - if expectedOutput != programOutput { - err := diff.Text(filepath.Base(path), path+".expected", programOutput, expectedOutput, os.Stdout) - if err != nil { - fmt.Println(err) - } - t.Fail() + }) + } +} + +func runFilesInDirBench(b *testing.B, dirPath string) { + b.Helper() + loxFiles, err := loxFilesInDir(dirPath) + if err != nil || len(loxFiles) == 0 { + b.Logf("No .lox files in %s", dirPath) + b.Skip() + } + + for _, file := range loxFiles { + b.Run(file, func(b *testing.B) { + for i := 0; i < b.N; i++ { + var ( + stdoutBuilder = strings.Builder{} + stderrBuilder = strings.Builder{} + ) + lox := NewLox(&stdoutBuilder, &stderrBuilder) + lox.RunFile(file) } }) + } +} - return nil - }) +func checkRunOutput(filename string, diffOutput io.Writer) error { + var ( + outputPattern = regexp.MustCompile("^.*expect: (.*)$") + errorPattern = regexp.MustCompile("^.*error: (.*)$") + stdoutBuilder = strings.Builder{} + stderrBuilder = strings.Builder{} + ) + lox := NewLox(&stdoutBuilder, &stderrBuilder) + lox.RunFile(filename) + programOutput := stdoutBuilder.String() + programErr := stderrBuilder.String() + + rawFileContent, err := os.ReadFile(filename) if err != nil { - t.Fail() + return err + } + // Required when files use CRLF or CR instead of LF (Go doesn't convert when reading) + fileContent := strings.ReplaceAll(string(rawFileContent), "\r", "") + expectedStdoutBuilder := strings.Builder{} + for _, line := range strings.Split(fileContent, "\n") { + match := outputPattern.FindStringSubmatch(line) + if len(match) > 1 { + expectedStdoutBuilder.WriteString(match[1]) + expectedStdoutBuilder.WriteByte('\n') + } } + expectedOutput := expectedStdoutBuilder.String() + if expectedOutput != programOutput { + err := diff.Text(filename, filename+".expected", programOutput, expectedOutput, diffOutput) + if err != nil { + return err + } + } + + expectedStderrBuilder := strings.Builder{} + for _, line := range strings.Split(fileContent, "\n") { + match := errorPattern.FindStringSubmatch(line) + if len(match) > 1 { + expectedStderrBuilder.WriteString(match[1]) + expectedStderrBuilder.WriteByte('\n') + } + } + expectedError := expectedStderrBuilder.String() + if expectedError != programErr { + err := diff.Text(filename, filename+".expected", programErr, expectedError, diffOutput) + if err != nil { + return err + } + } + + return nil } diff --git a/go/pkg/ast/loxvalue.go b/go/pkg/ast/loxvalue.go index 5ecea9a..c010cda 100644 --- a/go/pkg/ast/loxvalue.go +++ b/go/pkg/ast/loxvalue.go @@ -18,6 +18,17 @@ const ( Instance ) +var KindString = map[Kind]string{ + Boolean: "boolean", + String: "string", + Number: "number", + Nil: "nil", + NativeFunc: "native function", + Function: "function", + Class: "class", + Instance: "instance", +} + type LoxValue interface { Kind() Kind IsTruthy() bool diff --git a/go/pkg/ast/printer.go b/go/pkg/ast/printer.go new file mode 100644 index 0000000..b7a89ba --- /dev/null +++ b/go/pkg/ast/printer.go @@ -0,0 +1,375 @@ +package ast + +import ( + "fmt" + "io" + "strings" +) + +const DefaultTabSize = 2 + +type Printer struct { + output io.Writer + identationLevel uint + tabSize uint +} + +func NewAstPrinter(outputStream io.Writer, tabSize uint) Printer { + return Printer{ + output: outputStream, + identationLevel: 0, + tabSize: tabSize, + } +} + +func (astPrinter *Printer) Dump(statements []Statement) { + for _, statement := range statements { + statement.Accept(astPrinter) + } +} + +func (astPrinter *Printer) VisitAssignmentExpression(assignementExpression *AssignmentExpression) { + astPrinter.write("AssignmentExpression") + astPrinter.identationLevel++ + + astPrinter.write("identifier: " + assignementExpression.Name.Lexeme) + + if assignementExpression.Value != nil { + astPrinter.write("value: ") + astPrinter.identationLevel++ + assignementExpression.Value.Accept(astPrinter) + astPrinter.identationLevel-- + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitBinaryExpression(binaryExpression *BinaryExpression) { + astPrinter.write("BinaryExpression") + astPrinter.identationLevel++ + + astPrinter.write("operator: " + binaryExpression.Operator.Lexeme) + + astPrinter.write("left_operand: ") + astPrinter.identationLevel++ + binaryExpression.LHS.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("right_operand: ") + astPrinter.identationLevel++ + binaryExpression.RHS.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitCallExpression(callExpression *CallExpression) { + astPrinter.write("CallExpression") + astPrinter.identationLevel++ + + astPrinter.write("callee: ") + astPrinter.identationLevel++ + callExpression.Callee.Accept(astPrinter) + astPrinter.identationLevel-- + + if len(callExpression.Args) > 0 { + astPrinter.writeExpression("arguments", callExpression.Args) + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitGetExpression(getExpression *GetExpression) { + astPrinter.write("GetExpression") + astPrinter.identationLevel++ + + astPrinter.write("object:") + astPrinter.identationLevel++ + getExpression.Object.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("property: " + getExpression.Name.Lexeme) + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitGroupingExpression(groupingExpression *GroupingExpression) { + astPrinter.write("GroupingExpression") + astPrinter.identationLevel++ + + astPrinter.write("expression") + astPrinter.identationLevel++ + groupingExpression.Expr.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitLiteralExpression(literalExpression *LiteralExpression) { + astPrinter.write("LiteralExpression") + astPrinter.identationLevel++ + astPrinter.write("value: " + literalExpression.value.String()) + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitLogicalExpression(logicalExpression *LogicalExpression) { + astPrinter.write("LogicalExpression") + astPrinter.identationLevel++ + + astPrinter.write("operator: " + logicalExpression.Operator.Lexeme) + + astPrinter.write("left_operand: ") + astPrinter.identationLevel++ + logicalExpression.LHS.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("right_operand: ") + astPrinter.identationLevel++ + logicalExpression.RHS.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitSetExpression(setExpression *SetExpression) { + astPrinter.write("SetExpression") + astPrinter.identationLevel++ + + astPrinter.write("object:") + astPrinter.identationLevel++ + setExpression.Object.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("property: " + setExpression.Name.Lexeme) + + astPrinter.write("value:") + astPrinter.identationLevel++ + setExpression.Value.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitSuperExpression(superExpression *SuperExpression) { + astPrinter.write("SuperExpression") + astPrinter.identationLevel++ + astPrinter.write("method: " + superExpression.Method.Lexeme) + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitThisExpression(_ *ThisExpression) { + astPrinter.write("ThisExpression") +} + +func (astPrinter *Printer) VisitUnaryExpression(unaryExpression *UnaryExpression) { + astPrinter.write("UnaryExpression") + astPrinter.identationLevel++ + + astPrinter.write("operator: " + unaryExpression.Operator.Lexeme) + + astPrinter.write("operand: ") + astPrinter.identationLevel++ + unaryExpression.RHS.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitVariableExpression(variableExpression *VariableExpression) { + astPrinter.write("VariableExpression") + astPrinter.identationLevel++ + astPrinter.write("name: " + variableExpression.Name.Lexeme) + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitBlockStatement(blockStatement *BlockStatement) { + astPrinter.write("BlockStatement") + astPrinter.identationLevel++ + + astPrinter.writeStatements("statements", blockStatement.Statements) + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitClassStatement(classStatement *ClassStatement) { + astPrinter.write("ClassStatement") + astPrinter.identationLevel++ + + astPrinter.write("name: " + classStatement.Name.Lexeme) + if classStatement.Superclass != nil { + astPrinter.write("superclass:") + astPrinter.identationLevel++ + classStatement.Superclass.Accept(astPrinter) + astPrinter.identationLevel-- + } + + if len(classStatement.Methods) > 0 { + // FIXME: why are we using pointers? + // astPrinter.writeStatements("methods", classStatement.Methods) + astPrinter.write("methods:") + astPrinter.identationLevel++ + for i, method := range classStatement.Methods { + astPrinter.identationLevel++ + astPrinter.write(fmt.Sprintf("%d: ", i)) + + astPrinter.identationLevel++ + method.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- + } + astPrinter.identationLevel-- + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitExpressionStatement(expressionStatement *ExpressionStatement) { + astPrinter.write("ExpressionStatement") + astPrinter.identationLevel++ + + astPrinter.write("expression:") + astPrinter.identationLevel++ + expressionStatement.Expression.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitFunctionStatement(functionStatement *FunctionStatement) { + astPrinter.write("FunctionStatement") + astPrinter.identationLevel++ + + astPrinter.write("name: " + functionStatement.Name.Lexeme) + + if len(functionStatement.Parameters) > 0 { + for i, parameter := range functionStatement.Parameters { + astPrinter.identationLevel++ + astPrinter.write(fmt.Sprintf("%d: %s", i, parameter.Lexeme)) + astPrinter.identationLevel-- + } + } + + astPrinter.writeStatements("body", functionStatement.Body) + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitIfStatement(ifStatement *IfStatement) { + astPrinter.write("IfStatement") + astPrinter.identationLevel++ + + astPrinter.write("condition:") + astPrinter.identationLevel++ + ifStatement.Condition.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("then:") + astPrinter.identationLevel++ + ifStatement.ThenCode.Accept(astPrinter) + astPrinter.identationLevel-- + + if ifStatement.ElseCode != nil { + astPrinter.write("else:") + astPrinter.identationLevel++ + ifStatement.ElseCode.Accept(astPrinter) + astPrinter.identationLevel-- + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitPrintStatement(printStatement *PrintStatement) { + astPrinter.write("PrintStatement") + astPrinter.identationLevel++ + + astPrinter.write("value:") + astPrinter.identationLevel++ + printStatement.Expression.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitReturnStatement(returnStatement *ReturnStatement) { + astPrinter.write("ReturnStatement") + astPrinter.identationLevel++ + + if returnStatement.Value != nil { + astPrinter.write("value:") + astPrinter.identationLevel++ + returnStatement.Value.Accept(astPrinter) + astPrinter.identationLevel-- + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitVariableStatement(variableStatement *VariableStatement) { + astPrinter.write("VariableStatement") + astPrinter.identationLevel++ + + astPrinter.write("name: " + variableStatement.Name.Lexeme) + + if variableStatement.Initializer != nil { + astPrinter.write("initializer: ") + astPrinter.identationLevel++ + variableStatement.Initializer.Accept(astPrinter) + astPrinter.identationLevel-- + } + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) VisitWhileStatement(whileStatement *WhileStatement) { + astPrinter.write("WhileStatement") + astPrinter.identationLevel++ + + astPrinter.write("condition:") + astPrinter.identationLevel++ + whileStatement.Condition.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.write("body:") + astPrinter.identationLevel++ + whileStatement.Body.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- +} + +func (astPrinter *Printer) write(value string) { + fmt.Fprintf(astPrinter.output, + "%s%s\n", + strings.Repeat(" ", int(astPrinter.identationLevel*astPrinter.tabSize)), + value, + ) +} + +func (astPrinter *Printer) writeStatements(name string, statements []Statement) { + astPrinter.write(name + ": ") + for i, statement := range statements { + astPrinter.identationLevel++ + astPrinter.write(fmt.Sprintf("%d: ", i)) + + astPrinter.identationLevel++ + statement.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- + } +} + +func (astPrinter *Printer) writeExpression(name string, expressions []Expression) { + astPrinter.write(name + ": ") + for i, expression := range expressions { + astPrinter.identationLevel++ + astPrinter.write(fmt.Sprintf("%d: ", i)) + + astPrinter.identationLevel++ + expression.Accept(astPrinter) + astPrinter.identationLevel-- + + astPrinter.identationLevel-- + } +} diff --git a/go/pkg/lexer/lexer.go b/go/pkg/lexer/lexer.go index 450098d..e5fab95 100644 --- a/go/pkg/lexer/lexer.go +++ b/go/pkg/lexer/lexer.go @@ -1,29 +1,30 @@ +//go:generate go run ../../cmd/code-generator lexer + package lexer import ( - "fmt" "strconv" "github.com/fpotier/lox/go/pkg/loxerror" ) type Lexer struct { - errorReporter loxerror.ErrorReporter - sourceCode string - tokens []Token - start int - current int - line int + ErrorFormatter loxerror.ErrorFormatter + sourceCode string + tokens []Token + start int + current int + line int } -func NewLexer(errorReporter loxerror.ErrorReporter, sourceCode string) *Lexer { +func NewLexer(errorFormatter loxerror.ErrorFormatter, sourceCode string) *Lexer { return &Lexer{ - errorReporter: errorReporter, - sourceCode: sourceCode, - tokens: make([]Token, 0), - start: 0, - current: 0, - line: 1, + ErrorFormatter: errorFormatter, + sourceCode: sourceCode, + tokens: make([]Token, 0), + start: 0, + current: 0, + line: 1, } } @@ -115,7 +116,7 @@ func (l *Lexer) scanToken() { case isAlpha(c): l.identifier() default: - l.errorReporter.Error(l.line, fmt.Sprintf("Unexpected character '%c'", c)) + l.ErrorFormatter.PushError(NewUnexpectedCharacter(l.line, c)) } } } @@ -180,7 +181,7 @@ func (l *Lexer) string() { } if l.isAtEnd() { - l.errorReporter.Error(l.line, "Unterminated string") + l.ErrorFormatter.PushError(NewUnterminatedString(l.line)) return } @@ -206,8 +207,7 @@ func (l *Lexer) number() { floatValue, err := strconv.ParseFloat(l.sourceCode[l.start:l.current], 64) if err != nil { - l.errorReporter.Error(l.line, - fmt.Sprintf("Error converting %v to float: %v", l.sourceCode[l.start:l.current], err)) + l.ErrorFormatter.PushError(NewInvalidFloat(l.line, l.sourceCode[l.start:l.current])) return } l.addTokenWithLiteral(Number, &NumberLiteral{Value: floatValue}) diff --git a/go/pkg/loxerror/formatter.go b/go/pkg/loxerror/formatter.go new file mode 100644 index 0000000..dadc9c2 --- /dev/null +++ b/go/pkg/loxerror/formatter.go @@ -0,0 +1,9 @@ +package loxerror + +type ErrorFormatter interface { + PushError(loxError LoxError) + PopError() (LoxError, error) + Format(loxerror LoxError) string + HasErrors() bool + Reset() +} diff --git a/go/pkg/loxerror/jsonformatter.go b/go/pkg/loxerror/jsonformatter.go new file mode 100644 index 0000000..e88f8a5 --- /dev/null +++ b/go/pkg/loxerror/jsonformatter.go @@ -0,0 +1,71 @@ +package loxerror + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" +) + +var ErrEmptyErrorStack = errors.New("empty error stack") + +type JSONErrorFormatter struct { + errors []LoxError +} + +func NewJSONErrorFormatter() *JSONErrorFormatter { + return &JSONErrorFormatter{ + errors: make([]LoxError, 0), + } +} + +func (f *JSONErrorFormatter) PushError(e LoxError) { + f.errors = append(f.errors, e) +} + +func (f *JSONErrorFormatter) PopError() (LoxError, error) { + if !f.HasErrors() { + return nil, ErrEmptyErrorStack + } + + err := f.errors[0] + f.errors = f.errors[1:] + return err, nil +} + +func (f *JSONErrorFormatter) HasErrors() bool { + return len(f.errors) > 0 +} + +func (f *JSONErrorFormatter) Format(e LoxError) string { + rawString, err := MarshalJSON(e) + if err != nil { + panic(err) + } + + return string(rawString) +} + +func (f *JSONErrorFormatter) Errors() []LoxError { + return f.errors +} + +func (f *JSONErrorFormatter) Reset() { + f.errors = f.errors[:0] +} + +func MarshalJSON[T LoxError](e T) ([]byte, error) { + var buffer bytes.Buffer + encoder := json.NewEncoder(&buffer) + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(map[string]any{ + "line": e.Line(), + "type": e.Kind(), + "message": e.Message(), + }); err != nil { + return nil, fmt.Errorf("failed to encode the error message: %w", err) + } + + return buffer.Bytes(), nil +} diff --git a/go/pkg/loxerror/loxerror.go b/go/pkg/loxerror/loxerror.go index b006d00..d19248c 100644 --- a/go/pkg/loxerror/loxerror.go +++ b/go/pkg/loxerror/loxerror.go @@ -1,20 +1,7 @@ package loxerror -type ErrorReporter interface { - Error(line int, message string) -} -type ParserError struct { - Message string -} - -func (e ParserError) Error() string { - return e.Message -} - -type RuntimeError struct { - Message string -} - -func (e RuntimeError) Error() string { - return e.Message +type LoxError interface { + Line() int + Kind() string + Message() string } diff --git a/go/pkg/parser/parseerror.go b/go/pkg/parser/parseerror.go new file mode 100644 index 0000000..82b04d4 --- /dev/null +++ b/go/pkg/parser/parseerror.go @@ -0,0 +1,25 @@ +package parser + +type ParseError struct { + line int + message string +} + +func NewParseError(line int, message string) *ParseError { + return &ParseError{ + line: line, + message: message, + } +} + +func (e *ParseError) Line() int { + return e.line +} + +func (e *ParseError) Kind() string { + return "ParseError" +} + +func (e *ParseError) Message() string { + return e.message +} diff --git a/go/pkg/parser/parser.go b/go/pkg/parser/parser.go index 24d93e4..4bae9fa 100644 --- a/go/pkg/parser/parser.go +++ b/go/pkg/parser/parser.go @@ -83,11 +83,11 @@ import ( // arguments -> expression ( "," expression )* // -func NewParser(errorReporter loxerror.ErrorReporter, tokens []lexer.Token) *Parser { +func NewParser(errorFormatter loxerror.ErrorFormatter, tokens []lexer.Token) *Parser { return &Parser{ - errorReporter: errorReporter, - tokens: tokens, - current: 0, + errorFormatter: errorFormatter, + tokens: tokens, + current: 0, } } @@ -104,7 +104,7 @@ func (p *Parser) Parse() []ast.Statement { func (p *Parser) declaration() ast.Statement { defer func() { if r := recover(); r != nil { - if _, ok := r.(loxerror.ParserError); ok { + if _, ok := r.(*ParseError); ok { p.synchronize() return } @@ -149,7 +149,7 @@ func (p *Parser) function(kind string) ast.Statement { if !p.check(lexer.RightParenthesis) { for next := true; next; next = p.match(lexer.Comma) { if len(parameters) >= ast.Limits.MaxArgs { - p.errorReporter.Error(p.peek().Line, "Can't have more than 255 parameters") + p.errorFormatter.PushError(NewParseError(p.peek().Line, "Can't have more than 255 parameters")) } parameters = append(parameters, p.consume(lexer.Identifier, "Expect parameter name")) @@ -157,7 +157,7 @@ func (p *Parser) function(kind string) ast.Statement { } p.consume(lexer.RightParenthesis, "Expect ')' after parameters") - p.consume(lexer.LeftBrace, "Expect '{' after parameters") + p.consume(lexer.LeftBrace, "Function body must start with '{'") body := p.block() return ast.NewFunctionStatement(name, parameters, body) @@ -239,7 +239,6 @@ func (p *Parser) forStatement() ast.Statement { body := p.statement() if increment != nil { - // Note: I think this creates a useless block body = ast.NewBlockStatement([]ast.Statement{body, ast.NewExpressionStatement(increment)}) } @@ -328,7 +327,7 @@ func (p *Parser) assignment() ast.Expression { // No need to go to put the parser in recovery mode // TODO: better error message - p.errorReporter.Error(equalsToken.Line, "Invalid assignment target") + p.errorFormatter.PushError(NewParseError(equalsToken.Line, "Invalid assignment target")) return nil } @@ -444,13 +443,13 @@ func (p *Parser) finishCall(callee ast.Expression) ast.Expression { if !p.check(lexer.RightParenthesis) { for next := true; next; next = p.match(lexer.Comma) { if len(args) >= ast.Limits.MaxArgs { - p.errorReporter.Error(p.peek().Line, "Can't have more than 255 arguments") + p.errorFormatter.PushError(NewParseError(p.peek().Line, "Can't have more than 255 arguments")) } args = append(args, p.expression()) } } - position := p.consume(lexer.RightParenthesis, "Execpect ')' after function arguments") + position := p.consume(lexer.RightParenthesis, "Expect ')' after function arguments") return ast.NewCallExpression(callee, position, args) } @@ -481,8 +480,9 @@ func (p *Parser) primary() ast.Expression { p.consume(lexer.RightParenthesis, "Expect ')' after expression") return ast.NewGroupingExpression(expr) default: - p.errorReporter.Error(p.peek().Line, "expect expression") - panic(loxerror.ParserError{Message: "expect expression"}) + err := NewParseError(p.peek().Line, "expect expression") + p.errorFormatter.PushError(err) + panic(err) } } @@ -530,17 +530,20 @@ func (p *Parser) consume(tokenType lexer.TokenType, message string) lexer.Token return p.advance() } - p.errorReporter.Error(p.peek().Line, message) - panic(loxerror.ParserError{Message: message}) + err := NewParseError(p.peek().Line, message) + p.errorFormatter.PushError(err) + panic(err) } func (p *Parser) synchronize() { p.advance() for !p.isAtEnd() { - switch p.previous().Type { - case lexer.Semicolon: - fallthrough + if p.previous().Type == lexer.Semicolon { + return + } + + switch p.peek().Type { case lexer.Class: fallthrough case lexer.Var: @@ -562,7 +565,7 @@ func (p *Parser) synchronize() { } type Parser struct { - errorReporter loxerror.ErrorReporter - tokens []lexer.Token - current int + errorFormatter loxerror.ErrorFormatter + tokens []lexer.Token + current int } diff --git a/go/pkg/runtime/environment.go b/go/pkg/runtime/environment.go index 00eba83..43fac4c 100644 --- a/go/pkg/runtime/environment.go +++ b/go/pkg/runtime/environment.go @@ -1,11 +1,8 @@ package runtime import ( - "fmt" - "github.com/fpotier/lox/go/pkg/ast" "github.com/fpotier/lox/go/pkg/lexer" - "github.com/fpotier/lox/go/pkg/loxerror" ) type Environment struct { @@ -41,7 +38,7 @@ func (e *Environment) Get(name lexer.Token) ast.LoxValue { return e.enclosing.Get(name) } - panic(loxerror.RuntimeError{Message: fmt.Sprintf("Undefined variable '%v'", name.Lexeme)}) + panic(NewUndefinedVariable(name.Line, name.Lexeme)) } func (e *Environment) GetAt(distance int, name string) ast.LoxValue { @@ -59,7 +56,7 @@ func (e *Environment) Assign(name lexer.Token, value ast.LoxValue) { return } - panic(loxerror.RuntimeError{Message: fmt.Sprintf("Undefined variable '%v'", name.Lexeme)}) + panic(NewUndefinedVariable(name.Line, name.Lexeme)) } func (e *Environment) AssignAt(distance int, name lexer.Token, value ast.LoxValue) { diff --git a/go/pkg/runtime/interpreter.go b/go/pkg/runtime/interpreter.go index b44ddcb..7daca21 100644 --- a/go/pkg/runtime/interpreter.go +++ b/go/pkg/runtime/interpreter.go @@ -1,9 +1,10 @@ +//go:generate go run ../../cmd/code-generator runtime + package runtime import ( "fmt" "io" - "reflect" "github.com/fpotier/lox/go/pkg/ast" "github.com/fpotier/lox/go/pkg/lexer" @@ -14,18 +15,18 @@ type Interpreter struct { // Value can be virtually anything (string, number, boolean, object, nil, etc.) Value ast.LoxValue HadRuntimeError bool - ErrorReporter loxerror.ErrorReporter + ErrorFormatter loxerror.ErrorFormatter OutputStream io.Writer globals *Environment environment *Environment locals map[ast.Expression]int } -func NewInterpreter(outputStream io.Writer, errorReporter loxerror.ErrorReporter) *Interpreter { +func NewInterpreter(outputStream io.Writer, errorFormatter loxerror.ErrorFormatter) *Interpreter { i := Interpreter{ Value: ast.NewNilValue(), HadRuntimeError: false, - ErrorReporter: errorReporter, + ErrorFormatter: errorFormatter, OutputStream: outputStream, globals: NewEnvironment(), environment: nil, @@ -45,10 +46,10 @@ func (i *Interpreter) Eval(statements []ast.Statement) { // without changing the Visit...() methods to return an error and propagate manually these errors defer func() { if r := recover(); r != nil { - if err, ok := r.(loxerror.RuntimeError); ok { + if err, ok := r.(loxerror.LoxError); ok { i.HadRuntimeError = true // TODO: better runtime error messages - i.ErrorReporter.Error(0, err.Message) + i.ErrorFormatter.PushError(err) return } panic(r) @@ -82,13 +83,10 @@ func (i *Interpreter) VisitBinaryExpression(binaryExpression *ast.BinaryExpressi case lhs.Kind() == ast.String && rhs.Kind() == ast.String: i.Value = ast.NewStringValue(lhs.(*ast.StringValue).Value + rhs.(*ast.StringValue).Value) default: - // TODO: print lox types instead of go types - panic(loxerror.RuntimeError{ - Message: fmt.Sprintf("Operator '%v': incompatible types %v and %v", - binaryExpression.Operator.Lexeme, - reflect.TypeOf(lhs), - reflect.TypeOf(rhs)), - }) + panic(NewUnsupportedBinaryOperation(binaryExpression.Operator.Line, + binaryExpression.Operator.Lexeme, + ast.KindString[lhs.Kind()], + ast.KindString[rhs.Kind()])) } case lexer.Dash: assertNumberOperands(binaryExpression.Operator, lhs, rhs) @@ -128,13 +126,11 @@ func (i *Interpreter) VisitCallExpression(callExpression *ast.CallExpression) { if function, ok := callee.(LoxCallable); ok { if function.Arity() != len(arguments) { - panic(loxerror.RuntimeError{ - Message: fmt.Sprintf("Expected %d arguments but got %d", function.Arity(), len(arguments)), - }) + panic(NewBadArity(callExpression.Position.Line, function.Name(), function.Arity(), len(arguments))) } i.Value = function.Call(i, arguments) } else { - panic(loxerror.RuntimeError{Message: "Can only call functions and classes"}) + panic(NewNotCallable(callExpression.Position.Line)) } } @@ -145,7 +141,7 @@ func (i *Interpreter) VisitGetExpression(getExpression *ast.GetExpression) { return } - panic(loxerror.RuntimeError{Message: "Only instances have properties"}) + panic(NewInvalidSetGet(getExpression.Name.Line)) } func (i *Interpreter) VisitGroupingExpression(groupingExpression *ast.GroupingExpression) { @@ -178,7 +174,8 @@ func (i *Interpreter) VisitSetExpression(setExpression *ast.SetExpression) { return } - panic(loxerror.RuntimeError{Message: "Only instances have fields"}) + + panic(NewInvalidSetGet(setExpression.Name.Line)) } func (i *Interpreter) VisitSuperExpression(superExpression *ast.SuperExpression) { @@ -187,7 +184,7 @@ func (i *Interpreter) VisitSuperExpression(superExpression *ast.SuperExpression) this := i.environment.GetAt(distance-1, "this").(*LoxInstance) method, ok := superclass.findMethod(superExpression.Method.Lexeme) if !ok { - panic(loxerror.RuntimeError{Message: "Undefined property" + superExpression.Method.Lexeme}) + panic(NewUndefinedProperty(superExpression.Method.Line, superExpression.Keyword.Lexeme, superclass.String())) } i.Value = method.Bind(this) @@ -224,7 +221,7 @@ func (i *Interpreter) VisitClassStatement(classStatement *ast.ClassStatement) { var ok bool superclass, ok = result.(*LoxClass) if !ok { - panic(loxerror.RuntimeError{Message: "Superclass must be a class"}) + panic(NewInvalidInheritance(classStatement.Superclass.Name.Line, "Superclass must be a class")) } } @@ -237,7 +234,9 @@ func (i *Interpreter) VisitClassStatement(classStatement *ast.ClassStatement) { methods := make(map[string]*LoxFunction) for _, method := range classStatement.Methods { - methods[method.Name.Lexeme] = NewLoxFunction(method, i.environment, method.Name.Lexeme == "init") + function := NewLoxFunction(method, i.environment, method.Name.Lexeme == "init") + function.setClassName(classStatement.Name.Lexeme) + methods[method.Name.Lexeme] = function } class := NewLoxClass(classStatement.Name.Lexeme, superclass, methods) @@ -272,7 +271,7 @@ func (i *Interpreter) VisitPrintStatement(printStatement *ast.PrintStatement) { } func (i *Interpreter) VisitReturnStatement(returnStatement *ast.ReturnStatement) { - var value ast.LoxValue + var value ast.LoxValue = ast.NewNilValue() if returnStatement.Value != nil { value = i.evaluate(returnStatement.Value) } @@ -334,21 +333,19 @@ func (i *Interpreter) lookupVariable(name lexer.Token, e ast.Expression) ast.Lox func assertNumberOperands(operator lexer.Token, lhs ast.LoxValue, rhs ast.LoxValue) { if !(lhs.Kind() == ast.Number) || !(rhs.Kind() == ast.Number) { - panic(loxerror.RuntimeError{ - // TODO: print lox types instead of go types - Message: fmt.Sprintf("Operator '%v': incompatible types %v and %v", + panic( + NewUnsupportedBinaryOperation( + operator.Line, operator.Lexeme, - reflect.TypeOf(lhs), - reflect.TypeOf(rhs)), - }) + ast.KindString[lhs.Kind()], + ast.KindString[rhs.Kind()], + ), + ) } } func assertNumberOperand(operator lexer.Token, rhs ast.LoxValue) { if !(rhs.Kind() == ast.Number) { - panic(loxerror.RuntimeError{ - // TODO: print lox types instead of go types - Message: fmt.Sprintf("Operator '%v': incompatible type %v", operator.Lexeme, reflect.TypeOf(rhs)), - }) + panic(NewUnsupportedUnaryOperation(operator.Line, operator.Lexeme, ast.KindString[rhs.Kind()])) } } diff --git a/go/pkg/runtime/loxcallable.go b/go/pkg/runtime/loxcallable.go index 78df481..0ee668e 100644 --- a/go/pkg/runtime/loxcallable.go +++ b/go/pkg/runtime/loxcallable.go @@ -5,6 +5,7 @@ import "github.com/fpotier/lox/go/pkg/ast" type LoxCallable interface { Call(interpreter *Interpreter, arguments []ast.LoxValue) ast.LoxValue Arity() int + Name() string } type CallableCode func(*Interpreter, []ast.LoxValue) ast.LoxValue diff --git a/go/pkg/runtime/loxclass.go b/go/pkg/runtime/loxclass.go index 3a09079..0939c62 100644 --- a/go/pkg/runtime/loxclass.go +++ b/go/pkg/runtime/loxclass.go @@ -1,6 +1,10 @@ package runtime -import "github.com/fpotier/lox/go/pkg/ast" +import ( + "fmt" + + "github.com/fpotier/lox/go/pkg/ast" +) type LoxClass struct { name string @@ -19,6 +23,7 @@ func NewLoxClass(name string, superclass *LoxClass, methods map[string]*LoxFunct func (c *LoxClass) Kind() ast.Kind { return ast.Class } func (c *LoxClass) IsTruthy() bool { return true } func (c *LoxClass) String() string { return c.name } +func (c *LoxClass) Name() string { return fmt.Sprintf("%s::%s", c.name, c.name) } func (c *LoxClass) Equals(v ast.LoxValue) bool { return c == v } func (c *LoxClass) Call(i *Interpreter, arguments []ast.LoxValue) ast.LoxValue { diff --git a/go/pkg/runtime/loxfunction.go b/go/pkg/runtime/loxfunction.go index a790610..48c29a2 100644 --- a/go/pkg/runtime/loxfunction.go +++ b/go/pkg/runtime/loxfunction.go @@ -10,15 +10,29 @@ type LoxFunction struct { Declaration *ast.FunctionStatement Closure *Environment isConstructor bool + className string } func NewLoxFunction(declaration *ast.FunctionStatement, closure *Environment, isConstructor bool) *LoxFunction { - return &LoxFunction{Declaration: declaration, Closure: closure, isConstructor: isConstructor} + return &LoxFunction{Declaration: declaration, Closure: closure, isConstructor: isConstructor, className: ""} +} +func (f *LoxFunction) setClassName(className string) { f.className = className } +func (f LoxFunction) Kind() ast.Kind { return ast.Function } +func (f LoxFunction) IsTruthy() bool { return true } +func (f LoxFunction) String() string { return fmt.Sprintf("", f.Declaration.Name.Lexeme) } +func (f LoxFunction) Name() string { + if len(f.className) == 0 { + return f.Declaration.Name.Lexeme + } + + return f.className + "::" + f.Declaration.Name.Lexeme +} +func (f LoxFunction) Equals(v ast.LoxValue) bool { + if v, ok := v.(*LoxFunction); ok { + return f.Closure == v.Closure + } + return false } -func (f LoxFunction) Kind() ast.Kind { return ast.Function } -func (f LoxFunction) IsTruthy() bool { return true } -func (f LoxFunction) String() string { return fmt.Sprintf("", f.Declaration.Name.Lexeme) } -func (f LoxFunction) Equals(_ ast.LoxValue) bool { return false } func (f LoxFunction) Call(i *Interpreter, arguments []ast.LoxValue) (returnValue ast.LoxValue) { environment := NewSubEnvironment(f.Closure) for i := range f.Declaration.Parameters { @@ -58,9 +72,5 @@ func (f *LoxFunction) Bind(this *LoxInstance) *LoxFunction { boundFunction := NewLoxFunction(f.Declaration, environment, f.isConstructor) - if f.isConstructor { - fmt.Println(f.Arity() == boundFunction.Arity()) - } - return boundFunction } diff --git a/go/pkg/runtime/loxinstance.go b/go/pkg/runtime/loxinstance.go index acf45f5..440c874 100644 --- a/go/pkg/runtime/loxinstance.go +++ b/go/pkg/runtime/loxinstance.go @@ -3,7 +3,6 @@ package runtime import ( "github.com/fpotier/lox/go/pkg/ast" "github.com/fpotier/lox/go/pkg/lexer" - "github.com/fpotier/lox/go/pkg/loxerror" ) type LoxInstance struct { @@ -27,7 +26,7 @@ func (i *LoxInstance) Get(name lexer.Token) ast.LoxValue { return method.Bind(i) } - panic(loxerror.RuntimeError{Message: "Undefined property " + name.Lexeme}) + panic(NewUndefinedProperty(name.Line, name.Lexeme, i.class.name)) } func (i *LoxInstance) Set(name lexer.Token, value ast.LoxValue) { diff --git a/go/pkg/runtime/nativefunction.go b/go/pkg/runtime/nativefunction.go index ddee816..6b75ecf 100644 --- a/go/pkg/runtime/nativefunction.go +++ b/go/pkg/runtime/nativefunction.go @@ -16,6 +16,7 @@ type NativeFunction struct { func (f NativeFunction) Kind() ast.Kind { return ast.NativeFunc } func (f NativeFunction) IsTruthy() bool { return true } func (f NativeFunction) String() string { return "" } +func (f NativeFunction) Name() string { return f.name } func (f NativeFunction) Equals(_ ast.LoxValue) bool { return false } func (f NativeFunction) Call(i *Interpreter, arguments []ast.LoxValue) ast.LoxValue { return f.code(i, arguments) diff --git a/go/pkg/runtime/resolver.go b/go/pkg/runtime/resolver.go index 9b896c0..48dedd5 100644 --- a/go/pkg/runtime/resolver.go +++ b/go/pkg/runtime/resolver.go @@ -1,8 +1,6 @@ package runtime import ( - "fmt" - "github.com/fpotier/lox/go/pkg/ast" "github.com/fpotier/lox/go/pkg/lexer" "github.com/fpotier/lox/go/pkg/loxerror" @@ -26,16 +24,16 @@ const ( ) type Resolver struct { - errorReporter loxerror.ErrorReporter + errorFormatter loxerror.ErrorFormatter interpreter *Interpreter scopes []map[string]bool currentFnType FunctionType currentClassType ClassType } -func NewResolver(errorReporter loxerror.ErrorReporter, i *Interpreter) *Resolver { +func NewResolver(errorFormatter loxerror.ErrorFormatter, i *Interpreter) *Resolver { r := Resolver{ - errorReporter: errorReporter, + errorFormatter: errorFormatter, interpreter: i, scopes: make([]map[string]bool, 0), currentFnType: NoFunc, @@ -91,9 +89,9 @@ func (r *Resolver) VisitSetExpression(e *ast.SetExpression) { func (r *Resolver) VisitSuperExpression(e *ast.SuperExpression) { if r.currentClassType == NoClass { - r.errorReporter.Error(e.Keyword.Line, "Can't use 'super' outside of a class") + r.errorFormatter.PushError(NewInvalidSuper(e.Keyword.Line, "outside of a class")) } else if r.currentClassType != InSubClass { - r.errorReporter.Error(e.Keyword.Line, "Can't use 'super' in a class with no superclass") + r.errorFormatter.PushError(NewInvalidSuper(e.Keyword.Line, "in a class with no superclass")) } r.resolveLocal(e, e.Keyword) @@ -101,7 +99,7 @@ func (r *Resolver) VisitSuperExpression(e *ast.SuperExpression) { func (r *Resolver) VisitThisExpression(e *ast.ThisExpression) { if r.currentClassType == NoClass { - r.errorReporter.Error(e.Keyword.Line, "Can't use 'this' outside of a class") + r.errorFormatter.PushError(NewInvalidThis(e.Keyword.Line, "outside of a class")) } r.resolveLocal(e, e.Keyword) @@ -114,7 +112,7 @@ func (r *Resolver) VisitUnaryExpression(e *ast.UnaryExpression) { func (r *Resolver) VisitVariableExpression(e *ast.VariableExpression) { if len(r.scopes) > 0 { if declared, ok := r.scopes[len(r.scopes)-1][e.Name.Lexeme]; ok && !declared { - r.errorReporter.Error(e.Name.Line, "Can't read local variable in its own initializer") + r.errorFormatter.PushError(NewUninitializedRead(e.Name.Line)) } } @@ -139,7 +137,7 @@ func (r *Resolver) VisitClassStatement(s *ast.ClassStatement) { if s.Superclass != nil { r.currentClassType = InSubClass if s.Name.Lexeme == s.Superclass.Name.Lexeme { - r.errorReporter.Error(s.Superclass.Name.Line, "A class can't inherit from itself") + r.errorFormatter.PushError(NewInvalidInheritance(s.Superclass.Name.Line, "A class can't inherit from itself")) } r.resolveExpression(s.Superclass) @@ -190,12 +188,12 @@ func (r *Resolver) VisitPrintStatement(s *ast.PrintStatement) { func (r *Resolver) VisitReturnStatement(s *ast.ReturnStatement) { if r.currentFnType == NoFunc { - r.errorReporter.Error(s.Keyword.Line, "Can't return from top-level code") + r.errorFormatter.PushError(NewInvalidReturn(s.Keyword.Line, "top-level code")) } if s.Value != nil { if r.currentFnType == Constructor { - r.errorReporter.Error(s.Keyword.Line, "Can't return from constructor") + r.errorFormatter.PushError(NewInvalidReturn(s.Keyword.Line, "constructor")) } r.resolveExpression(s.Value) @@ -250,7 +248,7 @@ func (r *Resolver) endScope() { r.scopes = r.scopes[:len(r.scopes)-1] } func (r *Resolver) declare(name lexer.Token) { if len(r.scopes) > 0 { if _, ok := r.scopes[len(r.scopes)-1][name.Lexeme]; ok { - r.errorReporter.Error(name.Line, fmt.Sprintf("Variable '%s' already declared in this scope", name.Lexeme)) + r.errorFormatter.PushError(NewVariableRedeclaration(name.Line, name.Lexeme)) } r.scopes[len(r.scopes)-1][name.Lexeme] = false } diff --git a/test/official_tests/assignment/grouping.lox b/test/official_tests/assignment/grouping.lox index dc6e79f..12f7039 100644 --- a/test/official_tests/assignment/grouping.lox +++ b/test/official_tests/assignment/grouping.lox @@ -1,2 +1,2 @@ var a = "a"; -(a) = "value"; // Error at '=': Invalid assignment target. +(a) = "value"; // error: {"line":2,"message":"Invalid assignment target","type":"ParseError"} diff --git a/test/official_tests/assignment/infix_operator.lox b/test/official_tests/assignment/infix_operator.lox index fa55c47..fb39c2a 100644 --- a/test/official_tests/assignment/infix_operator.lox +++ b/test/official_tests/assignment/infix_operator.lox @@ -1,3 +1,3 @@ var a = "a"; var b = "b"; -a + b = "value"; // Error at '=': Invalid assignment target. +a + b = "value"; // error: {"line":3,"message":"Invalid assignment target","type":"ParseError"} diff --git a/test/official_tests/assignment/prefix_operator.lox b/test/official_tests/assignment/prefix_operator.lox index fdd143c..030ae6d 100644 --- a/test/official_tests/assignment/prefix_operator.lox +++ b/test/official_tests/assignment/prefix_operator.lox @@ -1,2 +1,2 @@ var a = "a"; -!a = "value"; // Error at '=': Invalid assignment target. +!a = "value"; // error: {"line":2,"message":"Invalid assignment target","type":"ParseError"} diff --git a/test/official_tests/assignment/to_this.lox b/test/official_tests/assignment/to_this.lox index c1e4a6b..321d930 100644 --- a/test/official_tests/assignment/to_this.lox +++ b/test/official_tests/assignment/to_this.lox @@ -1,6 +1,6 @@ class Foo { Foo() { - this = "value"; // Error at '=': Invalid assignment target. + this = "value"; // error: {"line":3,"message":"Invalid assignment target","type":"ParseError"} } } diff --git a/test/official_tests/assignment/undefined.lox b/test/official_tests/assignment/undefined.lox index 4991be3..c0e4c09 100644 --- a/test/official_tests/assignment/undefined.lox +++ b/test/official_tests/assignment/undefined.lox @@ -1 +1,2 @@ -unknown = "what"; // expect runtime error: Undefined variable 'unknown'. +unknown = "what"; +// error: {"line":1,"message":"Undefined variable 'unknown'","type":"RuntimeError"} diff --git a/test/official_tests/call/bool.lox b/test/official_tests/call/bool.lox index 859cd86..474b0e7 100644 --- a/test/official_tests/call/bool.lox +++ b/test/official_tests/call/bool.lox @@ -1 +1 @@ -true(); // expect runtime error: Can only call functions and classes. +true(); // error: {"line":1,"message":"Only classes and functions are callable","type":"RuntimeError"} diff --git a/test/official_tests/call/nil.lox b/test/official_tests/call/nil.lox index 7fdf584..7e82cb6 100644 --- a/test/official_tests/call/nil.lox +++ b/test/official_tests/call/nil.lox @@ -1 +1 @@ -nil(); // expect runtime error: Can only call functions and classes. +nil(); // error: {"line":1,"message":"Only classes and functions are callable","type":"RuntimeError"} diff --git a/test/official_tests/call/num.lox b/test/official_tests/call/num.lox index 1ed1b86..cec839e 100644 --- a/test/official_tests/call/num.lox +++ b/test/official_tests/call/num.lox @@ -1 +1 @@ -123(); // expect runtime error: Can only call functions and classes. +123(); // error: {"line":1,"message":"Only classes and functions are callable","type":"RuntimeError"} diff --git a/test/official_tests/call/object.lox b/test/official_tests/call/object.lox index dce58d9..ab9fc8c 100644 --- a/test/official_tests/call/object.lox +++ b/test/official_tests/call/object.lox @@ -1,4 +1,4 @@ class Foo {} var foo = Foo(); -foo(); // expect runtime error: Can only call functions and classes. +foo(); // error: {"line":4,"message":"Only classes and functions are callable","type":"RuntimeError"} \ No newline at end of file diff --git a/test/official_tests/call/string.lox b/test/official_tests/call/string.lox index 91f62a0..0e6864e 100644 --- a/test/official_tests/call/string.lox +++ b/test/official_tests/call/string.lox @@ -1 +1 @@ -"str"(); // expect runtime error: Can only call functions and classes. +"str"(); // error: {"line":1,"message":"Only classes and functions are callable","type":"RuntimeError"} diff --git a/test/official_tests/class/inherit_self.lox b/test/official_tests/class/inherit_self.lox index 8bb1dac..3599303 100644 --- a/test/official_tests/class/inherit_self.lox +++ b/test/official_tests/class/inherit_self.lox @@ -1 +1 @@ -class Foo < Foo {} // Error at 'Foo': A class can't inherit from itself. +class Foo < Foo {} // error: {"line":1,"message":"A class can't inherit from itself","type":"RuntimeError"} diff --git a/test/official_tests/class/local_inherit_self.lox b/test/official_tests/class/local_inherit_self.lox index ffd0d4d..2a0e8f5 100644 --- a/test/official_tests/class/local_inherit_self.lox +++ b/test/official_tests/class/local_inherit_self.lox @@ -1,4 +1,4 @@ { - class Foo < Foo {} // Error at 'Foo': A class can't inherit from itself. + class Foo < Foo {} // error: {"line":2,"message":"A class can't inherit from itself","type":"RuntimeError"} } // [c line 5] Error at end: Expect '}' after block. diff --git a/test/official_tests/constructor/default_arguments.lox b/test/official_tests/constructor/default_arguments.lox index 21d073a..1f64837 100644 --- a/test/official_tests/constructor/default_arguments.lox +++ b/test/official_tests/constructor/default_arguments.lox @@ -1,3 +1,3 @@ class Foo {} -var foo = Foo(1, 2, 3); // expect runtime error: Expected 0 arguments but got 3. +var foo = Foo(1, 2, 3); // error: {"line":3,"message":"Function 'Foo::Foo' expected 0 arguments but got 3","type":"RuntimeError"} diff --git a/test/official_tests/constructor/extra_arguments.lox b/test/official_tests/constructor/extra_arguments.lox index bee6ebc..c89ec28 100644 --- a/test/official_tests/constructor/extra_arguments.lox +++ b/test/official_tests/constructor/extra_arguments.lox @@ -5,4 +5,4 @@ class Foo { } } -var foo = Foo(1, 2, 3, 4); // expect runtime error: Expected 2 arguments but got 4. \ No newline at end of file +var foo = Foo(1, 2, 3, 4); // error: {"line":8,"message":"Function 'Foo::Foo' expected 2 arguments but got 4","type":"RuntimeError"} \ No newline at end of file diff --git a/test/official_tests/constructor/missing_arguments.lox b/test/official_tests/constructor/missing_arguments.lox index cf26928..524be3e 100644 --- a/test/official_tests/constructor/missing_arguments.lox +++ b/test/official_tests/constructor/missing_arguments.lox @@ -2,4 +2,4 @@ class Foo { init(a, b) {} } -var foo = Foo(1); // expect runtime error: Expected 2 arguments but got 1. +var foo = Foo(1); // error: {"line":5,"message":"Function 'Foo::Foo' expected 2 arguments but got 1","type":"RuntimeError"} diff --git a/test/official_tests/constructor/return_value.lox b/test/official_tests/constructor/return_value.lox index 115b450..758ed2e 100644 --- a/test/official_tests/constructor/return_value.lox +++ b/test/official_tests/constructor/return_value.lox @@ -1,5 +1,5 @@ class Foo { init() { - return "result"; // Error at 'return': Can't return a value from an initializer. + return "result"; // error: {"line":3,"message":"Can't return from constructor","type":"RuntimeError"} } } diff --git a/test/official_tests/field/call_nonfunction_field.lox b/test/official_tests/field/call_nonfunction_field.lox index cf43899..4fb4661 100644 --- a/test/official_tests/field/call_nonfunction_field.lox +++ b/test/official_tests/field/call_nonfunction_field.lox @@ -3,4 +3,4 @@ class Foo {} var foo = Foo(); foo.bar = "not fn"; -foo.bar(); // expect runtime error: Can only call functions and classes. +foo.bar(); // error: {"line":6,"message":"Only classes and functions are callable","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_bool.lox b/test/official_tests/field/get_on_bool.lox index d8e1793..b012835 100644 --- a/test/official_tests/field/get_on_bool.lox +++ b/test/official_tests/field/get_on_bool.lox @@ -1 +1 @@ -true.foo; // expect runtime error: Only instances have properties. +true.foo; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_class.lox b/test/official_tests/field/get_on_class.lox index 068b6f9..4ae6c89 100644 --- a/test/official_tests/field/get_on_class.lox +++ b/test/official_tests/field/get_on_class.lox @@ -1,2 +1,2 @@ class Foo {} -Foo.bar; // expect runtime error: Only instances have properties. +Foo.bar; // error: {"line":2,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_function.lox b/test/official_tests/field/get_on_function.lox index 87c3845..b8573c2 100644 --- a/test/official_tests/field/get_on_function.lox +++ b/test/official_tests/field/get_on_function.lox @@ -1,3 +1,3 @@ fun foo() {} -foo.bar; // expect runtime error: Only instances have properties. +foo.bar; // error: {"line":3,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_nil.lox b/test/official_tests/field/get_on_nil.lox index 829bb1a..1655408 100644 --- a/test/official_tests/field/get_on_nil.lox +++ b/test/official_tests/field/get_on_nil.lox @@ -1 +1 @@ -nil.foo; // expect runtime error: Only instances have properties. +nil.foo; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_num.lox b/test/official_tests/field/get_on_num.lox index 287f422..690677a 100644 --- a/test/official_tests/field/get_on_num.lox +++ b/test/official_tests/field/get_on_num.lox @@ -1 +1 @@ -123.foo; // expect runtime error: Only instances have properties. +123.foo; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/get_on_string.lox b/test/official_tests/field/get_on_string.lox index 8a56dad..16b609b 100644 --- a/test/official_tests/field/get_on_string.lox +++ b/test/official_tests/field/get_on_string.lox @@ -1 +1 @@ -"str".foo; // expect runtime error: Only instances have properties. +"str".foo; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_evaluation_order.lox b/test/official_tests/field/set_evaluation_order.lox index f9edb00..45c7896 100644 --- a/test/official_tests/field/set_evaluation_order.lox +++ b/test/official_tests/field/set_evaluation_order.lox @@ -1,2 +1,2 @@ -undefined1.bar // expect runtime error: Undefined variable 'undefined1'. +undefined1.bar // error: {"line":1,"message":"Undefined variable 'undefined1'","type":"RuntimeError"} = undefined2; \ No newline at end of file diff --git a/test/official_tests/field/set_on_bool.lox b/test/official_tests/field/set_on_bool.lox index eb9fb3a..957eeaa 100644 --- a/test/official_tests/field/set_on_bool.lox +++ b/test/official_tests/field/set_on_bool.lox @@ -1 +1 @@ -true.foo = "value"; // expect runtime error: Only instances have fields. +true.foo = "value"; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_on_class.lox b/test/official_tests/field/set_on_class.lox index a7700ec..59039cf 100644 --- a/test/official_tests/field/set_on_class.lox +++ b/test/official_tests/field/set_on_class.lox @@ -1,2 +1,2 @@ class Foo {} -Foo.bar = "value"; // expect runtime error: Only instances have fields. +Foo.bar = "value"; // error: {"line":2,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_on_function.lox b/test/official_tests/field/set_on_function.lox index d28c193..3572949 100644 --- a/test/official_tests/field/set_on_function.lox +++ b/test/official_tests/field/set_on_function.lox @@ -1,3 +1,3 @@ fun foo() {} -foo.bar = "value"; // expect runtime error: Only instances have fields. +foo.bar = "value"; // error: {"line":3,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_on_nil.lox b/test/official_tests/field/set_on_nil.lox index 560dded..5df965e 100644 --- a/test/official_tests/field/set_on_nil.lox +++ b/test/official_tests/field/set_on_nil.lox @@ -1 +1 @@ -nil.foo = "value"; // expect runtime error: Only instances have fields. +nil.foo = "value"; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_on_num.lox b/test/official_tests/field/set_on_num.lox index cd66c9b..cf9368e 100644 --- a/test/official_tests/field/set_on_num.lox +++ b/test/official_tests/field/set_on_num.lox @@ -1 +1 @@ -123.foo = "value"; // expect runtime error: Only instances have fields. +123.foo = "value"; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/set_on_string.lox b/test/official_tests/field/set_on_string.lox index c0fdb3b..af65467 100644 --- a/test/official_tests/field/set_on_string.lox +++ b/test/official_tests/field/set_on_string.lox @@ -1 +1 @@ -"str".foo = "value"; // expect runtime error: Only instances have fields. +"str".foo = "value"; // error: {"line":1,"message":"Only class instances have properties","type":"RuntimeError"} diff --git a/test/official_tests/field/undefined.lox b/test/official_tests/field/undefined.lox index b6b6297..2929089 100644 --- a/test/official_tests/field/undefined.lox +++ b/test/official_tests/field/undefined.lox @@ -1,4 +1,4 @@ class Foo {} var foo = Foo(); -foo.bar; // expect runtime error: Undefined property 'bar'. +foo.bar; // error: {"line":4,"message":"Undefined property 'bar' for class 'Foo'","type":"RuntimeError"} diff --git a/test/official_tests/for/class_in_body.lox b/test/official_tests/for/class_in_body.lox index 86e76b3..653e697 100644 --- a/test/official_tests/for/class_in_body.lox +++ b/test/official_tests/for/class_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'class': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} for (;;) class Foo {} diff --git a/test/official_tests/for/fun_in_body.lox b/test/official_tests/for/fun_in_body.lox index fd5c5d0..47282ba 100644 --- a/test/official_tests/for/fun_in_body.lox +++ b/test/official_tests/for/fun_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'fun': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} for (;;) fun foo() {} diff --git a/test/official_tests/for/statement_condition.lox b/test/official_tests/for/statement_condition.lox index a068c2e..01e6ddf 100644 --- a/test/official_tests/for/statement_condition.lox +++ b/test/official_tests/for/statement_condition.lox @@ -1,3 +1,3 @@ -// [line 3] Error at '{': Expect expression. -// [line 3] Error at ')': Expect ';' after expression. +// error: {"line":3,"message":"expect expression","type":"ParseError"} +// error: {"line":3,"message":"Expect ';' after value","type":"ParseError"} for (var a = 1; {}; a = a + 1) {} diff --git a/test/official_tests/for/statement_increment.lox b/test/official_tests/for/statement_increment.lox index 4f47d1d..0b76d9e 100644 --- a/test/official_tests/for/statement_increment.lox +++ b/test/official_tests/for/statement_increment.lox @@ -1,2 +1,2 @@ -// [line 2] Error at '{': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} for (var a = 1; a < 2; {}) {} diff --git a/test/official_tests/for/statement_initializer.lox b/test/official_tests/for/statement_initializer.lox index 9618e21..ea5bc38 100644 --- a/test/official_tests/for/statement_initializer.lox +++ b/test/official_tests/for/statement_initializer.lox @@ -1,3 +1,3 @@ -// [line 3] Error at '{': Expect expression. -// [line 3] Error at ')': Expect ';' after expression. +// error: {"line":3,"message":"expect expression","type":"ParseError"} +// error: {"line":3,"message":"Expect ';' after value","type":"ParseError"} for ({}; a < 2; a = a + 1) {} diff --git a/test/official_tests/for/var_in_body.lox b/test/official_tests/for/var_in_body.lox index 66d2257..a67b922 100644 --- a/test/official_tests/for/var_in_body.lox +++ b/test/official_tests/for/var_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'var': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} for (;;) var foo; diff --git a/test/official_tests/function/body_must_be_block.lox b/test/official_tests/function/body_must_be_block.lox index 57552bc..2cd4590 100644 --- a/test/official_tests/function/body_must_be_block.lox +++ b/test/official_tests/function/body_must_be_block.lox @@ -1,3 +1,3 @@ -// [line 3] Error at '123': Expect '{' before function body. +// error: {"line":3,"message":"Function body must start with '{'","type":"ParseError"} // [c line 4] Error at end: Expect '}' after block. fun f() 123; diff --git a/test/official_tests/function/extra_arguments.lox b/test/official_tests/function/extra_arguments.lox index 350ac63..d025a9b 100644 --- a/test/official_tests/function/extra_arguments.lox +++ b/test/official_tests/function/extra_arguments.lox @@ -3,4 +3,4 @@ fun f(a, b) { print b; } -f(1, 2, 3, 4); // expect runtime error: Expected 2 arguments but got 4. +f(1, 2, 3, 4); // error: {"line":6,"message":"Function 'f' expected 2 arguments but got 4","type":"RuntimeError"} diff --git a/test/official_tests/function/local_mutual_recursion.lox b/test/official_tests/function/local_mutual_recursion.lox index 2cd6c47..bcf39d2 100644 --- a/test/official_tests/function/local_mutual_recursion.lox +++ b/test/official_tests/function/local_mutual_recursion.lox @@ -1,7 +1,7 @@ { fun isEven(n) { if (n == 0) return true; - return isOdd(n - 1); // expect runtime error: Undefined variable 'isOdd'. + return isOdd(n - 1); // error: {"line":4,"message":"Undefined variable 'isOdd'","type":"RuntimeError"} } fun isOdd(n) { diff --git a/test/official_tests/function/missing_arguments.lox b/test/official_tests/function/missing_arguments.lox index 2acee00..9803267 100644 --- a/test/official_tests/function/missing_arguments.lox +++ b/test/official_tests/function/missing_arguments.lox @@ -1,3 +1,3 @@ fun f(a, b) {} -f(1); // expect runtime error: Expected 2 arguments but got 1. +f(1); // error: {"line":3,"message":"Function 'f' expected 2 arguments but got 1","type":"RuntimeError"} diff --git a/test/official_tests/function/missing_comma_in_parameters.lox b/test/official_tests/function/missing_comma_in_parameters.lox index eeb2d49..27975e7 100644 --- a/test/official_tests/function/missing_comma_in_parameters.lox +++ b/test/official_tests/function/missing_comma_in_parameters.lox @@ -1,3 +1,3 @@ -// [line 3] Error at 'c': Expect ')' after parameters. +// error: {"line":3,"message":"Expect ')' after parameters","type":"ParseError"} // [c line 4] Error at end: Expect '}' after block. fun foo(a, b c, d, e, f) {} diff --git a/test/official_tests/function/too_many_arguments.lox b/test/official_tests/function/too_many_arguments.lox index 9b21b86..c6aa986 100644 --- a/test/official_tests/function/too_many_arguments.lox +++ b/test/official_tests/function/too_many_arguments.lox @@ -257,5 +257,5 @@ fun foo() {} a, // 253 a, // 254 a, // 255 - a); // Error at 'a': Can't have more than 255 arguments. + a); // error: {"line":260,"message":"Can't have more than 255 arguments","type":"ParseError"} } diff --git a/test/official_tests/function/too_many_parameters.lox b/test/official_tests/function/too_many_parameters.lox index 869767b..20850db 100644 --- a/test/official_tests/function/too_many_parameters.lox +++ b/test/official_tests/function/too_many_parameters.lox @@ -254,4 +254,4 @@ fun f( a252, a253, a254, - a255, a) {} // Error at 'a': Can't have more than 255 parameters. + a255, a) {} // error: {"line":257,"message":"Can't have more than 255 parameters","type":"ParseError"} diff --git a/test/official_tests/if/class_in_else.lox b/test/official_tests/if/class_in_else.lox index 8308434..4e3008d 100644 --- a/test/official_tests/if/class_in_else.lox +++ b/test/official_tests/if/class_in_else.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'class': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) "ok"; else class Foo {} diff --git a/test/official_tests/if/class_in_then.lox b/test/official_tests/if/class_in_then.lox index f59f471..4d2b10d 100644 --- a/test/official_tests/if/class_in_then.lox +++ b/test/official_tests/if/class_in_then.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'class': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) class Foo {} diff --git a/test/official_tests/if/fun_in_else.lox b/test/official_tests/if/fun_in_else.lox index 694d1e8..8e772c2 100644 --- a/test/official_tests/if/fun_in_else.lox +++ b/test/official_tests/if/fun_in_else.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'fun': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) "ok"; else fun foo() {} diff --git a/test/official_tests/if/fun_in_then.lox b/test/official_tests/if/fun_in_then.lox index 75421d8..96ca699 100644 --- a/test/official_tests/if/fun_in_then.lox +++ b/test/official_tests/if/fun_in_then.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'fun': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) fun foo() {} diff --git a/test/official_tests/if/var_in_else.lox b/test/official_tests/if/var_in_else.lox index 59cff3d..d53b9c7 100644 --- a/test/official_tests/if/var_in_else.lox +++ b/test/official_tests/if/var_in_else.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'var': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) "ok"; else var foo; diff --git a/test/official_tests/if/var_in_then.lox b/test/official_tests/if/var_in_then.lox index a8b111b..aeb750e 100644 --- a/test/official_tests/if/var_in_then.lox +++ b/test/official_tests/if/var_in_then.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'var': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} if (true) var foo; diff --git a/test/official_tests/inheritance/inherit_from_function.lox b/test/official_tests/inheritance/inherit_from_function.lox index 0d3ce48..6848e13 100644 --- a/test/official_tests/inheritance/inherit_from_function.lox +++ b/test/official_tests/inheritance/inherit_from_function.lox @@ -1,3 +1,3 @@ fun foo() {} -class Subclass < foo {} // expect runtime error: Superclass must be a class. +class Subclass < foo {} // error: {"line":3,"message":"Superclass must be a class","type":"RuntimeError"} diff --git a/test/official_tests/inheritance/inherit_from_nil.lox b/test/official_tests/inheritance/inherit_from_nil.lox index 72cf8ed..a61ea0b 100644 --- a/test/official_tests/inheritance/inherit_from_nil.lox +++ b/test/official_tests/inheritance/inherit_from_nil.lox @@ -1,2 +1,2 @@ var Nil = nil; -class Foo < Nil {} // expect runtime error: Superclass must be a class. +class Foo < Nil {} // error: {"line":2,"message":"Superclass must be a class","type":"RuntimeError"} diff --git a/test/official_tests/inheritance/inherit_from_number.lox b/test/official_tests/inheritance/inherit_from_number.lox index ab1539a..3a8531a 100644 --- a/test/official_tests/inheritance/inherit_from_number.lox +++ b/test/official_tests/inheritance/inherit_from_number.lox @@ -1,2 +1,2 @@ var Number = 123; -class Foo < Number {} // expect runtime error: Superclass must be a class. +class Foo < Number {} // error: {"line":2,"message":"Superclass must be a class","type":"RuntimeError"} diff --git a/test/official_tests/inheritance/parenthesized_superclass.lox b/test/official_tests/inheritance/parenthesized_superclass.lox index 4b59836..cf55b2c 100644 --- a/test/official_tests/inheritance/parenthesized_superclass.lox +++ b/test/official_tests/inheritance/parenthesized_superclass.lox @@ -1,4 +1,4 @@ class Foo {} -// [line 4] Error at '(': Expect superclass name. +// error: {"line":4,"message":"Expect superclass name","type":"ParseError"} class Bar < (Foo) {} diff --git a/test/official_tests/method/extra_arguments.lox b/test/official_tests/method/extra_arguments.lox index 76f3fab..443cb77 100644 --- a/test/official_tests/method/extra_arguments.lox +++ b/test/official_tests/method/extra_arguments.lox @@ -5,4 +5,4 @@ class Foo { } } -Foo().method(1, 2, 3, 4); // expect runtime error: Expected 2 arguments but got 4. +Foo().method(1, 2, 3, 4); // error: {"line":8,"message":"Function 'method' expected 2 arguments but got 4","type":"RuntimeError"} diff --git a/test/official_tests/method/missing_arguments.lox b/test/official_tests/method/missing_arguments.lox index 734f455..22a0301 100644 --- a/test/official_tests/method/missing_arguments.lox +++ b/test/official_tests/method/missing_arguments.lox @@ -2,4 +2,4 @@ class Foo { method(a, b) {} } -Foo().method(1); // expect runtime error: Expected 2 arguments but got 1. +Foo().method(1); // error: {"line":5,"message":"Function 'method' expected 2 arguments but got 1","type":"RuntimeError"} diff --git a/test/official_tests/method/not_found.lox b/test/official_tests/method/not_found.lox index bbac99b..4b1debe 100644 --- a/test/official_tests/method/not_found.lox +++ b/test/official_tests/method/not_found.lox @@ -1,3 +1,3 @@ class Foo {} -Foo().unknown(); // expect runtime error: Undefined property 'unknown'. +Foo().unknown(); // error: {"line":3,"message":"Undefined property 'unknown' for class 'Foo'","type":"RuntimeError"} diff --git a/test/official_tests/method/refer_to_name.lox b/test/official_tests/method/refer_to_name.lox index df59d42..26e9b2e 100644 --- a/test/official_tests/method/refer_to_name.lox +++ b/test/official_tests/method/refer_to_name.lox @@ -1,6 +1,6 @@ class Foo { method() { - print method; // expect runtime error: Undefined variable 'method'. + print method; // error: {"line":3,"message":"Undefined variable 'method'","type":"RuntimeError"} } } diff --git a/test/official_tests/method/too_many_arguments.lox b/test/official_tests/method/too_many_arguments.lox index 1777f72..d03fcf9 100644 --- a/test/official_tests/method/too_many_arguments.lox +++ b/test/official_tests/method/too_many_arguments.lox @@ -256,5 +256,5 @@ a, // 253 a, // 254 a, // 255 - a); // Error at 'a': Can't have more than 255 arguments. + a); // error: {"line":259,"message":"Can't have more than 255 arguments","type":"ParseError"} } diff --git a/test/official_tests/method/too_many_parameters.lox b/test/official_tests/method/too_many_parameters.lox index 9e9b921..88a28c7 100644 --- a/test/official_tests/method/too_many_parameters.lox +++ b/test/official_tests/method/too_many_parameters.lox @@ -255,5 +255,5 @@ class Foo { a252, a253, a254, - a255, a) {} // Error at 'a': Can't have more than 255 parameters. + a255, a) {} // error: {"line":258,"message":"Can't have more than 255 parameters","type":"ParseError"} } diff --git a/test/official_tests/number/decimal_point_at_eof.lox b/test/official_tests/number/decimal_point_at_eof.lox index 9181925..82c77f9 100644 --- a/test/official_tests/number/decimal_point_at_eof.lox +++ b/test/official_tests/number/decimal_point_at_eof.lox @@ -1,2 +1,2 @@ -// [line 2] Error at end: Expect property name after '.'. +// error: {"line":2,"message":"Expect property name after '.'","type":"ParseError"} 123. \ No newline at end of file diff --git a/test/official_tests/number/leading_dot.lox b/test/official_tests/number/leading_dot.lox index 93345a5..9bffc60 100644 --- a/test/official_tests/number/leading_dot.lox +++ b/test/official_tests/number/leading_dot.lox @@ -1,2 +1,2 @@ -// [line 2] Error at '.': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} .123; diff --git a/test/official_tests/number/trailing_dot.lox b/test/official_tests/number/trailing_dot.lox index 1af7dea..92565bf 100644 --- a/test/official_tests/number/trailing_dot.lox +++ b/test/official_tests/number/trailing_dot.lox @@ -1,2 +1,2 @@ -// [line 2] Error at ';': Expect property name after '.'. +// error: {"line":2,"message":"Expect property name after '.'","type":"ParseError"} 123.; diff --git a/test/official_tests/operator/add_bool_nil.lox b/test/official_tests/operator/add_bool_nil.lox index 6c4d7fd..72bddc8 100644 --- a/test/official_tests/operator/add_bool_nil.lox +++ b/test/official_tests/operator/add_bool_nil.lox @@ -1 +1 @@ -true + nil; // expect runtime error: Operands must be two numbers or two strings. +true + nil; // error: {"line":1,"message":"Operator '+': incompatible types 'boolean' and 'nil'","type":"RuntimeError"} diff --git a/test/official_tests/operator/add_bool_num.lox b/test/official_tests/operator/add_bool_num.lox index 308245a..51f310d 100644 --- a/test/official_tests/operator/add_bool_num.lox +++ b/test/official_tests/operator/add_bool_num.lox @@ -1 +1 @@ -true + 123; // expect runtime error: Operands must be two numbers or two strings. +true + 123; // error: {"line":1,"message":"Operator '+': incompatible types 'boolean' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/add_bool_string.lox b/test/official_tests/operator/add_bool_string.lox index 04739d5..a88f169 100644 --- a/test/official_tests/operator/add_bool_string.lox +++ b/test/official_tests/operator/add_bool_string.lox @@ -1 +1 @@ -true + "s"; // expect runtime error: Operands must be two numbers or two strings. +true + "s"; // error: {"line":1,"message":"Operator '+': incompatible types 'boolean' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/add_nil_nil.lox b/test/official_tests/operator/add_nil_nil.lox index b8371ab..c360208 100644 --- a/test/official_tests/operator/add_nil_nil.lox +++ b/test/official_tests/operator/add_nil_nil.lox @@ -1 +1 @@ -nil + nil; // expect runtime error: Operands must be two numbers or two strings. +nil + nil; // error: {"line":1,"message":"Operator '+': incompatible types 'nil' and 'nil'","type":"RuntimeError"} diff --git a/test/official_tests/operator/add_num_nil.lox b/test/official_tests/operator/add_num_nil.lox index 1ce8fb7..e63ea60 100644 --- a/test/official_tests/operator/add_num_nil.lox +++ b/test/official_tests/operator/add_num_nil.lox @@ -1 +1 @@ -1 + nil; // expect runtime error: Operands must be two numbers or two strings. +1 + nil; // error: {"line":1,"message":"Operator '+': incompatible types 'number' and 'nil'","type":"RuntimeError"} diff --git a/test/official_tests/operator/add_string_nil.lox b/test/official_tests/operator/add_string_nil.lox index 5949630..8348dae 100644 --- a/test/official_tests/operator/add_string_nil.lox +++ b/test/official_tests/operator/add_string_nil.lox @@ -1 +1 @@ -"s" + nil; // expect runtime error: Operands must be two numbers or two strings. +"s" + nil; // error: {"line":1,"message":"Operator '+': incompatible types 'string' and 'nil'","type":"RuntimeError"} diff --git a/test/official_tests/operator/divide_nonnum_num.lox b/test/official_tests/operator/divide_nonnum_num.lox index e406498..51097cc 100644 --- a/test/official_tests/operator/divide_nonnum_num.lox +++ b/test/official_tests/operator/divide_nonnum_num.lox @@ -1 +1 @@ -"1" / 1; // expect runtime error: Operands must be numbers. +"1" / 1; // error: {"line":1,"message":"Operator '/': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/divide_num_nonnum.lox b/test/official_tests/operator/divide_num_nonnum.lox index 9596cc6..a7535d0 100644 --- a/test/official_tests/operator/divide_num_nonnum.lox +++ b/test/official_tests/operator/divide_num_nonnum.lox @@ -1 +1 @@ -1 / "1"; // expect runtime error: Operands must be numbers. +1 / "1"; // error: {"line":1,"message":"Operator '/': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/greater_nonnum_num.lox b/test/official_tests/operator/greater_nonnum_num.lox index 4746c03..35de99d 100644 --- a/test/official_tests/operator/greater_nonnum_num.lox +++ b/test/official_tests/operator/greater_nonnum_num.lox @@ -1 +1 @@ -"1" > 1; // expect runtime error: Operands must be numbers. +"1" > 1; // error: {"line":1,"message":"Operator '>': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/greater_num_nonnum.lox b/test/official_tests/operator/greater_num_nonnum.lox index e06f67d..cb2c415 100644 --- a/test/official_tests/operator/greater_num_nonnum.lox +++ b/test/official_tests/operator/greater_num_nonnum.lox @@ -1 +1 @@ -1 > "1"; // expect runtime error: Operands must be numbers. +1 > "1"; // error: {"line":1,"message":"Operator '>': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/greater_or_equal_nonnum_num.lox b/test/official_tests/operator/greater_or_equal_nonnum_num.lox index 67994c6..707f083 100644 --- a/test/official_tests/operator/greater_or_equal_nonnum_num.lox +++ b/test/official_tests/operator/greater_or_equal_nonnum_num.lox @@ -1 +1 @@ -"1" >= 1; // expect runtime error: Operands must be numbers. +"1" >= 1; // error: {"line":1,"message":"Operator '>=': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/greater_or_equal_num_nonnum.lox b/test/official_tests/operator/greater_or_equal_num_nonnum.lox index 592eab4..cc90517 100644 --- a/test/official_tests/operator/greater_or_equal_num_nonnum.lox +++ b/test/official_tests/operator/greater_or_equal_num_nonnum.lox @@ -1 +1 @@ -1 >= "1"; // expect runtime error: Operands must be numbers. +1 >= "1"; // error: {"line":1,"message":"Operator '>=': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/less_nonnum_num.lox b/test/official_tests/operator/less_nonnum_num.lox index 38a75db..1d3e399 100644 --- a/test/official_tests/operator/less_nonnum_num.lox +++ b/test/official_tests/operator/less_nonnum_num.lox @@ -1 +1 @@ -"1" < 1; // expect runtime error: Operands must be numbers. +"1" < 1; // error: {"line":1,"message":"Operator '<': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/less_num_nonnum.lox b/test/official_tests/operator/less_num_nonnum.lox index 3abfe24..cf8d5d0 100644 --- a/test/official_tests/operator/less_num_nonnum.lox +++ b/test/official_tests/operator/less_num_nonnum.lox @@ -1 +1 @@ -1 < "1"; // expect runtime error: Operands must be numbers. +1 < "1"; // error: {"line":1,"message":"Operator '<': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/less_or_equal_nonnum_num.lox b/test/official_tests/operator/less_or_equal_nonnum_num.lox index 2bad506..7b3b3a2 100644 --- a/test/official_tests/operator/less_or_equal_nonnum_num.lox +++ b/test/official_tests/operator/less_or_equal_nonnum_num.lox @@ -1 +1 @@ -"1" <= 1; // expect runtime error: Operands must be numbers. +"1" <= 1; // error: {"line":1,"message":"Operator '<=': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/less_or_equal_num_nonnum.lox b/test/official_tests/operator/less_or_equal_num_nonnum.lox index c5daf7b..b2f829e 100644 --- a/test/official_tests/operator/less_or_equal_num_nonnum.lox +++ b/test/official_tests/operator/less_or_equal_num_nonnum.lox @@ -1 +1 @@ -1 <= "1"; // expect runtime error: Operands must be numbers. +1 <= "1"; // error: {"line":1,"message":"Operator '<=': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/multiply_nonnum_num.lox b/test/official_tests/operator/multiply_nonnum_num.lox index fdeeb96..a166781 100644 --- a/test/official_tests/operator/multiply_nonnum_num.lox +++ b/test/official_tests/operator/multiply_nonnum_num.lox @@ -1 +1 @@ -"1" * 1; // expect runtime error: Operands must be numbers. +"1" * 1; // error: {"line":1,"message":"Operator '*': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/multiply_num_nonnum.lox b/test/official_tests/operator/multiply_num_nonnum.lox index 58fb3ae..52f0d1e 100644 --- a/test/official_tests/operator/multiply_num_nonnum.lox +++ b/test/official_tests/operator/multiply_num_nonnum.lox @@ -1 +1 @@ -1 * "1"; // expect runtime error: Operands must be numbers. +1 * "1"; // error: {"line":1,"message":"Operator '*': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/negate_nonnum.lox b/test/official_tests/operator/negate_nonnum.lox index e76792f..c117613 100644 --- a/test/official_tests/operator/negate_nonnum.lox +++ b/test/official_tests/operator/negate_nonnum.lox @@ -1 +1 @@ --"s"; // expect runtime error: Operand must be a number. +-"s"; // error: {"line":1,"message":"Operator '-': incompatible type 'string'","type":"RuntimeError"} diff --git a/test/official_tests/operator/subtract_nonnum_num.lox b/test/official_tests/operator/subtract_nonnum_num.lox index 2accd9f..dc928ca 100644 --- a/test/official_tests/operator/subtract_nonnum_num.lox +++ b/test/official_tests/operator/subtract_nonnum_num.lox @@ -1 +1 @@ -"1" - 1; // expect runtime error: Operands must be numbers. +"1" - 1; // error: {"line":1,"message":"Operator '-': incompatible types 'string' and 'number'","type":"RuntimeError"} diff --git a/test/official_tests/operator/subtract_num_nonnum.lox b/test/official_tests/operator/subtract_num_nonnum.lox index c7a4d68..88a5b6d 100644 --- a/test/official_tests/operator/subtract_num_nonnum.lox +++ b/test/official_tests/operator/subtract_num_nonnum.lox @@ -1 +1 @@ -1 - "1"; // expect runtime error: Operands must be numbers. +1 - "1"; // error: {"line":1,"message":"Operator '-': incompatible types 'number' and 'string'","type":"RuntimeError"} diff --git a/test/official_tests/print/missing_argument.lox b/test/official_tests/print/missing_argument.lox index 78bf2c1..2af7399 100644 --- a/test/official_tests/print/missing_argument.lox +++ b/test/official_tests/print/missing_argument.lox @@ -1,2 +1,2 @@ -// [line 2] Error at ';': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} print; diff --git a/test/official_tests/return/at_top_level.lox b/test/official_tests/return/at_top_level.lox index 22f39df..f599d61 100644 --- a/test/official_tests/return/at_top_level.lox +++ b/test/official_tests/return/at_top_level.lox @@ -1 +1 @@ -return "wat"; // Error at 'return': Can't return from top-level code. +return "wat"; // error: {"line":1,"message":"Can't return from top-level code","type":"RuntimeError"} diff --git a/test/official_tests/string/error_after_multiline.lox b/test/official_tests/string/error_after_multiline.lox index 59bcbdf..ee510d3 100644 --- a/test/official_tests/string/error_after_multiline.lox +++ b/test/official_tests/string/error_after_multiline.lox @@ -4,4 +4,4 @@ var a = "1 3 "; -err; // // expect runtime error: Undefined variable 'err'. \ No newline at end of file +err; // error: {"line":7,"message":"Undefined variable 'err'","type":"RuntimeError"} \ No newline at end of file diff --git a/test/official_tests/string/unterminated.lox b/test/official_tests/string/unterminated.lox index fd169f7..dd325ec 100644 --- a/test/official_tests/string/unterminated.lox +++ b/test/official_tests/string/unterminated.lox @@ -1,2 +1,2 @@ -// [line 2] Error: Unterminated string. +// error: {"line":2,"message":"Unterminated string literal","type":"LexingError"} "this string has no close quote \ No newline at end of file diff --git a/test/official_tests/super/extra_arguments.lox b/test/official_tests/super/extra_arguments.lox index b6b0735..7fc8674 100644 --- a/test/official_tests/super/extra_arguments.lox +++ b/test/official_tests/super/extra_arguments.lox @@ -7,7 +7,7 @@ class Base { class Derived < Base { foo() { print "Derived.foo()"; // expect: Derived.foo() - super.foo("a", "b", "c", "d"); // expect runtime error: Expected 2 arguments but got 4. + super.foo("a", "b", "c", "d"); // error: {"line":10,"message":"Function 'foo' expected 2 arguments but got 4","type":"RuntimeError"} } } diff --git a/test/official_tests/super/missing_arguments.lox b/test/official_tests/super/missing_arguments.lox index e055a6e..9576345 100644 --- a/test/official_tests/super/missing_arguments.lox +++ b/test/official_tests/super/missing_arguments.lox @@ -6,7 +6,7 @@ class Base { class Derived < Base { foo() { - super.foo(1); // expect runtime error: Expected 2 arguments but got 1. + super.foo(1); // error: {"line":9,"message":"Function 'foo' expected 2 arguments but got 1","type":"RuntimeError"} } } diff --git a/test/official_tests/super/no_superclass_bind.lox b/test/official_tests/super/no_superclass_bind.lox index bdb5fd6..e759400 100644 --- a/test/official_tests/super/no_superclass_bind.lox +++ b/test/official_tests/super/no_superclass_bind.lox @@ -1,6 +1,6 @@ class Base { foo() { - super.doesNotExist; // Error at 'super': Can't use 'super' in a class with no superclass. + super.doesNotExist; // error: {"line":3,"message":"Can't use 'super' in a class with no superclass","type":"RuntimeError"} } } diff --git a/test/official_tests/super/no_superclass_call.lox b/test/official_tests/super/no_superclass_call.lox index d807e50..1a9a9c9 100644 --- a/test/official_tests/super/no_superclass_call.lox +++ b/test/official_tests/super/no_superclass_call.lox @@ -1,6 +1,6 @@ class Base { foo() { - super.doesNotExist(1); // Error at 'super': Can't use 'super' in a class with no superclass. + super.doesNotExist(1); // error: {"line":3,"message":"Can't use 'super' in a class with no superclass","type":"RuntimeError"} } } diff --git a/test/official_tests/super/no_superclass_method.lox b/test/official_tests/super/no_superclass_method.lox index 89e6b17..29aae8b 100644 --- a/test/official_tests/super/no_superclass_method.lox +++ b/test/official_tests/super/no_superclass_method.lox @@ -2,7 +2,7 @@ class Base {} class Derived < Base { foo() { - super.doesNotExist(1); // expect runtime error: Undefined property 'doesNotExist'. + super.doesNotExist(1); // error: {"line":5,"message":"Undefined property 'super' for class 'Base'","type":"RuntimeError"} } } diff --git a/test/official_tests/super/parenthesized.lox b/test/official_tests/super/parenthesized.lox index 63fdc72..5d44888 100644 --- a/test/official_tests/super/parenthesized.lox +++ b/test/official_tests/super/parenthesized.lox @@ -4,7 +4,7 @@ class A { class B < A { method() { - // [line 8] Error at ')': Expect '.' after 'super'. + // error: {"line":8,"message":"Expect '.' after 'super'","type":"ParseError"} (super).method(); } } diff --git a/test/official_tests/super/super_at_top_level.lox b/test/official_tests/super/super_at_top_level.lox index 83c4213..e54746f 100644 --- a/test/official_tests/super/super_at_top_level.lox +++ b/test/official_tests/super/super_at_top_level.lox @@ -1,2 +1,2 @@ -super.foo("bar"); // Error at 'super': Can't use 'super' outside of a class. -super.foo; // Error at 'super': Can't use 'super' outside of a class. \ No newline at end of file +super.foo("bar"); // error: {"line":1,"message":"Can't use 'super' outside of a class","type":"RuntimeError"} +super.foo; // error: {"line":2,"message":"Can't use 'super' outside of a class","type":"RuntimeError"} \ No newline at end of file diff --git a/test/official_tests/super/super_in_top_level_function.lox b/test/official_tests/super/super_in_top_level_function.lox index 42997b1..37990e3 100644 --- a/test/official_tests/super/super_in_top_level_function.lox +++ b/test/official_tests/super/super_in_top_level_function.lox @@ -1,3 +1,3 @@ - super.bar(); // Error at 'super': Can't use 'super' outside of a class. + super.bar(); // error: {"line":1,"message":"Can't use 'super' outside of a class","type":"RuntimeError"} fun foo() { } \ No newline at end of file diff --git a/test/official_tests/super/super_without_dot.lox b/test/official_tests/super/super_without_dot.lox index 3fe95a7..0adb6a1 100644 --- a/test/official_tests/super/super_without_dot.lox +++ b/test/official_tests/super/super_without_dot.lox @@ -2,7 +2,7 @@ class A {} class B < A { method() { - // [line 6] Error at ';': Expect '.' after 'super'. + // error: {"line":6,"message":"Expect '.' after 'super'","type":"ParseError"} super; } } diff --git a/test/official_tests/super/super_without_name.lox b/test/official_tests/super/super_without_name.lox index 5917031..d308b83 100644 --- a/test/official_tests/super/super_without_name.lox +++ b/test/official_tests/super/super_without_name.lox @@ -2,6 +2,6 @@ class A {} class B < A { method() { - super.; // Error at ';': Expect superclass method name. + super.; // error: {"line":5,"message":"Expect superclass method name","type":"ParseError"} } } diff --git a/test/official_tests/this/this_at_top_level.lox b/test/official_tests/this/this_at_top_level.lox index 918c806..f68411c 100644 --- a/test/official_tests/this/this_at_top_level.lox +++ b/test/official_tests/this/this_at_top_level.lox @@ -1 +1 @@ -this; // Error at 'this': Can't use 'this' outside of a class. +this; // error: {"line":1,"message":"Can't use 'this' outside of a class","type":"RuntimeError"} diff --git a/test/official_tests/this/this_in_top_level_function.lox b/test/official_tests/this/this_in_top_level_function.lox index a564c43..878b5ca 100644 --- a/test/official_tests/this/this_in_top_level_function.lox +++ b/test/official_tests/this/this_in_top_level_function.lox @@ -1,3 +1,3 @@ fun foo() { - this; // Error at 'this': Can't use 'this' outside of a class. + this; // error: {"line":2,"message":"Can't use 'this' outside of a class","type":"RuntimeError"} } diff --git a/test/official_tests/unexpected_character.lox b/test/official_tests/unexpected_character.lox index 5e51396..7cf8df9 100644 --- a/test/official_tests/unexpected_character.lox +++ b/test/official_tests/unexpected_character.lox @@ -1,3 +1,3 @@ -// [line 3] Error: Unexpected character. -// [java line 3] Error at 'b': Expect ')' after arguments. +// error: {"line":3,"message":"Unexpected character '|'","type":"LexingError"} +// error: {"line":3,"message":"Expect ')' after function arguments","type":"ParseError"} foo(a | b); diff --git a/test/official_tests/variable/collide_with_parameter.lox b/test/official_tests/variable/collide_with_parameter.lox index 2c06bd1..3e37f8d 100644 --- a/test/official_tests/variable/collide_with_parameter.lox +++ b/test/official_tests/variable/collide_with_parameter.lox @@ -1,3 +1,3 @@ fun foo(a) { - var a; // Error at 'a': Already a variable with this name in this scope. + var a; // error: {"line":2,"message":"Variable 'a' is already declared in this scope","type":"RuntimeError"} } diff --git a/test/official_tests/variable/duplicate_local.lox b/test/official_tests/variable/duplicate_local.lox index 77cb951..9c6c57a 100644 --- a/test/official_tests/variable/duplicate_local.lox +++ b/test/official_tests/variable/duplicate_local.lox @@ -1,4 +1,4 @@ { var a = "value"; - var a = "other"; // Error at 'a': Already a variable with this name in this scope. + var a = "other"; // error: {"line":3,"message":"Variable 'a' is already declared in this scope","type":"RuntimeError"} } diff --git a/test/official_tests/variable/duplicate_parameter.lox b/test/official_tests/variable/duplicate_parameter.lox index 23c5049..9bf3df4 100644 --- a/test/official_tests/variable/duplicate_parameter.lox +++ b/test/official_tests/variable/duplicate_parameter.lox @@ -1,4 +1,4 @@ fun foo(arg, - arg) { // Error at 'arg': Already a variable with this name in this scope. + arg) { // error: {"line":2,"message":"Variable 'arg' is already declared in this scope","type":"RuntimeError"} "body"; } diff --git a/test/official_tests/variable/undefined_global.lox b/test/official_tests/variable/undefined_global.lox index 5ac89b8..e708af8 100644 --- a/test/official_tests/variable/undefined_global.lox +++ b/test/official_tests/variable/undefined_global.lox @@ -1 +1 @@ -print notDefined; // expect runtime error: Undefined variable 'notDefined'. +print notDefined; // error: {"line":1,"message":"Undefined variable 'notDefined'","type":"RuntimeError"} diff --git a/test/official_tests/variable/undefined_local.lox b/test/official_tests/variable/undefined_local.lox index d196d59..cfd068c 100644 --- a/test/official_tests/variable/undefined_local.lox +++ b/test/official_tests/variable/undefined_local.lox @@ -1,3 +1,3 @@ { - print notDefined; // expect runtime error: Undefined variable 'notDefined'. + print notDefined; // error: {"line":2,"message":"Undefined variable 'notDefined'","type":"RuntimeError"} } diff --git a/test/official_tests/variable/use_false_as_var.lox b/test/official_tests/variable/use_false_as_var.lox index 206d51e..dac3f38 100644 --- a/test/official_tests/variable/use_false_as_var.lox +++ b/test/official_tests/variable/use_false_as_var.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'false': Expect variable name. +// error: {"line":2,"message":"Expect a variable name","type":"ParseError"} var false = "value"; diff --git a/test/official_tests/variable/use_local_in_initializer.lox b/test/official_tests/variable/use_local_in_initializer.lox index 797a13c..d657ac6 100644 --- a/test/official_tests/variable/use_local_in_initializer.lox +++ b/test/official_tests/variable/use_local_in_initializer.lox @@ -1,4 +1,4 @@ var a = "outer"; { - var a = a; // Error at 'a': Can't read local variable in its own initializer. + var a = a; // error: {"line":3,"message":"Can't read local variable in its own initializer","type":"RuntimeError"} } diff --git a/test/official_tests/variable/use_nil_as_var.lox b/test/official_tests/variable/use_nil_as_var.lox index 7624bce..e6b67c4 100644 --- a/test/official_tests/variable/use_nil_as_var.lox +++ b/test/official_tests/variable/use_nil_as_var.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'nil': Expect variable name. +// error: {"line":2,"message":"Expect a variable name","type":"ParseError"} var nil = "value"; diff --git a/test/official_tests/variable/use_this_as_var.lox b/test/official_tests/variable/use_this_as_var.lox index 233a553..da8def0 100644 --- a/test/official_tests/variable/use_this_as_var.lox +++ b/test/official_tests/variable/use_this_as_var.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'this': Expect variable name. +// error: {"line":2,"message":"Expect a variable name","type":"ParseError"} var this = "value"; diff --git a/test/official_tests/while/class_in_body.lox b/test/official_tests/while/class_in_body.lox index c3cbe95..c52b94b 100644 --- a/test/official_tests/while/class_in_body.lox +++ b/test/official_tests/while/class_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'class': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} while (true) class Foo {} diff --git a/test/official_tests/while/fun_in_body.lox b/test/official_tests/while/fun_in_body.lox index 8a42a48..1933597 100644 --- a/test/official_tests/while/fun_in_body.lox +++ b/test/official_tests/while/fun_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'fun': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} while (true) fun foo() {} diff --git a/test/official_tests/while/var_in_body.lox b/test/official_tests/while/var_in_body.lox index bafc2fd..a458fbb 100644 --- a/test/official_tests/while/var_in_body.lox +++ b/test/official_tests/while/var_in_body.lox @@ -1,2 +1,2 @@ -// [line 2] Error at 'var': Expect expression. +// error: {"line":2,"message":"expect expression","type":"ParseError"} while (true) var foo;