-
Notifications
You must be signed in to change notification settings - Fork 0
/
tinyrec.lua
183 lines (139 loc) · 5.5 KB
/
tinyrec.lua
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
local lpeg = require "lpeglabel"
local relabel = require "relabel"
--local lpeglabel = require "lpeglabel"
lpeg.locale(lpeg)
local P, V, C, Ct, R, S, B, Cmt = lpeg.P, lpeg.V, lpeg.C, lpeg.Ct, lpeg.R, lpeg.S, lpeg.B, lpeg.Cmt -- for lpeg
local T = lpeg.T -- lpeglabel
local space = lpeg.space
local alpha = lpeg.alpha
local Rec = lpeg.Rec
local Cp,Cc = lpeg.Cp,lpeg.Cc
local terror = {}
local vars = {}
local function newError(s)
table.insert(terror, s)
return #terror
end
-- error definitions
local errSemicolon = newError("Unexpected semicolon")
local errMissingSemicolon = newError("Missing semicolon")
local errInvalidStatement= newError("Invalid statement")
local errIfMissingThen= newError("Missing keyword 'then' in If statement")
local errIfMissingEnd= newError("Missing keyword 'end' in If statement")
local errRepeatMissingUntil= newError("Missing keyword 'until' in Repeat statement")
local errAssMissingExp= newError("Missing expression on RHS of assignment")
local errReadMissingId= newError("Missing Identifier for read operation")
local errWriteMissingExp= newError("Missing Expression for write operation")
local errCompMissingSExp= newError("Missing Simple Expression for comparison operation")
local errAddopMissingTerm= newError("Missing Term for add operation")
local errMulopMissingFactor= newError("Missing Factor for mul operation")
local errMissingClosingBracket= newError("Missing closing bracket")
local errExtra = newError("Extra characters after statement")
local errMissingExp = newError("Missing expression")
local subject, errors
function record(label)
return (Cp() * Cc(label)) / recorderror
end
function recorderror(position,label)
local line, col = relabel.calcline(subject, position)
local err = { line = line, col = col, label=label, msg = terror[label] }
table.insert(errors, err)
end
function sync (patt)
return (-patt * P(1))^0 -- skip until we find pattern
end
local Skip = (space)^0
function token (patt)
return patt * Skip
end
function sym (str)
return token(P(str))
end
function kw (str)
return token(P(str))
end
function try (patt, err)
return patt + T(err)
end
function throws(patt,err) -- if pattern is matched throw error
return patt * T(err)
end
--[[ todo
function expect (rule)
return V(rule) + T(getLabel(rule))
end
-]]--
local gram = P {
"program",
program = Skip * V "stmtsequence" * -1 + T(errExtra),
stmtsequence = V "statement" * (sym(";") * (V "eossemicolon" + V "statement") + throws(#V "firstTokens",errMissingSemicolon))^0,
eossemicolon = throws(-1,errSemicolon), -- semicolon at the end of the input
firstTokens = V "keywordsStart" + V "Identifier", -- for the missing semicolon test
keywords = V "keywordsStart" + V "keywordsRest",
keywordsStart = P "if" + P "repeat" + P "read" + P "write", -- keywords that appear in the beginning of a statement, necessary to check for missing semicolons
keywordsRest = P "then" + P "else" + P "end" + P "until",
statement = try(V "assignstmt" +V "ifstmt" + V "repeatstmt" + V "readstmt" + V "writestmt",errInvalidStatement), -- assignstmt is moved first to match "ifa:=4" instead of "if a (expect:then).."
ifstmt = kw("if") * try(V "exp",errMissingExp) * try(kw("then"),errIfMissingThen) * V "stmtsequence" * (kw("else") * V "stmtsequence")^-1 * try(kw("end"),errIfMissingEnd),
repeatstmt = kw("repeat") * V "stmtsequence" * try(kw("until"),errRepeatMissingUntil) * try(V "exp",errMissingExp),
assignstmt = V "Identifier" * sym(":=") * try(V "exp",errAssMissingExp),
readstmt = kw("read") * try(V "Identifier",errReadMissingId),
writestmt = kw("write") * try(V "exp",errWriteMissingExp),
exp = V "simpleexp" * (V "comparisonop" * try(V "simpleexp",errCompMissingSExp))^0,
comparisonop = sym("<") + sym("="),
simpleexp = V "term" * (V "addop" * try(V "term",errAddopMissingTerm))^0,
addop = sym("+") + sym("-"),
term = V "factor" * (V "mulop" * try(V "factor",errMulopMissingFactor))^0,
mulop = sym("*") + sym("/"),
factor = sym("(") * V "exp" * try(sym(")"),errMissingClosingBracket) + V "Number" + V "Identifier",
Number = token(P"-"^-1 * R("09")^1),
Identifier = token(alpha^1 - #V "Reserved"),
Reserved = V "keywords" * -alpha, -- ifabc is a valid identifier; if, if3 if. are not
}
local grec = gram -- start with grammar and build up
local synctoken = sync(sym(";"))
for k,v in pairs(terror) do
grec = Rec(grec,record(k) * synctoken, k)
end
function mymatch (s, g)
errors = {}
subject = s
local r, e, sfail = g:match(s)
if #errors > 0 then
local out = {}
for i, err in ipairs(errors) do
local msg = "Error at line " .. err.line .. " (col " .. err.col .. "): " .. err.msg
table.insert(out, msg)
end
return nil, table.concat(out, "\n") .. "\n"
end
return r
end
function tiny(str) -- to use from test file
errors = {}
subject = str
local r, e, sfail = grec:match(str)
return r,errors
end
if arg[1] then
-- argument must be in quotes if it contains spaces
print(mymatch(arg[1],grec));
end
local re = {
tiny = tiny,
errSemicolon = errSemicolon,
errMissingSemicolon = errMissingSemicolon,
errInvalidStatement = errInvalidStatement,
errIfMissingThen=errIfMissingThen,
errIfMissingEnd=errIfMissingEnd,
errRepeatMissingUntil=errRepeatMissingUntil,
errAssMissingExp=errAssMissingExp,
errReadMissingId=errReadMissingId,
errWriteMissingExp=errWriteMissingExp,
errCompMissingSExp=errCompMissingSExp,
errAddopMissingTerm=errAddopMissingTerm,
errMulopMissingFactor=errMulopMissingFactor,
errMissingClosingBracket=errMissingClosingBracket,
errExtra=errExtra,
errMissingExp=errMissingExp,
}
return re