From 1c6516fe0160d2745f5e701cadd19d0c254a71e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 25 Feb 2019 18:17:33 +0100 Subject: [PATCH] _js: add a non-blocking wrapper of Interactive JS only has a single thread, meaning that if the interactive parser blocks on a call, the only way to run any other code in the meantime is to run it as part of the "read" call. This is understandably clunky, as the API was designed for concurrent languages. However, with about twenty lines of code we can offer an API that never blocks to JS, which is more usable for real programs. It does re-parse chunks of incomplete statements, but those shouldn't get very large. Adding a non-blocking Interactive variant to the original parser would be too much of a refactor, since it's a recursive descent parser. It can't be stopped and resumed, as the parse state is in the call stack. --- _js/main.go | 30 ++++++++++++++++++++++++++++++ _js/testmain.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/_js/main.go b/_js/main.go index ce6eabafb..5ea891d9a 100644 --- a/_js/main.go +++ b/_js/main.go @@ -116,6 +116,9 @@ func (r streamReader) Read(p []byte) (n int, err error) { type jsParser struct { syntax.Parser + + accumulated []*syntax.Stmt + incomplete bytes.Buffer } func adaptReader(src *js.Object) io.Reader { @@ -151,6 +154,33 @@ func (p *jsParser) Interactive(src *js.Object, jsFn func([]*js.Object) bool) { } } +func (p *jsParser) InteractiveStep(line string) []*syntax.Stmt { + // pick up previous chunks of the incomplete statement + r := strings.NewReader(p.incomplete.String() + line) + lastEnd := uint(0) + err := p.Parser.Interactive(r, func(stmts []*syntax.Stmt) bool { + if len(stmts) > 0 { + // don't re-parse finished statements + lastEnd = stmts[len(stmts)-1].End().Offset() + } + p.accumulated = append(p.accumulated, stmts...) + return false + }) + if syntax.IsIncomplete(err) { + // starting or continuing an incomplete statement + p.incomplete.WriteString(line[lastEnd:]) + return p.accumulated + } + // complete; empty both fields and return + p.incomplete.Reset() + if err != nil { + throw(err) + } + acc := p.accumulated + p.accumulated = p.accumulated[:0] + return acc +} + type jsPrinter struct { *syntax.Printer } diff --git a/_js/testmain.js b/_js/testmain.js index 07a1f1290..73088642d 100644 --- a/_js/testmain.js +++ b/_js/testmain.js @@ -174,6 +174,38 @@ const printer = syntax.NewPrinter() assert.deepEqual(gotCallbacks, wantCallbacks) } +{ + // using the parser interactively with steps + const lines = [ + "foo\n", + "bar; baz\n", + "\n", + "foo; 'incom\n", + " \n", + "plete'\n", + ] + const wantResults = [ + {"count": 1, "incomplete": false}, + {"count": 2, "incomplete": false}, + {"count": 0, "incomplete": false}, + {"count": 1, "incomplete": true}, + {"count": 1, "incomplete": true}, + {"count": 2, "incomplete": false}, + ] + var gotResults = [] + for (var i = 0; i < lines.length; i++) { + var line = lines[i] + var want = wantResults[i] + + var stmts = parser.InteractiveStep(line) + gotResults.push({ + "count": stmts.length, + "incomplete": parser.Incomplete(), + }) + } + assert.deepEqual(gotResults, wantResults) +} + { // splitting brace expressions const parser = syntax.NewParser()