Skip to content

Commit

Permalink
Basic annotation processing
Browse files Browse the repository at this point in the history
  • Loading branch information
Denneisk committed Nov 15, 2023
1 parent 4bdb050 commit 3cd9df1
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 25 deletions.
46 changes: 25 additions & 21 deletions lua/entities/gmod_wire_expression2/core/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@ end

__e2setcost(1) -- approximation

[nodiscard]
---@nodiscard
e2function number first()
return self.entity.first and 1 or 0
end

[nodiscard]
---@nodiscard
e2function number duped()
return self.entity.duped and 1 or 0
end

[nodiscard, deprecated = "Use the input event instead"]
---@nodiscard
---@deprecated Use the input event instead
e2function number inputClk()
return self.triggerinput and 1 or 0
end

[nodiscard, deprecated = "Use the input event instead"]
---@nodiscard
---@deprecated Use the input event instead
e2function string inputClkName()
return self.triggerinput or ""
end
Expand Down Expand Up @@ -78,19 +80,21 @@ local function dupefinished( TimedPasteData, TimedPasteDataCurrent )
end
hook.Add("AdvDupe_FinishPasting", "E2_dupefinished", dupefinished )

[nodiscard]
---@nodiscard
e2function number dupefinished()
return self.entity.dupefinished and 1 or 0
end

--- Returns 1 if this is the last() execution and caused by the entity being removed.
[nodiscard, deprecated = "Use the removed event instead"]
---@nodiscard
---@deprecated Use the removed event instead
e2function number removing()
return self.entity.removing and 1 or 0
end

--- If <activate> != 0, the chip will run once when it is removed, setting the last() flag when it does.
[nodiscard, deprecated = "Use the removed event instead"]
---@nodiscard
---@deprecated Use the removed event instead
e2function void runOnLast(activate)
if self.data.last then return end
self.data.runOnLast = activate ~= 0
Expand All @@ -106,7 +110,7 @@ e2function void exit()
end

do
[noreturn]
---@noreturn
e2function void error( string reason )
self:forceThrow(reason)
end
Expand All @@ -128,7 +132,7 @@ end

__e2setcost(100) -- approximation

[noreturn]
---@noreturn
e2function void reset()
self.Scope, self.ScopeID, self.Scopes = self.GlobalScope, 0, { [0] = self.GlobalScope }

Expand Down Expand Up @@ -166,43 +170,43 @@ local round = math.Round

__e2setcost(1) -- approximation

[nodiscard]
---@nodiscard
e2function number ops()
return round(self.prfbench)
end

[nodiscard]
---@nodiscard
e2function number entity:ops()
if not IsValid(this) or this:GetClass() ~= "gmod_wire_expression2" or not this.context then return 0 end
return round(this.context.prfbench)
end

[nodiscard]
---@nodiscard
e2function number opcounter()
return ceil(self.prf + self.prfcount)
end

[nodiscard]
---@nodiscard
e2function number cpuUsage()
return self.timebench
end

[nodiscard]
---@nodiscard
e2function number entity:cpuUsage()
if not IsValid(this) or this:GetClass() ~= "gmod_wire_expression2" or not this.context then return 0 end
return this.context.timebench
end

--- If used as a while loop condition, stabilizes the expression around <maxexceed> hardquota used.
[nodiscard]
---@nodiscard
e2function number perf()
if self.prf >= e2_tickquota*0.95-200 then return 0 end
if self.prf + self.prfcount >= e2_hardquota then return 0 end
if self.prf >= e2_softquota*2 then return 0 end
return 1
end

[nodiscard]
---@nodiscard
e2function number perf(number n)
n = math.Clamp(n, 0, 100)
if self.prf >= e2_tickquota*n*0.01 then return 0 end
Expand All @@ -215,7 +219,7 @@ e2function number perf(number n)
return 1
end

[nodiscard]
---@nodiscard
e2function number minquota()
if self.prf < e2_softquota then
return floor(e2_softquota - self.prf)
Expand All @@ -224,7 +228,7 @@ e2function number minquota()
end
end

[nodiscard]
---@nodiscard
e2function number maxquota()
if self.prf < e2_tickquota then
local tickquota = e2_tickquota - self.prf
Expand All @@ -240,17 +244,17 @@ e2function number maxquota()
end
end

[nodiscard]
---@nodiscard
e2function number softQuota()
return e2_softquota
end

[nodiscard]
---@nodiscard
e2function number hardQuota()
return e2_hardquota
end

[nodiscard]
---@nodiscard
e2function number timeQuota()
return e2_timequota
end
Expand Down
109 changes: 105 additions & 4 deletions lua/entities/gmod_wire_expression2/core/extpp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,103 @@ local function parseAttributes(attributes, trace)
end
end

local annotationHandlers = {}

local function checkAttributes(annotations)
local attributes = annotations.Attributes
if not attributes then
attributes = { legacy = "false" }
annotations.Attributes = attributes
end
return attributes
end

annotationHandlers.deprecated = function(annotations, msg)
local attrs = checkAttributes(annotations)
msg = msg:Trim()
if msg ~= "" then
attrs.deprecated = string.format("%q", msg)
else
attrs.deprecated = "true"
end
end

annotationHandlers.nodiscard = function(annotations)
checkAttributes(annotations).nodiscard = "true"
end

annotationHandlers.noreturn = function(annotations)
checkAttributes(annotations).noreturn = "true"
end

annotationHandlers.cost = function(annotations, cost)
annotations.Cost = tonumber(cost:Trim()) -- Will be nil if invalid number, won't be processed
end

---@param contents string
---@param endPos number
---@return table
local function parseFunctionAnnotations(contents, endPos, trace)
local output = {}
local annotationLines = {}
-- Funky reverse find
do
local lineEnd = endPos - 1
local checkedAttributes = false
local pastFirstLine = false -- This is here because I don't want to assumed every e2function follows a newline, yet most do
for i = lineEnd, 1, -1 do
if contents:sub(i, i) == "\n" then
local line = contents:sub(i + 1, lineEnd)
if line:sub(1, 3) == "---" then
table.insert(annotationLines, 1, line:sub(4))
lineEnd = i - 1
elseif not checkedAttributes then
local oldAttributes = line:match("%b[]")
if oldAttributes then
output.AttributesBegin = i + 1
output.Attributes = parseAttributes(oldAttributes, trace)
elseif pastFirstLine then
break
end
checkedAttributes = pastFirstLine
else
if pastFirstLine then break end
end
pastFirstLine = true
end
end
end
if #annotationLines ~= 0 then
for _, v in ipairs(annotationLines) do
if v:sub(1, 1) == "@" then
local param = v:match("^%S*", 2)
if param and annotationHandlers[param] then
annotationHandlers[param](output, v:sub(#param + 2))
end
--[[ -- Just kidding, let's not try to completely change how E2 descriptions are handled... yet
else -- Annotation comment
local comments = output.Comment ---@diagnostic disable-line: param-type-mismatch
if not comments then
comments = {}
output.Comment = comments
end
v = v:gsub("<br/?>", "\n"):gsub("\\n", "\n") -- Normalize newline escapes
comments[#comments + 1] = ((v:match("^[ \t]*(.-)[ \t\n\r]*$") or v) .. (v:sub(-1, -1) == "\n" and "" or " ")):gsub("\n", "\\n"):Trim()
]]
end
end

--[[
if output.Comment then
output.Comment = table.concat(output.Comment) ---@diagnostic disable-line: param-type-mismatch
end
]]
end
output.LineCount = #annotationLines
return output
end

--- Compact lua code to a single line to avoid changing lua's tracebacks.
local function compact(lua)
return (lua:Trim():gsub("\n\t*", " "))
Expand All @@ -147,7 +244,7 @@ function E2Lib.ExtPP.Pass2(contents, filename)

-- This flag helps determine whether the preprocessor changed, so we can tell the environment about it.
local changed = false
for a_begin, attributes, h_begin, ret, thistype, colon, name, args, whitespace, equals, h_end in contents:gmatch("()(%[?[%w,_ =\"]*%]?)[\r\n\t ]*()e2function%s+(" .. p_typename .. ")%s+([a-z0-9]-)%s*(:?)%s*(" .. p_func_operator .. ")%(([^)]*)%)(%s*)(=?)%s*()") do
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*)(=?)%s*()") do
local _, line = contents:sub(1, h_begin):gsub("\n", "")

local trace = "(at line " .. line .. ")" .. (E2Lib.currentextension and (" @" .. filename) or "")
Expand All @@ -170,7 +267,8 @@ function E2Lib.ExtPP.Pass2(contents, filename)
error("PP syntax error: Invalid return type: '" .. ret .. "' " .. trace)
elseif RemovedOperators[name] then -- Old operator that no longer is needed.
ErrorNoHalt("Warning: Operator " .. name .. " is now redundant. Ignoring registration. " .. trace .. "\n")
local pivot = parseAttributes(attributes, trace) and a_begin - 1 or h_begin - 1
local annotations = parseAnnotations(contents, h_begin, trace)
local pivot = annotations.AttributesBegin and annotations.AttributesBegin - 1 or h_begin - 1
table.insert(output, contents:sub(lastpos, pivot)) -- Insert code from before header.
changed, lastpos = true, h_end -- Mark as changed and remove function header.
table.insert(output, "local _ = function() ") -- Insert dummy lambda function to substitute for function declaration.
Expand All @@ -193,7 +291,10 @@ function E2Lib.ExtPP.Pass2(contents, filename)

local params, has_vararg, vartbl_name = parseParameters(args, trace)

local attributes = parseAttributes(attributes, trace)
local annotations = parseFunctionAnnotations(contents, h_begin, trace)

local attributes = annotations.Attributes
local a_begin = annotations.AttributesBegin or h_begin

local attr_str
if attributes then
Expand Down Expand Up @@ -250,7 +351,7 @@ function E2Lib.ExtPP.Pass2(contents, filename)
"]] .. param_sig .. (has_vararg and "..." or "") .. [[",
"]] .. ret_typeid .. [[",
registeredfunctions.]] .. mangled .. [[,
tempcosts.]] .. mangled .. [[,
]] .. (annotations.Cost and tostring(annotations.Cost) or ([[tempcosts.]] .. mangled)) .. [[,
]] .. "{" .. table.concat(param_names_quot, ",", thistype ~= "" and 2 or 1) .. "}" .. [[,
]] .. attr_str .. [[
)
Expand Down

0 comments on commit 3cd9df1

Please sign in to comment.