From 9208a7f8622be6ef8ded71ed1536f578054ba200 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Mon, 28 Oct 2013 07:12:39 -0700 Subject: [PATCH] Start testing errors. - Add support to test runner for compile errors. - Include line number in tokens. - Exit with non-zero on compile error. --- runtests | 57 +++++++++++++++++++++++++++----------- src/compiler.c | 32 +++++++++++++++------ src/main.c | 7 ++++- test/undefined_global.wren | 1 + test/undefined_local.wren | 3 ++ 5 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 test/undefined_global.wren create mode 100644 test/undefined_local.wren diff --git a/runtests b/runtests index 18513718..39f8dd08 100755 --- a/runtests +++ b/runtests @@ -33,6 +33,8 @@ else: sys.exit('System not supported!') EXPECT_PATTERN = re.compile(r'// expect: (.*)') +EXPECT_ERROR_PATTERN = re.compile(r'// expect error') +ERROR_PATTERN = re.compile(r'\[Line (\d+)\] Error ') SKIP_PATTERN = re.compile(r'// skip: (.*)') NONTEST_PATTERN = re.compile(r'// nontest') @@ -112,6 +114,13 @@ def run_test(path): if match: expect_output.append((match.group(1), line_num)) + match = EXPECT_ERROR_PATTERN.search(line) + if match: + expect_error.append(line_num) + # If we expect compile errors in the test, it should return + # exit code 1. + expect_return = 1 + match = SKIP_PATTERN.search(line) if match: num_skipped += 1 @@ -132,10 +141,27 @@ def run_test(path): fails = [] + # Validate that no unexpected errors occurred. + if expect_return == 1 and err != '': + lines = err.split('\n') + while len(lines) > 0: + line = lines.pop(0) + match = ERROR_PATTERN.search(line) + if match: + if float(match.group(1)) not in expect_error: + fails.append('Unexpected error:') + fails.append(line) + elif line != '': + fails.append('Unexpected output on stderr:') + fails.append(line) + else: + for line in expect_error: + fails.append('Expected error on line ' + str(line) + ' and got none.') + # Validate the exit code. - if proc.returncode != 0: - fails.append('Expected return code 0 and got {0}.'. - format(proc.returncode)) + if proc.returncode != expect_return: + fails.append('Expected return code {0} and got {1}.' + .format(expect_return, proc.returncode)) else: # Validate the output. expect_index = 0 @@ -143,23 +169,22 @@ def run_test(path): # Remove the trailing last empty line. out_lines = out.split('\n') if out_lines[-1] == '': - del out_lines[-1] + del out_lines[-1] for line in out_lines: - if expect_index >= len(expect_output): - fails.append('Got output "{0}" when none was expected.'. - format(line)) - elif expect_output[expect_index][0] != line: - fails.append('Expected output "{0}" on line {1} and got "{2}".'. - format(expect_output[expect_index][0], - expect_output[expect_index][1], line)) - expect_index += 1 + if expect_index >= len(expect_output): + fails.append('Got output "{0}" when none was expected.'.format(line)) + elif expect_output[expect_index][0] != line: + fails.append('Expected output "{0}" on line {1} and got "{2}".'. + format(expect_output[expect_index][0], + expect_output[expect_index][1], line)) + expect_index += 1 while expect_index < len(expect_output): - fails.append('Missing expected output "{0}" on line {1}.'. - format(expect_output[expect_index][0], - expect_output[expect_index][1])) - expect_index += 1 + fails.append('Missing expected output "{0}" on line {1}.'. + format(expect_output[expect_index][0], + expect_output[expect_index][1])) + expect_index += 1 # Display the results. if len(fails) == 0: diff --git a/src/compiler.c b/src/compiler.c index e1159ce0..5ad11448 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -51,8 +51,15 @@ typedef enum typedef struct Token_s { TokenType type; + + // The beginning of the token as an offset of characters in the source. int start; + + // The offset of the character immediately following the end of the token. int end; + + // The 1-based line where the token appears. + int line; } Token; typedef struct @@ -68,6 +75,9 @@ typedef struct // The position of the current character being lexed. int currentChar; + // The 1-based line number of [currentChar]. + int currentLine; + // The most recently lexed token. Token current; @@ -191,12 +201,14 @@ ObjBlock* compile(VM* vm, const char* source, size_t sourceLength) parser.tokenStart = 0; parser.currentChar = 0; + parser.currentLine = 1; // Zero-init the current token. This will get copied to previous when // advance() is called below. parser.current.type = TOKEN_EOF; parser.current.start = 0; parser.current.end = 0; + parser.current.line = 0; // Read the first token. nextToken(&parser); @@ -300,22 +312,22 @@ void emit(Compiler* compiler, Code code) void error(Compiler* compiler, const char* format, ...) { compiler->parser->hasError = 1; - printf("Compile error on '"); + fprintf(stderr, "[Line %d] Error on '", compiler->parser->previous.line); for (int i = compiler->parser->previous.start; i < compiler->parser->previous.end; i++) { - putchar(compiler->parser->source[i]); + putc(compiler->parser->source[i], stderr); } - printf("': "); + fprintf(stderr, "': "); va_list args; va_start(args, format); - vprintf(format, args); + vfprintf(stderr, format, args); va_end(args); - printf("\n"); + fprintf(stderr, "\n"); } void statement(Compiler* compiler) @@ -499,8 +511,8 @@ void primary(Compiler* compiler) return; } - // TODO(bob): Look for globals or names in outer scopes. - error(compiler, "Unknown variable."); + // TODO(bob): Look for names in outer scopes. + error(compiler, "Undefined variable."); } // Number. @@ -734,7 +746,10 @@ void readRawToken(Parser* parser) } return; - case '\n': makeToken(parser, TOKEN_LINE); return; + case '\n': + parser->currentLine++; + makeToken(parser, TOKEN_LINE); + return; case ' ': skipWhitespace(parser); break; case '"': readString(parser); return; @@ -842,5 +857,6 @@ void makeToken(Parser* parser, TokenType type) parser->current.type = type; parser->current.start = parser->tokenStart; parser->current.end = parser->currentChar; + parser->current.line = parser->currentLine; } diff --git a/src/main.c b/src/main.c index 64817436..f7d233b2 100644 --- a/src/main.c +++ b/src/main.c @@ -28,15 +28,20 @@ int main(int argc, const char * argv[]) VM* vm = newVM(); ObjBlock* block = compile(vm, source, length); + int exitCode = 0; if (block) { interpret(vm, block); } + else + { + exitCode = 1; + } freeVM(vm); free(source); - return 0; + return exitCode; } /* diff --git a/test/undefined_global.wren b/test/undefined_global.wren new file mode 100644 index 00000000..ff292ca7 --- /dev/null +++ b/test/undefined_global.wren @@ -0,0 +1 @@ +io.write(notDefined) // expect error diff --git a/test/undefined_local.wren b/test/undefined_local.wren new file mode 100644 index 00000000..6f60f27e --- /dev/null +++ b/test/undefined_local.wren @@ -0,0 +1,3 @@ +{ + io.write(notDefined) // expect error +}