--- simple s expressions parser local parse, parse_exp, parse_atom -- expression: starts with ( and ends with ), contains a whietspace separated list of expressions and tokens -- s has no leading whitespace, starts with ( -- returns exp, r (r has no leading whitespace) -- returns nil, err parse_exp = function(s) local r = s:match("^%(%s*(.*)$") if not r then return nil, "no expression found" end local exp = {} repeat local item, r_item = parse(r) if item then table.insert(exp, item) r = r_item end until not item if not r:match("^%)") then return nil, "expected closing )" end return exp, r:match("^%)%s*(.-)$") end -- atom: litteral delimited by whitespace, ), or (; and with escaping using \ -- s has no leading whitespace -- returns exp, r (r has no leading whitespace) -- returns nil, err parse_atom = function(s) local atom = {} local n, r = s:match("^([^%s%(%)\\]*)(.-)$") if #n > 0 then table.insert(atom, n) end while r:match("^\\") do table.insert(atom, r:match("^\\(.)")) n, r = r:match("^\\.([^%s%(%)\\]*)(.-)$") if #n > 0 then table.insert(atom, n) end end if #atom == 0 then return nil, "no atom found" end return table.concat(atom), r:match("^%s*(.-)$") end -- s has no leading whitespace -- returns exp, r (r has no leading whitespace) -- returns nil, err parse = function(s) local i, r = parse_exp(s) if i then return i, r end i, r = parse_atom(s) if i then return i, r end return nil, "no expression found" end local function test(s) local trimmed = s:match("^%s*(.-)$") local parsed, r = parse(s) if not parsed then print(r) elseif r:match(".") then print(("unexpected %q at end of expression"):format(r)) else print(require("inspect")(parsed)) end end test("((str (Hel\\)lo world) sa mère) (lol))") test("\\lol\\ wut") test("()")