Updated parser to avoid 'breaking' statements during incremental processing.

This commit is contained in:
Paul Kulchenko
2014-10-06 09:43:13 -07:00
parent 9edc54a019
commit 7a33783acf
3 changed files with 45 additions and 35 deletions

View File

@@ -40,42 +40,47 @@ function PARSE.parse_scope(lx, f, level)
local scopes = {{}}
for l = 2, (level or 1) do scopes[l] = {} end
local function scope_begin(opt, lineinfo)
local function scope_begin(opt, lineinfo, nobreak)
scopes[#scopes+1] = {}
f('Scope', opt, lineinfo)
f('Scope', opt, lineinfo, nobreak)
end
local function scope_end(opt, lineinfo)
if #scopes <= 1 then
local scope = #scopes
if scope <= 1 then
warn("'end' without opening block", lineinfo)
else
table.remove(scopes)
end
f('EndScope', opt, lineinfo)
local inside_local = false
for scope = scope-1, 1, -1 do
if scopes[scope].inside_local then inside_local = true; break end
end
f('EndScope', opt, lineinfo, inside_local)
end
local function parse_function_list(has_self, name, pos)
local c = lx:next(); assert(c[1] == '(')
f('Statement', c[1], c.lineinfo) -- generate Statement for function definition
scope_begin(c[1], c.lineinfo)
f('Statement', c[1], c.lineinfo, true) -- generate Statement for function definition
scope_begin(c[1], c.lineinfo, true)
if has_self then
local lineinfo = c.lineinfo+1 -- zero size
f('VarSelf', 'self', lineinfo)
f('VarSelf', 'self', lineinfo, true)
end
while true do
local n = lx:peek()
if not (n.tag == 'Id' or n.tag == 'Keyword' and n[1] == '...') then break end
local c = lx:next()
if c.tag == 'Id' then f('Var', c[1], c.lineinfo) end
if c.tag == 'Id' then f('Var', c[1], c.lineinfo, true) end
-- ignore '...' in this case
if lx:peek()[1] == ',' then lx:next() end
end
if lx:peek()[1] == ')' then
local n = lx:next()
f('Function', name, pos or c.lineinfo)
f('Function', name, pos or c.lineinfo, true)
end
end
while 1 do
while true do
local c = lx:next()
-- Detect end of previous statement
@@ -94,7 +99,10 @@ function PARSE.parse_scope(lx, f, level)
cprev.tag == 'Number' or cprev.tag == 'String')
then
if scopes[#scopes].inside_until then scope_end(nil, c.lineinfo) end
f('Statement', c[1], c.lineinfo)
local scope = #scopes
if not scopes[scope].inside_table then scopes[scope].inside_local = nil end
f('Statement', c[1], c.lineinfo,
scopes[scope].inside_local or c[1] == 'local' or c[1] == 'function' or c[1] == 'end')
end
if c.tag == 'Eof' then break end
@@ -107,7 +115,7 @@ function PARSE.parse_scope(lx, f, level)
local c = lx:next(); assert(c[1] == 'function')
if lx:peek().tag == 'Id' then
c = lx:next()
f('Var', c[1], c.lineinfo)
f('Var', c[1], c.lineinfo, true)
if lx:peek()[1] == '(' then parse_function_list(nil, c[1], c.lineinfo) end
end
elseif c[1] == 'function' then
@@ -117,13 +125,13 @@ function PARSE.parse_scope(lx, f, level)
c = lx:next(); assert(c.tag == 'Id')
local name = c[1]
local pos = c.lineinfo
f('Id', name, pos)
f('Id', name, pos, true)
local has_self
while lx:peek()[1] ~= '(' and lx:peek().tag ~= 'Eof' do
c = lx:next()
name = name .. c[1]
if c.tag == 'Id' then
f('String', c[1], c.lineinfo)
f('String', c[1], c.lineinfo, true)
elseif c.tag == 'Keyword' and c[1] == ':' then
has_self = true
end
@@ -131,20 +139,21 @@ function PARSE.parse_scope(lx, f, level)
if lx:peek()[1] == '(' then parse_function_list(has_self, name, pos) end
end
elseif c[1] == 'local' and lx:peek().tag == 'Id' then
scopes[#scopes].inside_local = true
c = lx:next()
f('VarNext', c[1], c.lineinfo)
f('VarNext', c[1], c.lineinfo, true)
while lx:peek().tag == 'Keyword' and lx:peek()[1] == ',' do
c = lx:next(); if lx:peek().tag ~= 'Id' then break end
c = lx:next()
f('VarNext', c[1], c.lineinfo)
f('VarNext', c[1], c.lineinfo, true)
end
elseif c[1] == 'for' and lx:peek().tag == 'Id' then
c = lx:next()
f('VarInside', c[1], c.lineinfo)
f('VarInside', c[1], c.lineinfo, true)
while lx:peek().tag == 'Keyword' and lx:peek()[1] == ',' do
c = lx:next(); if lx:peek().tag ~= 'Id' then break end
c = lx:next()
f('VarInside', c[1], c.lineinfo)
f('VarInside', c[1], c.lineinfo, true)
end
elseif c[1] == 'do' then
scope_begin('do', c.lineinfo)
@@ -169,15 +178,18 @@ function PARSE.parse_scope(lx, f, level)
local cnext = lx:peek()
if cnext.tag == 'Keyword' and (cnext[1] == '(' or cnext[1] == '{')
or cnext.tag == 'String' then
f('FunctionCall', c[1], c.lineinfo)
f('FunctionCall', c[1], c.lineinfo, scopes[#scopes].inside_local ~= nil)
end
if scopes[#scopes].inside_table and cnext.tag == 'Keyword' and cnext[1] == '=' then
local scope = #scopes
local inside_local = scopes[scope].inside_local ~= nil
if (scopes[scope].inside_table or cprev[1] == ',')
and cnext.tag == 'Keyword' and cnext[1] == '=' then
-- table field
f('String', c[1], c.lineinfo)
f('String', c[1], c.lineinfo, inside_local)
elseif cprev.tag == 'Keyword' and (cprev[1] == ':' or cprev[1] == '.') then
f('String', c[1], c.lineinfo)
f('String', c[1], c.lineinfo, inside_local)
else
f('Id', c[1], c.lineinfo)
f('Id', c[1], c.lineinfo, inside_local)
end
end
@@ -213,7 +225,7 @@ function PARSE.parse_scope_resolve(lx, f, vars)
vars = vars or newscope({[0] = 0}, nil, 1)
vars[NEXT] = false -- vars that come into scope upon next statement
vars[INSIDE] = false -- vars that come into scope upon entering block
PARSE.parse_scope(lx, function(op, name, lineinfo)
PARSE.parse_scope(lx, function(op, name, lineinfo, nobreak)
-- in some (rare) cases VarNext can follow Statement event (which copies
-- vars[NEXT]). This may cause vars[0] to be `nil`, so default to 1.
local var = op:find("^Var") and
@@ -249,7 +261,7 @@ function PARSE.parse_scope_resolve(lx, f, vars)
else
assert(false)
end
f(op, name, lineinfo, vars)
f(op, name, lineinfo, vars, nobreak)
end, vars[0])
end

View File

@@ -92,7 +92,7 @@ return {
local lx = LEX.lexc(code, nil, pos)
return coroutine.wrap(function()
local varnext = {}
PARSE.parse_scope_resolve(lx, function(op, name, lineinfo, vars)
PARSE.parse_scope_resolve(lx, function(op, name, lineinfo, vars, nobreak)
if not(op == 'Id' or op == 'Statement' or op == 'Var'
or op == 'Function' or op == 'String'
or op == 'VarNext' or op == 'VarInside' or op == 'VarSelf'
@@ -106,10 +106,10 @@ return {
for _, token in pairs(varnext) do coroutine.yield(unpack(token)) end
varnext = {}
elseif op == 'VarNext' or op == 'VarInside' then
table.insert(varnext, {'Var', name, lineinfo, vars, at})
table.insert(varnext, {'Var', name, lineinfo, vars, at, nobreak})
end
coroutine.yield(op, name, lineinfo, vars, op == 'Function' and at-1 or at)
coroutine.yield(op, name, lineinfo, vars, op == 'Function' and at-1 or at, nobreak)
end, vars)
end)
end,

View File

@@ -528,7 +528,7 @@ function IndicateAll(editor, lines, linee)
for n = #tokens, 1, -1 do
local token = tokens[n]
-- find the last token before the range
if token.name and token.fpos+#token.name < start then
if not token.nobreak and token.name and token.fpos+#token.name < start then
pos, vars = token.fpos+#token.name, token.context
break
end
@@ -583,13 +583,12 @@ function IndicateAll(editor, lines, linee)
local s = TimeGet()
local canwork = start and 0.010 or 0.100 -- use shorter interval when typing
local f = editor.spec.markvars(editor:GetText(), pos, vars)
local ops, lastinfo = 0, 0
while true do
local op, name, lineinfo, vars, at = f()
local op, name, lineinfo, vars, at, nobreak = f()
if not op then break end
local var = vars and vars[name]
local token = {op, name=name, fpos=lineinfo, at=at, context=vars,
self = (op == 'VarSelf') or nil }
self = (op == 'VarSelf') or nil, nobreak=nobreak}
if op == 'Function' then
vars['function'] = (vars['function'] or 0) + 1
end
@@ -614,16 +613,15 @@ function IndicateAll(editor, lines, linee)
if indic.varmasked and not var.masked.self then
editor:SetIndicatorCurrent(indicator.MASKED)
editor:IndicatorFillRange(fpos-1, #name)
table.insert(tokens, {"Masked", name=name, fpos=fpos})
table.insert(tokens, {"Masked", name=name, fpos=fpos, nobreak=nobreak})
end
if indic.varmasking then IndicateOne(indicator.MASKING, lineinfo, #name) end
end
if lineinfo and lineinfo > lastinfo and ops % 10 == 0 and TimeGet()-s > canwork then
if lineinfo and not nobreak and (op == 'Statement' or op == 'String') and TimeGet()-s > canwork then
delayed[editor] = {lineinfo, vars}
break
end
lastinfo = lineinfo or lastinfo
end
-- clear indicators till the end of processed fragment