fisl

fisl is scheme lox
git clone git://git.alexkarle.com.com/fisl
Log | Files | Refs | README | LICENSE

commit 7dc01d44885e76321bcbd135e03d3e8f84cf8c30 (patch)
parent 95960e0a1c88238e4202f22518fc231ff8333318
Author: Alex Karle <alex@alexkarle.com>
Date:   Wed,  9 Nov 2022 13:09:59 -0500

parser: Add synchronization at declaration level

Before the parser stopped entirely at the first parse error:

$ cat examples/sync.lox
print 1 + for;
print 2 + 2;
print 1 + this;

$ ./fisl.scm examples/sync.lox
examples/sync.lox:1:Error at for. Unknown token

Now it synchronizes by going to the next statement and finds all parse
errors (but does NOT execute them):

$ ./fisl.scm examples/sync.lox
examples/sync.lox:1:Error at for. Unknown token
examples/sync.lox:3:Error at this. Unknown token

Diffstat:
Aexamples/sync.lox | 3+++
Mfisl.scm | 5+++--
Mparser.scm | 55++++++++++++++++++++++++++++++++-----------------------
3 files changed, 38 insertions(+), 25 deletions(-)

diff --git a/examples/sync.lox b/examples/sync.lox @@ -0,0 +1,3 @@ +print 1 + for; +print 2 + 2; +print 1 + this; diff --git a/fisl.scm b/fisl.scm @@ -18,8 +18,9 @@ (let ((tokens (scan code))) (if tokens (let ((stmts (parse tokens))) - (if stmts - (interpret stmts)))))) + (unless had-err + (if stmts + (interpret stmts))))))) (define (prompt) ;; HACK: srfi-18 blocks for IO, so having run-prompt diff --git a/parser.scm b/parser.scm @@ -1,7 +1,7 @@ ;; parser.scm -- parser routines (import (chicken format)) -(define parser-abort #f) +(define parser-sync #f) ;; EXPRESSIONS @@ -62,7 +62,6 @@ (define (parse-declaration tokens) (if (top-type? tokens '(VAR)) - ;; TODO: sync on failure (parse-var-decl (cdr tokens)) (parse-statement tokens))) @@ -74,8 +73,8 @@ (values '() (cdr tokens))))) (if (top-type? toks '(SEMICOLON)) (values (make-var-stmt (car tokens) init) (cdr toks)) - (parse-err! (car toks) "Expected ';' after variable declaration"))) - (parse-err! (car tokens) "expected variable name"))) + (parse-err! toks "Expected ';' after variable declaration"))) + (parse-err! tokens "expected variable name"))) (define (parse-statement tokens) (if (top-type? tokens '(PRINT)) @@ -89,7 +88,7 @@ (values (maker expr) (cdr toks)) (if in-repl (values (maker expr) toks) - (parse-err! (car toks) "expected ;"))))) + (parse-err! toks "expected ;"))))) (define (parse-print-statement tokens) (parse-generic-stmt tokens make-print-stmt)) @@ -154,25 +153,35 @@ (let-values (((e2 t2) (parse-expression expr rest))) (if (top-type? t2 '(RIGHT_PAREN)) (values (make-grouping e2) (cdr t2)) - (parse-err! (car t2) "Expected ')'")))) - (else (parse-err! (car toks) "Unknown token"))))) + (parse-err! t2 "Expected ')'")))) + (else (parse-err! toks "Unknown token"))))) -(define (parse-err! tok msg) - (if (eq? (token-type tok) 'EOF) - (fname-err! (format "~A:~A ~A" (token-line tok) "Error at end." msg)) +(define (parse-err! toks msg) + (let ((top (car toks))) + (if (top-type? toks '(EOF)) + (fname-err! (format "~A:~A ~A" (token-line top) "Error at end." msg)) (fname-err! (format "~A:~A ~A. ~A" - (token-line tok) - "Error at" - (token-lexeme tok) - msg))) - ;; TODO: synchronize instead of abort - (parser-abort #f)) + (token-line top) + "Error at" + (token-lexeme top) + msg))) + (let ((t2 (synchronize (cdr toks)))) + (parser-sync t2)))) + +;; Given a list of tokens, returns the next statement (best guess based +;; on keyword being a statement keyword OR seeing a semicolon) +(define (synchronize tokens) + (cond + ((null? tokens) '()) + ((top-type? tokens '(SEMICOLON)) (cdr tokens)) + ((top-type? tokens '(CLASS FUN VAR FOR IF WHILE PRINT RETURN)) tokens) + (else (synchronize (cdr tokens))))) (define (parse tokens) - (call/cc (lambda (cc) - (set! parser-abort cc) - (let loop ((toks tokens)) - (if (not (top-type? toks '(EOF))) - (let-values (((expr rest) (parse-declaration toks))) - (cons expr (loop rest))) - '()))))) + ;; Loop through declarations, starting with tokens BUT using call/cc + ;; to bookmark the loop so we can synchronize on parse-err! + (let loop ((toks (call/cc (lambda (cc) (set! parser-sync cc) tokens)))) + (if (and toks (not (top-type? toks '(EOF)))) + (let-values (((expr rest) (parse-declaration toks))) + (cons expr (loop rest))) + '())))