1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-- Parse takes an action table t and computes the fixed point of this grammar + string
function parse(t)
if t:terminate() then return end
local peek = t.hash(string.char(t.stream:byte(t.at)))
t.at = t.at + 1
t[peek](t)
return t
end
-- Stack of characters consumed grouped by bracketing level
local stack = {0}
-- Maximum number of characters to parse before breaking
local max_char = 10
function push() table.insert(stack, 1) end
function pop() assert(#stack > 1, 'Dangling )'); table.remove(stack, #stack) end
function count() stack[#stack] = stack[#stack] + 1 end
trace = parse {
-- This is the string of character we want to parse
stream = "1234(xx()X(x))5678()90~~~~~~~",
-- At the end of the run, at will point to the end of the 10 unbracketed characters
at = 1,
-- This is a function that maps each character to an action label (so '(' = left, 'r' = right, etc)
hash = function(char) return ({['('] = 'left', [')'] = 'right', [''] = 'null'})[char] or 'character' end,
-- What to do when we encounter a normal character
character = function(t) count(); parse(t) end,
-- What to do when we encounter a left parenthesis
left = function(t) push(); parse(t) end,
-- What to do when we encounter a right parenthesis
right = function(t) pop(); parse(t) end,
-- What to do when we encounter the end of the stream
null = function(t) --[[Reduce stack]] assert(#stack == 1, 'Dangling (') end,
-- The condition for us to terminate
terminate = function(t) return stack[1] >= max_char end,
-- What was parsed/consumed in this trace
consumed = function(t) return t.stream:sub(1, trace.at - 1) end}
print(trace:consumed())