(defun parse (str)
"Parse STR as a Javascript script, returning a list of statements.
Semicolon insertion is performed according to the ECMA-262 standard."
(let ((lexer (make-instance 'javascript-lexer :text str)))
(labels ((resignal (err)
(let ((pos (token-start (yacc:yacc-parse-error-value err))))
(destructuring-bind (row . col) (position-to-line/column
str
pos)
(error (make-condition 'syntax-error
:token (yacc:yacc-parse-error-value err)
:expected-terminals (yacc:yacc-parse-error-expected-terminals err)
:pos pos :row row :column col)))))
(handle-yacc-error (err)
(cond
;; Irritating regular-expression-literal vs. division ambiguity case.
;; If we encounter an unexpected RE literal, try interpreting it as a
;; division operator instead. We do that by rewinding the lexer to
;; just before the RE literal and instructing it to read the slash as
;; just a slash. We then instruct the parser to throw away the RE
;; literal and continue parsing.
((and (eq :re-literal (yacc:yacc-parse-error-terminal err))
(find :slash (yacc:yacc-parse-error-expected-terminals err)))
(set-cursor lexer (token-start (yacc:yacc-parse-error-value err)))
(coerce-token lexer :slash)
(invoke-restart 'yacc:skip-terminal))
;; Don't try to perform semicolon insertion unless inserted-semicolons are permitted
((null (find :inserted-semicolon (yacc:yacc-parse-error-expected-terminals err)))
(resignal err))
;; Semicolon-insertion case
((or (encountered-line-terminator lexer)
(eq :right-curly (yacc:yacc-parse-error-terminal err))
(eq 'yacc:yacc-eof-symbol (yacc:yacc-parse-error-terminal err)))
(invoke-restart 'yacc:insert-terminal :inserted-semicolon
#s(token :terminal :inserted-semicolon :value ";")))
;; Resignal as a jwacs error if we don't handle the yacc error
(t (resignal err)))))
(handler-bind ((yacc:yacc-parse-error #'handle-yacc-error))
(yacc:parse-with-lexer (make-lexer-function lexer) javascript-script)))))