AddCSLuaFile("entities/gmod_wire_expression2/core/extpp.lua") -- some constants -- local p_typename = "[a-z][a-z0-9]*" local p_typeid = "[a-z][a-z0-9]?[a-z0-9]?[a-z0-9]?[a-z0-9]?" local p_argname = "[a-zA-Z][a-zA-Z0-9_]*" local p_funcname = "[a-z][a-zA-Z0-9]*" local p_func_operator = "[-a-zA-Z0-9+*/%%^=!><&|$_%[%]]*" local OPTYPE_FUNCTION = nil local OPTYPE_NORMAL = 0 local OPTYPE_DONT_FETCH_FIRST = 1 local OPTYPE_ASSIGN = 2 local OPTYPE_APPEND_RET = 3 local optable = { ["operator+" ] = "add", ["operator++"] = {"inc", OPTYPE_DONT_FETCH_FIRST}, ["operator-" ] = "sub", ["operator--"] = {"dec", OPTYPE_DONT_FETCH_FIRST}, ["operator*" ] = "mul", ["operator/" ] = "div", ["operator%" ] = "mod", ["operator^" ] = "exp", ["operator=" ] = {"ass", OPTYPE_ASSIGN}, ["operator=="] = "eq", ["operator!" ] = "not", ["operator!="] = "neq", ["operator>" ] = "gth", ["operator>="] = "geq", ["operator<" ] = "lth", ["operator<="] = "leq", ["operator&" ] = "and", ["operator&&"] = "and", ["operator|" ] = "or", ["operator||"] = "or", ["operator[]"] = "idx", -- typeless op[] ["operator[T]"] = {"idx", OPTYPE_APPEND_RET}, -- typed op[] ["operator$" ] = {"dlt", OPTYPE_DONT_FETCH_FIRST}, -- mostly superfluous now ["operator_is" ] = "is", ["operator_neg"] = "neg", ["operator_band"] = "band", ["operator_bor"] = "bor", ["operator_bxor"] = "bxor", ["operator_bshl"] = "bshl", ["operator_bshr"] = "bshr", } -- This is an array for types that were parsed from all E2 extensions. We initialize it with an alias "number" for "normal". local preparsed_types = { ["NUMBER"] = "n" } -- This function checks whether its argument is a valid type id. local function is_valid_typeid(typeid) return (typeid:match("^[a-wy-zA-WY-Z]$") or typeid:match("^[xX][a-wy-zA-WY-Z0-9][a-wy-zA-WY-Z0-9]$")) and true end -- Returns the typeid associated with the given typename function e2_get_typeid(typename) local n = string.upper(typename) -- was the type registered with E-2? if wire_expression_types[n] then return wire_expression_types[n][1] end -- was the name found when looking for registerType lines? if preparsed_types[n] then return preparsed_types[n] end -- is the type name a valid typeid? use the type name as the typeid if is_valid_typeid(typename) then return typename end return nil end -- parses an argument list function e2_parse_args(args) local ellipses = false local argtable = { typeids = {}, argnames = {}} if args:find("%S") == nil then return argtable end -- no arguments local function handle_arg(arg) -- ellipses before this argument? raise error if ellipses then error("PP syntax error: Ellipses (...) must be the last argument.",0) end -- is this argument an ellipsis? if string.match(arg, "^%s*%.%.%.%s*$") then -- signal ellipses-ness ellipses = true return false end -- assume a type name was given and split up the argument into type name and argument name. local typename,argname = string.match(arg, "^%s*("..p_typename..")%s+("..p_argname..")%s*$") -- the assumption failed if not typename then -- try looking for a argument name only and defaulting the type name to "number" argname = string.match(arg, "^%s*("..p_argname..")%s*$") typename = "number" end -- this failed as well? give up and print an error if not argname then error("PP syntax error: Invalid function parameter syntax.",0) end local typeid = e2_get_typeid(typename) if not typeid then error("PP syntax error: Invalid parameter type '"..typename.."' for argument '"..argname.."'.",0) end table.insert(argtable.typeids, typeid) table.insert(argtable.argnames, argname) return false end -- find the argument before the first comma local firstarg = args:find(",") or (args:len()+1) firstarg = args:sub(1, firstarg-1) -- handle it handle_arg(firstarg) -- find and handle the remaining arguments. args:gsub(",([^,]*)",handle_arg) return argtable, ellipses end local function mangle(name, arg_typeids, op_type) if op_type then name = "operator_"..name end local ret = "e2_"..name if arg_typeids == "" then return ret end return ret.."_"..arg_typeids:gsub("[:=]","_") end local function linenumber(s,i) local c = 1 local line = 0 while c and c < i do line = line + 1 c = s:find("\n", c+1, true) end return line end -- returns a name and a register function for the given name -- also optionally returns a flag signaling how to treat the operator in question. local function handleop(name) local operator = optable[name] if operator then local op_type = OPTYPE_NORMAL -- special treatment is needed for some operators. if type(operator) == "table" then operator, op_type = unpack(operator) end -- return everything. return operator, "registerOperator", op_type elseif name:find("^"..p_funcname.."$") then return name, "registerFunction", OPTYPE_FUNCTION else error("PP syntax error: Invalid character in function name.",0) end end local function makestringtable(tbl, i, j) if #tbl == 0 then return "{}" end if not i then i = 1 elseif i < 0 then i = #tbl + i + 1 elseif i < 1 then i = 1 elseif i > #tbl then i = #tbl end if not j then j = #tbl elseif j < 0 then j = #tbl + j + 1 elseif j < 1 then j = 1 elseif j > #tbl then j = #tbl end --return string.format("{"..string.rep("%q,", math.max(0,j-i+1)).."}", unpack(tbl, i, j)) local ok, ret = pcall(string.format, "{"..string.rep("%q,", math.max(0,j-i+1)).."}", unpack(tbl, i, j)) if not ok then print(i,j,#tbl,"{"..string.rep("%q,", math.max(0,j-i+1)).."}") error(ret) end return ret end function e2_extpp_pass1(contents) -- look for registerType lines and fill preparsed_types with them for typename, typeid in string.gmatch("\n"..contents, '%WregisterType%(%s*"('..p_typename..')"%s*,%s*"('..p_typeid..')"') do preparsed_types[string.upper(typename)] = typeid end end function e2_extpp_pass2(contents) -- We add some stuff to both ends of the string so we can look for %W (non-word characters) at the ends of the patterns. contents = "\nlocal tempcosts={}"..contents.."\n " -- this is a list of pieces that make up the final code local output = {} -- this is a list of registerFunction lines that will be put at the end of the file. local function_register = {} -- this is a lookup table for the local vars to be created at the top of the file. local locals = {} -- We start from position 2, since char #1 is always the \n we added earlier local lastpos = 2 local aliaspos,aliasdata = nil,nil -- This flag helps determine whether the preprocessor changed, so we can tell the environment about it. local changed = false for h_begin, ret, thistype, colon, name, args, whitespace, equals, h_end in contents:gmatch("()e2function%s+("..p_typename..")%s+([a-z0-9]-)%s*(:?)%s*("..p_func_operator..")%(([^)]*)%)(%s*)(=?)()") do changed = true local function handle_function() if contents:sub(h_begin-1,h_begin-1):match("%w") then return end local aliasflag = nil if equals == "" then if aliaspos then if contents:sub(aliaspos,h_begin-1):find("%S") then error ("PP syntax error: Malformed alias definition.",0) end -- right hand side of an alias assignment aliasflag = 2 aliaspos = nil end else if aliaspos then error("PP syntax error: Malformed alias definition.",0) end -- left hand side of an alias assignment aliasflag = 1 aliaspos = h_end end -- check for some obvious errors if thistype~="" and colon=="" then error("PP syntax error: Function names may not start with a number.",0) end if thistype=="" and colon~="" then error("PP syntax error: No type for 'this' given.",0) end if thistype:sub(1,1):find("[0-9]") then error("PP syntax error: Type names may not start with a number.",0) end -- append everything since the last function to the output. table.insert(output, contents:sub(lastpos,h_begin-1)) -- advance lastpos to the end of the function header lastpos = h_end -- this table contains the arguments in the following form: -- argtable.argname[n] = "" -- argtable.typeids[n] = "" local argtable, ellipses = e2_parse_args(args) -- take care of operators: give them a different name and register function -- op_type is nil if we register a function and a number if it as operator local name, regfn, op_type = handleop(name) -- return type (void means "returns nothing", i.e. "" in registerFunctionese) local ret_typeid = (ret == "void") and "" or e2_get_typeid(ret) -- ret_typeid = (ret == "void") ? "" : e2_get_typeid(ret) -- return type not found => throw an error if not ret_typeid then error("PP syntax error: Invalid return type: '"..ret.."'",0) end -- if "typename:" was found in front of the function name if thistype ~= "" then -- evaluate the type name local this_typeid = e2_get_typeid(thistype) -- the type was not found? if this_typeid == nil then -- is the type name a valid typeid? if is_valid_typeid(thistype) then -- use the type name as the typeid this_typeid = thistype else -- type is not found and not a valid typeid => error error("PP syntax error: Invalid type for 'this': '"..thistype.."'",0) end end -- prepend a "this" argument to the list, with the parsed type if op_type then -- allow pseudo-member-operators. example: e2function matrix:operator*(factor) table.insert(argtable.typeids, 1, this_typeid) else table.insert(argtable.typeids, 1, this_typeid..":") end table.insert(argtable.argnames, 1, "this") end -- if thistype ~= "" -- add a sub-table for flagging arguments as "no opfetch" argtable.no_opfetch = {} if op_type == OPTYPE_ASSIGN then -- assignment -- the assignment operator is registered with only argument typeid, hence we need a special case. -- we need to make sure the two types match: if argtable.typeids[1] ~= argtable.typeids[2] then error("PP syntax error: operator= needs two arguments of the same type.", 0) end -- remove the typeid of one of the arguments from the list argtable.typeids[1] = "" -- mark the argument as "no opfetch" argtable.no_opfetch[1] = true elseif op_type == OPTYPE_DONT_FETCH_FIRST then -- delta/increment/decrement -- mark the argument as "no opfetch" argtable.no_opfetch[1] = true elseif op_type == OPTYPE_APPEND_RET then table.insert(argtable.typeids, 1, ret_typeid.."=") end -- -- prepare some variables needed to generate the function header and the registerFunction line -- -- -- concatenated typeids. example: "s:nn" local arg_typeids = table.concat(argtable.typeids) -- generate a mangled name, which serves as the function's Lua name local mangled_name = mangle(name, arg_typeids, op_type) if aliasflag then if aliasflag == 1 then -- left hand side of an alias definition aliasdata = { regfn, name, arg_typeids, ret_typeid } elseif aliasflag == 2 then -- right hand side of an alias definition regfn, name, arg_typeids, ret_typeid = unpack(aliasdata) table.insert(function_register, ('if %s then %s(%q, %q, %q, %s) end\n'):format(mangled_name, regfn, name, arg_typeids, ret_typeid, mangled_name)) end else -- save tempcost table.insert(output, string.format("tempcosts[%q]=__e2getcost() ", mangled_name)) locals[mangled_name] = true if ellipses then -- generate a registerFunction line table.insert(function_register, string.format('if %s then %s(%q, %q, %q, %s) end\n', mangled_name, regfn, name, arg_typeids.."...", ret_typeid, mangled_name)) -- generate a new function header and append it to the output table.insert(output, 'function '..mangled_name..'(self, args, typeids, ...)') table.insert(output, " if not typeids then") table.insert(output, " local arr,typeids,source_typeids,tmp={},{},args[#args]") table.insert(output, " for i="..(2+#argtable.typeids)..",#args-1 do") table.insert(output, " tmp=args[i]") table.insert(output, " arr[#arr+1]=tmp[1](self,tmp)") if thistype ~= "" then -- offset to compensate the absence of this's typeid table.insert(output, " typeids[#typeids+1]=source_typeids[i-2]") else table.insert(output, " typeids[#typeids+1]=source_typeids[i-1]") end table.insert(output, " end") table.insert(output, " return "..mangled_name.."(self,args,typeids,unpack(arr))") table.insert(output, " end") else -- generate a registerFunction line table.insert(function_register, string.format('if %s then %s(%q, %q, %q, %s, tempcosts[%q], %s) end\n', mangled_name, regfn, name, arg_typeids, ret_typeid, mangled_name, mangled_name, makestringtable(argtable.argnames,(thistype~="") and 2 or 1))) -- generate a new function header and append it to the output table.insert(output, 'function '..mangled_name..'(self, args)') end -- if the function has arguments, insert argument fetch code if #argtable.argnames ~= 0 then local argfetch, opfetch_l, opfetch_r = '', '', '' for i,n in ipairs(argtable.argnames) do if not argtable.no_opfetch[i] then -- generate opfetch code if not flagged as "no opfetch" opfetch_l = opfetch_l..n..', ' opfetch_r = opfetch_r..string.format('%s[1](self, %s), ',n,n) end argfetch = argfetch..string.format('args[%d], ',i+1,i+1) end -- remove the trailing commas argfetch = argfetch:sub(1,-3) opfetch_l = opfetch_l:sub(1,-3) opfetch_r = opfetch_r:sub(1,-3) -- fetch the ops from the args table.insert(output, ' local ' .. table.concat(argtable.argnames, ', ') .. ' = ' .. argfetch) -- fetch the rvs from the ops table.insert(output, ' ' .. opfetch_l .. ' = ' .. opfetch_r) end -- if #argtable.argnames ~= 0 end -- if aliasflag table.insert(output, whitespace) end -- function handle_function() -- use pcall, so we can add line numbers to all errors local ok, msg = pcall(handle_function) if not ok then if msg:sub(1,2) == "PP" then error(":"..linenumber(contents,h_begin)..": "..msg,0) else error(": PP internal error: "..msg,0) end end end -- for contents:gmatch(e2function) -- did the preprocessor change anything? if changed then -- yes => sweep everything together into a neat pile of hopefully valid lua code local locals_str = "" for loc,_ in pairs(locals) do if locals_str == "" then locals_str = "local " .. loc else locals_str = locals_str .. "," .. loc end end return locals_str.." "..table.concat(output)..contents:sub(lastpos,-6)..table.concat(function_register) else -- no => tell the environment about it, so it can include() the source file instead. return false end end if e2_sim then function e2_extpp(contents) preparsed_types = { ["NUMBER"] = "n" } e2_extpp_pass1(contents) local ret = e2_extpp_pass2(contents) preparsed_types = nil return ret end end