terminal based frontend for the mobdebug lua remote debugger
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

378 lines
8.7 KiB

  1. --[[
  2. complete lua 5.3 syntax
  3. chunk ::= block
  4. block ::= {stat} [retstat]
  5. stat ::= ';' |
  6. varlist '=' explist |
  7. functioncall |
  8. label |
  9. 'break' |
  10. 'goto' Name |
  11. 'do' block 'end' |
  12. 'while' exp 'do' block 'end' |
  13. 'repeat' block 'until' exp |
  14. 'if' exp 'then' block {'elseif' exp 'then' block} ['else' block] 'end' |
  15. 'for' Name '=' exp ',' exp [',' exp] 'do' block 'end' |
  16. 'for' namelist 'in' explist 'do' block 'end' |
  17. 'function' funcname funcbody |
  18. 'local' 'function' Name funcbody |
  19. 'local' namelist ['=' explist]
  20. retstat ::= 'return' [explist] [';']
  21. label ::= '::' Name '::'
  22. funcname ::= Name {'.' Name} [':' Name]
  23. varlist ::= var {',' var}
  24. var ::= Name | prefixexp '[' exp ']' | prefixexp '.' Name
  25. namelist ::= Name {',' Name}
  26. explist ::= exp {',' exp}
  27. exp ::= 'nil' | 'false' | 'true' | Numeral | LiteralString | '...' | functiondef |
  28. prefixexp | tableconstructor | exp binop exp | unop exp
  29. prefixexp ::= var | functioncall | '(' exp ')'
  30. functioncall ::= prefixexp args | prefixexp ':' Name args
  31. args ::= '(' [explist] ')' | tableconstructor | LiteralString
  32. functiondef ::= 'function' funcbody
  33. funcbody ::= '(' [parlist] ')' block 'end'
  34. parlist ::= namelist [',' '...'] | '...'
  35. tableconstructor ::= '{' [fieldlist] '}'
  36. fieldlist ::= field {fieldsep field} [fieldsep]
  37. field ::= '[' exp ']' '=' exp | Name '=' exp | exp
  38. fieldsep ::= ',' | ';'
  39. binop ::= '+' | '-' | '*' | '/' | '//' | '^' | '%' |
  40. '&' | '~' | '|' | '>>' | '<<' | '..' |
  41. '<' | '<=' | '>' | '>=' | '==' | '~=' |
  42. 'and' | 'or'
  43. unop ::= '-' | 'not' | '#' | '~'
  44. --]]
  45. ---------- lua lexer ---------------------------------------------------
  46. local function mkset(t)
  47. local r = {}
  48. for _, v in ipairs(t) do r[v] = true end
  49. return r
  50. end
  51. local keywords = mkset { 'break', 'goto', 'do', 'end', 'while', 'repeat',
  52. 'until', 'if', 'then', 'elseif', 'else', 'for', 'function', 'local',
  53. 'return' }
  54. local binop = mkset { '+', '-', '*', '/', '//', '^', '%', '&', '~', '|',
  55. '>>', '<<', '..', '<', '<=', '>', '>=', '==', '~=', 'and', 'or' }
  56. local unop = mkset { '-', 'not', '#', '~' }
  57. local val = mkset { 'nil', 'true', 'false' } -- , number, string
  58. local other = mkset { '=', ':', ';', ',', '.', '[', ']', '(', ')', '{', '}',
  59. '...', '::' }
  60. local find = string.find
  61. local function lex_space(str, pos)
  62. local s, e = find(str, "^%s+", pos)
  63. if not s then return nil end
  64. return "spc", pos, e
  65. end
  66. local function lex_longstr(str, pos)
  67. local s, e = find(str, "^%[=*%[", pos)
  68. if not s then return nil end
  69. local ce = "]" .. string.rep('=', e-s-1) .. "]"
  70. s, e = find(str, ce, e+1, true)
  71. if not s then return nil, "unfinished string" end
  72. return "str", pos, e
  73. end
  74. function lex_shortstr(str, pos)
  75. local s, e = find(str, '^["\']', pos)
  76. if not s then return nil end
  77. local ch = string.sub(str, s, e)
  78. local srch = '[\\\\'..ch..']'
  79. repeat
  80. s, e = find(str, srch, e+1)
  81. if s then
  82. ch = string.sub(str, s, e)
  83. if ch == '\\' then e = e + 1 end
  84. end
  85. until not s or ch ~= '\\'
  86. if not s then return nil, "unfinished string" end
  87. return "str", pos, e
  88. end
  89. local function lex_name(str, pos)
  90. local s, e = find(str, "^[%a_][%w_]*", pos)
  91. if not s then return nil end
  92. local t = "name"
  93. local ss = string.sub(str, s, e)
  94. if keywords[ss] then
  95. t = "key"
  96. elseif unop[ss] or binop[ss] then
  97. t = "op"
  98. elseif val[ss] then
  99. t = "val"
  100. end
  101. return t, pos, e
  102. end
  103. local function lex_number(str, pos)
  104. local t = num
  105. local p = pos
  106. local s, e = find(str, "^%-?0[xX]", p)
  107. if s then
  108. p = e + 1
  109. s, e = find(str, "^%x+", p)
  110. if e then p = e + 1 end
  111. s, e = find(str, "^%.%x" .. (s and '*' or '+'), p)
  112. if e then p = e + 1 end
  113. s, e = find(str, "^[pP][+-]?%d+", p)
  114. if not e then e = p - 1 end
  115. if e == pos+1 then return nil, "malformed number" end
  116. else
  117. s, e = find(str, "^%-?%d+", p)
  118. if e then p = e + 1 end
  119. s, e = find(str, "^%.%d" .. (s and '*' or '+'), p)
  120. if e then p = e + 1 end
  121. s, e = find(str, "^[eE][+-]?%d+", p)
  122. if not e then e = p - 1 end
  123. if e < pos then return nil, "malformed number" end
  124. end
  125. return "num", pos, e
  126. end
  127. local function lex_comment(str, pos)
  128. local s, e = find(str, "^%-%-", pos)
  129. local t
  130. if not s then return nil end
  131. t, s, e = lex_longstr(str, pos+2)
  132. if not s then
  133. s, e = find(str, "^--[^\n]*\n", pos)
  134. e = e - 1
  135. elseif not t then
  136. return nil, "unfinished comment"
  137. end
  138. return "com", pos, e
  139. end
  140. local function lex_op(str, pos)
  141. local s, e = find(str, "^[/<>=~.]+", pos)
  142. if not s then s, e = find(str, "^[+%%-*^%&~|#]", pos) end
  143. if not s then return nil end
  144. local op = string.sub(str, s, e)
  145. if binop[op] or unop[op] then
  146. return "op", s, e
  147. end
  148. return nil
  149. end
  150. local function lex_other(str, pos)
  151. local s, e = find(str, "^[=:.]+", pos)
  152. if not s then s, e = find(str, "^[;,%[%](){}]", pos) end
  153. if not s then return nil end
  154. local op = string.sub(str, s, e)
  155. if other[op] then
  156. return "other", s, e
  157. end
  158. return nil
  159. end
  160. local function lualexer(str, skipws)
  161. local cr = coroutine.create(function()
  162. local pos = 1
  163. local line, col = 1, 1
  164. local ch, t, s, e, l, c
  165. -- skip initial #! if present
  166. s, e = string.find(str, "^#![^\n]*\n")
  167. if s then
  168. line = 2
  169. pos = e + 1
  170. end
  171. while pos <= #str do
  172. ch = string.sub(str, pos, pos)
  173. if ch == '-' then
  174. t, s, e = lex_comment(str, pos)
  175. if not t then
  176. t, s, e = lex_number(str, pos)
  177. end
  178. if not t then
  179. t, s, e = lex_op(str, pos)
  180. end
  181. elseif ch == "[" then
  182. t, s, e = lex_longstr(str, pos)
  183. if not t then
  184. t, s, e = lex_other(str, pos)
  185. end
  186. elseif ch == "'" or ch == '"' then
  187. t, s, e = lex_shortstr(str, pos)
  188. elseif find(ch, "[%a_]") then
  189. t, s, e = lex_name(str, pos)
  190. elseif find(ch, "%d") then
  191. t, s, e = lex_number(str, pos)
  192. elseif find(ch, "%p") then
  193. t, s, e = lex_number(str, pos)
  194. if not t then
  195. t, s, e = lex_op(str, pos)
  196. end
  197. if not t then
  198. t, s, e = lex_other(str, pos)
  199. end
  200. else
  201. t, s, e = lex_space(str, pos)
  202. end
  203. l, c = line, col
  204. if t then
  205. local s1 = string.find(str, "\n", s)
  206. while s1 and s1 <= e do
  207. col = 1
  208. line = line + 1
  209. s = s1 + 1
  210. s1 = string.find(str, "\n", s)
  211. end
  212. col = col + (s > e and 0 or e - s + 1)
  213. else
  214. col = col + 1
  215. end
  216. if t and (not skipws or t ~= "spc") then
  217. coroutine.yield(t, pos, e, l, c)
  218. elseif not t then
  219. s = s or "invalid token"
  220. coroutine.yield('err', s .. " in line " .. l .. " char " .. c)
  221. e = pos
  222. end
  223. pos = e + 1
  224. end
  225. return nil
  226. end)
  227. return function()
  228. local ok, t, s, e, l, c = coroutine.resume(cr)
  229. if ok then
  230. return t, s, e, l, c
  231. end
  232. return nil, t
  233. end
  234. end
  235. ---------- end of lua lexer --------------------------------------------
  236. local function expand_tabs(txt, tw)
  237. tw = tw or 4
  238. local tbl = {}
  239. local pos = 1
  240. local w = 0
  241. local s, e = string.find(txt, "^[^\t]*\t", 1)
  242. while s do
  243. tbl[#tbl+1] = string.sub(txt, s, e-1)
  244. w = w + e - s
  245. tbl[#tbl+1] = string.rep(' ', tw - w % tw)
  246. w = w + tw - w % tw
  247. pos = e + 1
  248. s, e = string.find(txt, "^[^\t]*\t", e + 1)
  249. end
  250. tbl[#tbl+1] = string.sub(txt, pos)
  251. return table.concat(tbl)
  252. end
  253. -- this should somehow also catch lines with only a [local] function(...)
  254. local function breakable(t, src, s, e)
  255. if t == "com" or t == "spc" then
  256. return false
  257. elseif t == 'key' then
  258. local what = string.sub(src, s, e)
  259. if what == 'end' then
  260. return false
  261. end
  262. elseif t == 'other' then
  263. local what = string.sub(src, s, e)
  264. if what == ')' or what == '}' or what == ']' then
  265. return false
  266. end
  267. end
  268. return true
  269. end
  270. -- very simple for the time being: we consider every line that has a
  271. -- token other than com or spc or the keyword 'end' breakable.
  272. local function lualoader(file)
  273. local srct = {}
  274. local canbrk = {}
  275. if file then
  276. local f = io.open(file, "r")
  277. if not f then
  278. return nil, "could not load source file "..file
  279. end
  280. local src = f:read("*a")
  281. f:close()
  282. local tokens = lualexer(src)
  283. for t, s, e, l, c in tokens do
  284. if t == "err" then
  285. return nil, "Error: "..s
  286. elseif breakable(t, src, s, e) then
  287. canbrk[l] = true
  288. end
  289. end
  290. if string.sub(src, #src, 1) ~= "\n" then
  291. src = src .. "\n"
  292. end
  293. string.gsub(src, "([^\r\n]*)\r?\n", function(s) table.insert(srct, s) end)
  294. for i = 1, #srct do
  295. srct[i] = expand_tabs(srct[i])
  296. end
  297. end
  298. return { src = srct, lines = #srct, canbrk = canbrk, breakpts = {}, selected = 0 }
  299. end
  300. --[[ DEBUG
  301. local file = io.stdin
  302. if arg[1] then
  303. file = io.open(arg[1], "r")
  304. if not file then print("could not open file " .. file) os.exit(1) end
  305. end
  306. local line
  307. repeat
  308. io.stdout:write("> ")
  309. line = file:read()
  310. local tokens = lualexer(line)
  311. for t, s, e, l, c in tokens do
  312. print(t, s, e, l, c)
  313. end
  314. until not line or line == ""
  315. -- DEBUG ]]
  316. return {
  317. lualoader = lualoader,
  318. lualexer = lualexer
  319. }