1- type Result = i32 ;
1+ type ApplyResult = bool ;
22
33declare function fillInputBuffer ( offset : i32 , maxLen : i32 ) : i32 ;
44declare function printI32 ( val : i32 ) : void ;
@@ -13,27 +13,57 @@ declare function isRuleSyntactic(ruleId: i32): bool;
1313@inline const STACK_START_OFFSET : usize = WASM_PAGE_SIZE ;
1414@inline const MAX_INPUT_LEN_BYTES : usize = 64 * 1024 ;
1515
16- // Note: the rule evaluation functions use a different representation.
17- // They return non-zero for success and zero for failure.
18- @inline const EMPTY : Result = 0 ;
19- @inline const FAIL : Result = 0xfffffff0 ;
20- @inline const UNUSED_LR_BOMB : Result = FAIL | 0x1 ;
21- @inline const USED_LR_BOMB : Result = FAIL | 0x3 ;
22-
23- @inline const CST_NODE_OVERHEAD : usize = 12 ;
16+ // CST nodes
17+ @inline const CST_NODE_OVERHEAD : usize = 16 ;
18+ @inline const NODE_TYPE_TERMINAL : i32 = - 1 ;
2419@inline const NODE_TYPE_ITERATION : i32 = - 2 ;
2520
21+ // Memo table entries
22+ type MemoEntry = i32 ;
23+
24+ @inline const EMPTY : MemoEntry = 0 ;
25+
26+ // Low bit: failure flag.
27+ // Rest: failurePos (signed int, 31 bits).
28+ @inline const MEMO_FAILURE_FLAG : MemoEntry = 0x1 ;
29+
30+ // Not: left recursion bombs never include failurePos.
31+ // We need to be careful that a true failure w/ failurePos can't produce
32+ // the same value. Because failurePos >= -1, we can use -2 and -3.
33+ // TODO: Use failureOffset (unsigned) instead? That's what we do in JS.
34+ @inline const UNUSED_LR_BOMB : MemoEntry = ( - 2 << 1 ) | MEMO_FAILURE_FLAG ;
35+ @inline const USED_LR_BOMB : MemoEntry = ( - 3 << 1 ) | MEMO_FAILURE_FLAG
36+
37+ // The result of a raw rule evaluation function.
38+ // Low bit: RULE_EVAL_SUCCESS_FLAG
39+ // Rest: failurePos (signed int, 31 bits).
40+ type RuleEvalResult = i32 ;
41+
42+ @inline const RULE_EVAL_SUCCESS_FLAG = 1 ;
43+
2644// Shared globals
2745let pos : i32 = 0 ;
46+
47+ // The rightmost position at which a leaf (Terminal, etc.) failed to match.
48+ let rightmostFailurePos : i32 = 0 ;
49+
2850let sp : usize = 0 ;
2951let bindings : Array < i32 > = new Array < i32 > ( ) ;
3052
31- @inline function memoTableGet ( memoPos : usize , ruleId : i32 ) : Result {
32- return load < Result > ( memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof < Result > ( ) , MEMO_START_OFFSET ) ;
53+ @inline function max < T > ( a : T , b : T ) : T {
54+ return a > b ? a : b ;
55+ }
56+
57+ @inline function memoEntryForFailure ( failurePos : i32 ) : MemoEntry {
58+ return ( failurePos << 1 ) | MEMO_FAILURE_FLAG ;
59+ }
60+
61+ @inline function memoTableGet ( memoPos : usize , ruleId : i32 ) : MemoEntry {
62+ return load < MemoEntry > ( memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof < MemoEntry > ( ) , MEMO_START_OFFSET ) ;
3363}
3464
35- @inline function memoTableSet ( memoPos : usize , ruleId : i32 , value : Result ) : void {
36- store < Result > ( memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof < Result > ( ) , value , MEMO_START_OFFSET ) ;
65+ @inline function memoTableSet ( memoPos : usize , ruleId : i32 , value : MemoEntry ) : void {
66+ store < MemoEntry > ( memoPos * MEMO_COL_SIZE_BYTES + ruleId * sizeof < MemoEntry > ( ) , value , MEMO_START_OFFSET ) ;
3767}
3868
3969@inline function cstGetCount ( ptr : usize ) : i32 {
@@ -60,28 +90,27 @@ let bindings: Array<i32> = new Array<i32>();
6090 store < i32 > ( ptr , t , 8 ) ;
6191}
6292
63- @inline function memoizeResult ( memoPos : usize , ruleId : i32 , result : Result ) : void {
64- memoTableSet ( memoPos , ruleId , result ) ;
93+ @inline function cstGetFailurePos ( ptr : usize ) : i32 {
94+ return load < i32 > ( ptr , 12 ) ;
6595}
6696
67- @inline function isFailure ( result : Result ) : bool {
68- return result < 0 ;
97+ @inline function cstSetFailurePos ( ptr : usize , pos : i32 ) : void {
98+ store < i32 > ( ptr , pos , 12 ) ;
6999}
70100
71- function useMemoizedResult ( ruleId : i32 , result : Result ) : Result {
72- if ( result === UNUSED_LR_BOMB ) {
73- memoTableSet ( pos , ruleId , USED_LR_BOMB ) ;
74- return 0 ;
75- } else if ( isFailure ( result ) ) {
76- return 0 ;
101+ function useMemoizedResult ( ruleId : i32 , result : MemoEntry ) : ApplyResult {
102+ if ( result & MEMO_FAILURE_FLAG ) {
103+ if ( result === UNUSED_LR_BOMB ) {
104+ memoTableSet ( pos , ruleId , USED_LR_BOMB ) ;
105+ } else {
106+ rightmostFailurePos = max ( rightmostFailurePos , result >> 1 ) ;
107+ }
108+ return false ;
77109 }
78110 pos += cstGetMatchLength ( result ) ;
111+ rightmostFailurePos = max ( rightmostFailurePos , cstGetFailurePos ( result ) ) ;
79112 bindings . push ( result ) ;
80- return result ;
81- }
82-
83- function hasMemoizedResult ( ruleId : i32 ) : boolean {
84- return memoTableGet ( pos , ruleId ) !== 0 ;
113+ return true ;
85114}
86115
87116@inline function maybeSkipSpaces ( ruleId : i32 ) : void {
@@ -93,12 +122,18 @@ function hasMemoizedResult(ruleId: i32): boolean {
93122 }
94123}
95124
96- export function match ( startRuleId : i32 ) : Result {
97- // (Re-)initialize globals, clear memo table.
125+ function resetParsingState ( ) : void {
98126 pos = 0 ;
127+ rightmostFailurePos = - 1 ;
99128 sp = STACK_START_OFFSET ;
129+ heap . reset ( ) ;
130+
100131 bindings = new Array < i32 > ( ) ;
101132 memory . fill ( MEMO_START_OFFSET , 0 , MEMO_COL_SIZE_BYTES * MAX_INPUT_LEN_BYTES ) ;
133+ }
134+
135+ export function match ( startRuleId : i32 ) : ApplyResult {
136+ resetParsingState ( ) ;
102137
103138 // Get the input and do the match.
104139 let inputLen = fillInputBuffer ( 0 , i32 ( WASM_PAGE_SIZE ) ) ;
@@ -107,98 +142,125 @@ export function match(startRuleId: i32): Result {
107142 const succeeded = evalApply0 ( startRuleId ) !== 0 ;
108143 if ( succeeded ) {
109144 maybeSkipSpaces ( startRuleId ) ;
145+ // printI32(heap.alloc(8) - __heap_base); // Print heap usage.
146+ // TODO: Do we need to update rightmostFailurePos here?
110147 return inputLen === pos ;
111148 }
112- return 0 ;
149+
150+ return false ;
113151}
114152
115- @inline function evalRuleBody ( ruleId : i32 ) : Result {
116- return call_indirect < Result > ( ruleId ) ;
153+ @inline function evalRuleBody ( ruleId : i32 ) : RuleEvalResult {
154+ return call_indirect < RuleEvalResult > ( ruleId ) ;
117155}
118156
119- export function evalApplyGeneralized ( ruleId : i32 , caseIdx : i32 ) : Result {
157+ // Extracts the local failure position from a RuleEvalResult.
158+ // If it's greater than the global rightmostFailurePos, it updates it.
159+ // Returns the local failure position.
160+ @inline function maybeUpdateRightmostFailurePos ( result : RuleEvalResult ) : i32 {
161+ const failurePos = result >> 1 ;
162+ rightmostFailurePos = max ( rightmostFailurePos , failurePos ) ;
163+ return failurePos ;
164+ }
165+
166+ // Evaluates a generalized rule. Identical to evalApplyNoMemo0, but includes
167+ // the caseIdx.
168+ export function evalApplyGeneralized ( ruleId : i32 , caseIdx : i32 ) : ApplyResult {
120169 const origPos = pos ;
121170 const origNumBindings = bindings . length ;
122- if ( call_indirect < Result > ( ruleId , caseIdx ) ) {
123- return newNonterminalNode ( origPos , pos , ruleId , origNumBindings ) ;
171+ const result = call_indirect < RuleEvalResult > ( ruleId , caseIdx )
172+ const failurePos = maybeUpdateRightmostFailurePos ( result ) ;
173+ if ( result & RULE_EVAL_SUCCESS_FLAG ) {
174+ newNonterminalNode ( origPos , pos , ruleId , origNumBindings , failurePos ) ;
175+ return true ;
124176 }
125- return 0 ;
177+ return false ;
126178}
127179
128- export function evalApplyNoMemo0 ( ruleId : i32 ) : Result {
180+ export function evalApplyNoMemo0 ( ruleId : i32 ) : ApplyResult {
129181 const origPos = pos ;
130182 const origNumBindings = bindings . length ;
131- if ( evalRuleBody ( ruleId ) ) {
132- return newNonterminalNode ( origPos , pos , ruleId , origNumBindings ) ;
183+ let result = evalRuleBody ( ruleId ) ;
184+ const failurePos = maybeUpdateRightmostFailurePos ( result ) ;
185+ if ( result & RULE_EVAL_SUCCESS_FLAG ) {
186+ newNonterminalNode ( origPos , pos , ruleId , origNumBindings , failurePos ) ;
187+ return true ;
133188 }
134- return 0 ;
189+ return false ;
135190}
136191
137- export function evalApply0 ( ruleId : i32 ) : Result {
138- let result = memoTableGet ( pos , ruleId ) ;
139- if ( result !== 0 ) {
140- return useMemoizedResult ( ruleId , result ) ;
192+ export function evalApply0 ( ruleId : i32 ) : ApplyResult {
193+ const memo = memoTableGet ( pos , ruleId ) ;
194+ if ( memo !== 0 ) {
195+ return useMemoizedResult ( ruleId , memo ) ;
141196 }
142197 const origPos = pos ;
143- let origNumBindings = bindings . length ;
144- memoizeResult ( origPos , ruleId , UNUSED_LR_BOMB ) ;
145- let succeeded : i32 = evalRuleBody ( ruleId ) ;
198+ const origNumBindings = bindings . length ;
199+ memoTableSet ( origPos , ruleId , UNUSED_LR_BOMB ) ;
200+
201+ const result = evalRuleBody ( ruleId ) ;
202+ const failurePos = maybeUpdateRightmostFailurePos ( result ) ;
146203
147204 // Straight failure — record a clean failure in the memo table.
148- if ( ! succeeded ) {
149- memoizeResult ( origPos , ruleId , FAIL ) ;
150- return 0 ;
205+ if ( ( result & RULE_EVAL_SUCCESS_FLAG ) == 0 ) {
206+ memoTableSet ( origPos , ruleId , memoEntryForFailure ( failurePos ) ) ;
207+ return false ;
151208 }
152209
153210 if ( memoTableGet ( origPos , ruleId ) === USED_LR_BOMB ) {
154- return handleLeftRecursion ( origPos , ruleId , origNumBindings ) ;
211+ return handleLeftRecursion ( origPos , ruleId , origNumBindings , failurePos ) ;
155212 }
156-
157213 // No left recursion — memoize and return.
158- result = newNonterminalNode ( origPos , pos , ruleId , origNumBindings ) ;
159- memoizeResult ( origPos , ruleId , result ) ;
160- return result ;
214+ const node = newNonterminalNode ( origPos , pos , ruleId , origNumBindings , failurePos ) ;
215+ memoTableSet ( origPos , ruleId , < MemoEntry > node ) ;
216+ return true ;
161217}
162218
163- export function handleLeftRecursion ( origPos : usize , ruleId : i32 , origNumBindings : i32 ) : Result {
219+ export function handleLeftRecursion ( origPos : usize , ruleId : i32 , origNumBindings : i32 , failurePos : i32 ) : ApplyResult {
164220 let maxPos : i32 ;
165- let result : Result ;
166- let succeeded : i32 ;
221+ let node : usize ;
222+ let succeeded : bool ;
167223 do {
168224 // The current result is the best one -- record it.
169225 maxPos = pos ;
170- result = newNonterminalNode ( origPos , pos , ruleId , origNumBindings ) ;
171- memoizeResult ( origPos , ruleId , result ) ;
226+ rightmostFailurePos = max ( rightmostFailurePos , failurePos ) ;
227+ node = newNonterminalNode ( origPos , pos , ruleId , origNumBindings , failurePos ) ;
228+ memoTableSet ( origPos , ruleId , < MemoEntry > node ) ;
172229
173230 // Reset and try to improve on the current best.
174231 pos = origPos ;
175232 bindings . length = origNumBindings ;
176- succeeded = evalRuleBody ( ruleId ) ;
233+ const result = evalRuleBody ( ruleId ) ;
234+ succeeded = ( result & RULE_EVAL_SUCCESS_FLAG ) != 0 ;
235+ failurePos = result >> 1 ;
177236 } while ( succeeded && pos > maxPos ) ;
178237
179238 pos = maxPos ;
239+
180240 bindings . length = origNumBindings + 1 ;
181- bindings [ origNumBindings ] = result ;
241+ bindings [ origNumBindings ] = node ;
182242 return succeeded ;
183243}
184244
185245export function newTerminalNode ( startIdx : i32 , endIdx : i32 ) : usize {
186246 const ptr = heap . alloc ( CST_NODE_OVERHEAD ) ;
187247 cstSetCount ( ptr , 0 ) ;
188248 cstSetMatchLength ( ptr , endIdx - startIdx ) ;
189- cstSetType ( ptr , - 1 ) ;
249+ cstSetType ( ptr , NODE_TYPE_TERMINAL ) ;
250+ cstSetFailurePos ( ptr , 0 ) ;
190251 bindings . push ( ptr ) ;
191252 return ptr ;
192253}
193254
194255// Create an internal (non-leaf) node (IterationNode or NonterminalNode).
195- @inline function newNonLeafNodeWithType ( startIdx : i32 , endIdx : i32 , type : i32 , origNumBindings : i32 ) : usize {
256+ @inline function newNonLeafNode ( startIdx : i32 , endIdx : i32 , type : i32 , origNumBindings : i32 , failurePos : i32 ) : usize {
196257 const bindingsLen = bindings . length ;
197258 const numChildren = bindingsLen - origNumBindings ;
198259 const ptr = heap . alloc ( CST_NODE_OVERHEAD + numChildren * 4 ) ;
199260 cstSetCount ( ptr , numChildren ) ;
200261 cstSetMatchLength ( ptr , endIdx - startIdx ) ;
201262 cstSetType ( ptr , type ) ;
263+ cstSetFailurePos ( ptr , failurePos ) ;
202264 for ( let i = 0 ; i < numChildren ; i ++ ) {
203265 store < i32 > ( ptr + CST_NODE_OVERHEAD + i * 4 , bindings [ bindingsLen - numChildren + i ] ) ;
204266 }
@@ -207,12 +269,12 @@ export function newTerminalNode(startIdx: i32, endIdx: i32): usize {
207269 return ptr ;
208270}
209271
210- export function newNonterminalNode ( startIdx : i32 , endIdx : i32 , ruleId : i32 , origNumBindings : i32 ) : usize {
211- return newNonLeafNodeWithType ( startIdx , endIdx , ruleId , origNumBindings ) ;
272+ export function newNonterminalNode ( startIdx : i32 , endIdx : i32 , ruleId : i32 , origNumBindings : i32 , failurePos : i32 ) : usize {
273+ return newNonLeafNode ( startIdx , endIdx , ruleId , origNumBindings , failurePos ) ;
212274}
213275
214276export function newIterationNode ( startIdx : i32 , endIdx : i32 , origNumBindings : i32 ) : usize {
215- return newNonLeafNodeWithType ( startIdx , endIdx , NODE_TYPE_ITERATION , origNumBindings ) ;
277+ return newNonLeafNode ( startIdx , endIdx , NODE_TYPE_ITERATION , origNumBindings , - 1 ) ;
216278}
217279
218280export function getBindingsLength ( ) : i32 {
0 commit comments