if not modules then modules = { } end modules ['syst-lua'] = { version = 1.001, comment = "companion to syst-lua.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local load, type, tonumber = load, type, tonumber local find, gsub = string.find, string.gsub local concat = table.concat local utfchar = utf.char local S, C, P, lpegmatch, lpegtsplitat = lpeg.S, lpeg.C, lpeg.P, lpeg.match, lpeg.tsplitat local xmath = xmath or math local xcomplex = xcomplex or { } ----- scannext = token.scannext local scancmdchr = token.scancmdchrexpanded local scantoken = token.scantoken local getcsname = token.getcsname local cmd = tokens.commands local letter_code = cmd.letter local other_char_code = cmd.other_char local spacer_code = cmd.spacer local other_char_code = cmd.other_char local relax_code = cmd.relax local register_int_code = cmd.register_int local internal_int_code = cmd.internal_int local register_dimen_code = cmd.register_dimen local internal_dimen_code = cmd.internal_dimen local register_glue_code = cmd.register_glue local internal_glue_code = cmd.internal_glue local register_toks_code = cmd.register_toks local internal_toks_code = cmd.internal_toks local char_given_code = cmd.char_given local math_given_code = cmd.math_given local xmath_given_code = cmd.xmath_given local some_item_code = cmd.some_item local getdimen = tex.getdimen local getglue = tex.getglue local getcount = tex.getcount local gettoks = tex.gettoks local gettex = tex.get local context = context commands = commands or { } local commands = commands local context = context local implement = interfaces.implement local dimenfactors = number.dimenfactors local ctx_protected_cs = context.protected.cs -- more efficient local ctx_firstoftwoarguments = context.firstoftwoarguments local ctx_secondoftwoarguments = context.secondoftwoarguments local ctx_firstofoneargument = context.firstofoneargument local ctx_gobbleoneargument = context.gobbleoneargument local values = tokens.values local boolean_code = values.boolean implement { -- will be overloaded later name = "writestatus", arguments = "2 arguments", actions = logs.status, } function commands.doifelse(b) if b then ctx_firstoftwoarguments() else ctx_secondoftwoarguments() end end function commands.doifelsesomething(b) if b and b ~= "" then ctx_firstoftwoarguments() else ctx_secondoftwoarguments() end end function commands.doif(b) if b then ctx_firstofoneargument() else ctx_gobbleoneargument() end end function commands.doifsomething(b) if b and b ~= "" then ctx_firstofoneargument() else ctx_gobbleoneargument() end end function commands.doifnot(b) if b then ctx_gobbleoneargument() else ctx_firstofoneargument() end end function commands.doifnotthing(b) if b and b ~= "" then ctx_gobbleoneargument() else ctx_firstofoneargument() end end commands.testcase = commands.doifelse -- obsolete function commands.boolcase(b) context(b and 1 or 0) end function commands.doifelsespaces(str) if find(str,"^ +$") then ctx_firstoftwoarguments() else ctx_secondoftwoarguments() end end local pattern = lpeg.patterns.validdimen function commands.doifelsedimenstring(str) if lpegmatch(pattern,str) then ctx_firstoftwoarguments() else ctx_secondoftwoarguments() end end local p_first = C((1-P(",")-P(-1))^0) implement { name = "firstinset", arguments = "string", actions = function(str) context(lpegmatch(p_first,str or "")) end, public = true, } implement { name = "ntimes", arguments = { "string", "integer" }, actions = { string.rep, context } } implement { name = "execute", arguments = "string", actions = os.execute -- wrapped in sandbox } implement { name = "doifelsesame", arguments = "2 strings", actions = function(a,b) if a == b then ctx_firstoftwoarguments() else ctx_secondoftwoarguments() end end } implement { name = "doifsame", arguments = "2 strings", actions = function(a,b) if a == b then ctx_firstofoneargument() else ctx_gobbleoneargument() end end } implement { name = "doifnotsame", arguments = "2 strings", actions = function(a,b) if a == b then ctx_gobbleoneargument() else ctx_firstofoneargument() end end } -- This is a bit of a joke as I never really needed floating point expressions (okay, -- maybe only with scaling because there one can get numbers that are too large for -- dimensions to deal with). Of course one can write a parser in \TEX\ speak but then -- one also needs to implement a bunch of functions. It doesn't pay of so we just -- stick to the next gimmick. It looks inefficient but performance is actually quite -- efficient. do local result = { "return " } local word = { } local r = 1 local w = 0 local report = logs.reporter("system","expression") local function unexpected(c) report("unexpected token %a",c) end local function unexpected(c) report("unexpected token %a",c) end local function expression() local w = 0 local r = 1 while true do local n, i = scancmdchr() if n == letter_code then w = w + 1 ; word[w] = utfchar(i) else if w > 0 then -- we could use a metatable for all math, complex and factors local s = concat(word,"",1,w) local d = dimenfactors[s] if d then r = r + 1 ; result[r] = "*" r = r + 1 ; result[r] = 1/d else if xmath[s] then r = r + 1 ; result[r] = "xmath." elseif xcomplex[s] then r = r + 1 ; result[r] = "xcomplex." end r = r + 1 ; result[r] = s end w = 0 end if n == other_char_code then r = r + 1 ; result[r] = utfchar(i) elseif n == spacer_code then -- r = r + 1 ; result[r] = " " elseif n == relax_code then break elseif n == register_int_code or n == internal_int_code then r = r + 1 ; result[r] = getcount(i) elseif n == register_dimen_code or n == internal_dimen_code then r = r + 1 ; result[r] = getdimen(i) elseif n == register_glue_code or n == n == register_dimen_code_glue_code then r = r + 1 ; result[r] = getglue(i) elseif n == register_toks_code or n == n == register_dimen_code_toks_code then r = r + 1 ; result[r] = gettoks(i) elseif n == char_given_code or n == math_given_code or n == xmath_given_code then r = r + 1 ; result[r] = i elseif n == some_item_code then local n = getcsname(t) if n then local s = gettex(n) if s then r = r + 1 ; result[r] = s else unexpected(c) end else unexpected(c) end -- elseif n == call_code then -- local n = getcsname(t) -- if n then -- local s = get_macro(n) -- if s then -- r = r + 1 ; result[r] = s -- else -- unexpected(c) -- end -- else -- unexpected(c) -- end -- elseif n == the_code or n == convert_code or n == lua_expandable_call_code then -- put_next(t) -- scantoken() -- expands else unexpected(c) end end end local code = concat(result,"",1,r) local func = load(code) if type(func) == "function" then context(func()) else report("invalid lua %a",code) end end implement { public = true, name = "expression", actions = expression, } end do interfaces.implement { name = "iflua", public = true, usage = "condition", arguments = "string", actions = function(s) local c = load("return(" .. s .. ")") return boolean_code, (c and c()) and true or false end, } end do -- This is some 20% slower than native but we only provide this for compatibility -- reasons so we don't care that much about it. Eventually we can drop the built-in -- method. local channels = { } local lastdone = { } local findbinfile = resolvers.findbinfile local loadbinfile = resolvers.loadbinfile local opentexfile = resolvers.opentexfile local scaninteger = tokens.scanners.integer local scankeyword = tokens.scanners.keyword local scanstring = tokens.scanners.string local scancsname = tokens.scanners.csname local setmacro = tokens.setters.macro local rlncatcodes = tex.rlncatcodes local texgetcount = tex.getcount local bytes = string.bytes local getcatcode = tex.getcatcode local char, concat = string.char, table.concat -- This uses the normal bin lookup method that we also use for other files, -- and in principle there is no limit on the amount of files other than the -- operating system imposes. local t = { } local l = 0 implement { name = "openin", public = true, usage = "value", actions = function() local n = scaninteger() scankeyword("=") local s = scanstring(true) local c = channels[n] if c then c:close() end local f = findbinfile(s,"tex") if f then channels[n] = opentexfile(f) lastdone[n] = false else channels[n] = false lastdone[n] = true end end, } implement { name = "closein", public = true, usage = "value", actions = function() local n = scaninteger() local c = channels[n] if c then c:close() end channels[n] = false t = { } end, } -- This is not the fastest routine but hardly used, occasionally for line by line -- input in e.g. tikz which is not that fast anyway so I'll deal with it when it -- really is a bottleneck. local readerror = function(what,n) tex.error(string.formatters["too many %s brace tokens in read from channel %i"](what,n)) end interfaces.implement { name = "read", public = true, usage = "value", actions = function(prefix) local n = scaninteger() local c = channels[n] if scankeyword("line") and c then c:gotoline(scaninteger()) end scankeyword("to") local m = scancsname(true) local g = 0 local s l = 0 if c then while true do local s = c and c:reader() if s then l = l + 1 s = gsub(s," *$","") t[l] = s for c in bytes(s) do local cc = getcatcode(c) if cc == 1 then g = g + 1 elseif cc == 2 then g = g - 1 end end if g <= 0 then break end else break end end if g > 0 then readerror("left",n) s = "" elseif g < 0 then readerror("right",n) s = "" elseif l == 0 then if c:endoffile() then lastdone[n] = true s = "\\par" else s = "" end channels[n] = false else local e = texgetcount("endlinechar") -- we can have tex.endline if needed if e < 0 or e > 127 then e = "" else e = " " l = l + 1 t[l] = "" end s = concat(t, e, 1, l) end else s = "" end setmacro(m, s, prefix) -- checks for frozen end, } -- This is an etex extension. All characters become catcode 12 but a space gets code -- 10. The etex manual specifies a lineending of catcode 12 too. Should we strip spaces -- at the end? A bit weird command that we never use anyway. implement { name = "readline", public = true, usage = "value", actions = function(prefix) local n = scaninteger() local c = channels[n] if scankeyword("line") and c then c:gotoline(scaninteger()) end scankeyword("to") local m = scancsname(true) local s = c and c:reader() if s then local e = texgetcount("endlinechar") -- we can have tex.endline if needed if e > 0 then -- forget about 0 s = s .. char(e) end else channels[n] = false s = "" end setmacro(rlncatcodes, m, s, prefix) -- checks for frozen end, } implement { name = "readlinedirect", public = true, untraced = true, actions = function(prefix) local n = scaninteger() local c = channels[n] if scankeyword("line") and c then c:gotoline(scaninteger()) end local s = c and c:reader() if s then local e = texgetcount("endlinechar") -- we can have tex.endline if needed if e > 0 then -- forget about 0 s = s .. char(e) end context(s) else channels[n] = false end end, } -- This one uses the special lua condition option which is kind of experimental -- but seems to work fine. local boolean_value = tokens.values.boolean implement { name = "ifeof", public = true, usage = "condition", actions = function() local n = scaninteger() return boolean_value, not channels[n] end, } -- This one doesn't belong here and it might become a real primitive if we need it -- frequently. So, for the moment we keep it in this file. local getnest = tex.getnest implement { name = "ifmvl", public = true, usage = "condition", actions = function() return boolean_value, getnest("ptr") == 0 end, } end