Skip to content

Add events to Expression 2 #2452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 27, 2022
2 changes: 2 additions & 0 deletions data/expression2/tests/parsing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ do { continue } while (A)

try {} catch(Err) {}

event tick() {}

A++
A--
A += 1
Expand Down
96 changes: 88 additions & 8 deletions lua/entities/gmod_wire_expression2/base/compiler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ AddCSLuaFile()
---@field persist table<string, string> # Variable: Type
---@field inputs table<string, string> # Variable: Type
---@field outputs table<string, string> # Variable: Type
---@field registered_events table<string, function>
local Compiler = {}
Compiler.__index = Compiler
E2Lib.Compiler = Compiler
Expand Down Expand Up @@ -56,6 +57,7 @@ end
---@return function script
function Compiler:Process(root, inputs, outputs, persist, delta, includes) -- Took params out becuase it isnt used.
self.context = {}
self.registered_events = {}
self.warnings = {}

self:InitScope() -- Creates global scope!
Expand All @@ -66,7 +68,6 @@ function Compiler:Process(root, inputs, outputs, persist, delta, includes) -- To
self.includes = includes or {}
self.prfcounter = 0
self.prfcounters = {}
self.tvars = {}
self.funcs = {}
self.dvars = {}
self.funcs_ret = {}
Expand Down Expand Up @@ -206,14 +207,18 @@ end

-- ---------------------------------------------------------------------------

--- May return nil in the case of a statement without any runtime side effects.
---@return table?, string, string, any
function Compiler:EvaluateStatement(args, index)
local trace = args[index + 2]

local name = string_upper(trace[1])

local ex, tp, extra = self:CallInstruction(name, trace)
ex.TraceName = name
ex.Trace = trace[2]
if ex then
ex.TraceName = name
ex.Trace = trace[2]
end

return ex, tp, name, extra
end
Expand Down Expand Up @@ -420,7 +425,10 @@ function Compiler:InstrSEQ(args)
self:Warning(ExprWarnings[instr], args[i + 2])
end

stmts[#stmts + 1] = stmt
if stmt then
-- Statement has a runtime side effect.
stmts[#stmts + 1] = stmt
end
end
end

Expand Down Expand Up @@ -593,9 +601,14 @@ function Compiler:InstrCALL(args)
exprs[#exprs + 1] = tps

if rt[4] then
if rt[4].deprecated then
if rt[4].deprecated ~= nil and rt[4].deprecated ~= true then
-- Deprecation message (string)
self:Warning("Use of deprecated function: " .. args[3] .. "(" .. tps_pretty(tps) .. "): '" .. rt[4].deprecated .. "'", args)
elseif rt[4].deprecated then
self:Warning("Use of deprecated function: " .. args[3] .. "(" .. tps_pretty(tps) .. ")", args)
elseif rt[4].noreturn then
end

if rt[4].noreturn then
self.Scope._dead = true
end
end
Expand Down Expand Up @@ -648,8 +661,17 @@ function Compiler:InstrMETHODCALL(args)
exprs[1] = rt[1]
exprs[#exprs + 1] = tps

if rt[4] and rt[4].deprecated then
self:Warning("Use of deprecated method: " .. tps_pretty(tp) .. ":" .. args[3] .. "(" .. tps_pretty(tps) .. ")", args)
if rt[4] then
if rt[4].deprecated ~= nil and rt[4].deprecated ~= true then
-- Deprecation message (string)
self:Warning("Use of deprecated method: " .. tps_pretty(tp) .. ":" .. args[3] .. "(" .. tps_pretty(tps) .. "): '" .. rt[4].deprecated .. "'", args)
elseif rt[4].deprecated then
self:Warning("Use of deprecated method: " .. tps_pretty(tp) .. ":" .. args[3] .. "(" .. tps_pretty(tps) .. ")", args)
end

if rt[4].noreturn then
self.Scope._dead = true
end
end

return exprs, rt[2], rt[4]
Expand Down Expand Up @@ -1277,4 +1299,62 @@ function Compiler:InstrTRY(args)
local prf_cond = self:PopPrfCounter()

return { self:GetOperator(args, "try", {})[1], prf_cond, stmt, var_name, stmt2 }
end

function Compiler:InstrEVENT(args)
-- args = { "event", trace, name, args, event_block }
local name, hargs = args[3], args[4]

if not E2Lib.Env.Events[name] then
self:Error("No such event exists: '" .. name .. "'", args)
end

local event = E2Lib.Env.Events[name]

if #hargs > #event.args then
local extra_arg_types = {}
for i = #event.args + 1, #hargs do
-- name, type, variadic
extra_arg_types[#extra_arg_types + 1] = hargs[i][2]
end

self:Error("Event '" .. name .. "' does not take arguments (" .. table.concat(extra_arg_types, ", ") .. ")", args)
end

for k, typeid in ipairs(event.args) do
if not hargs[k] then
-- TODO: Maybe this should be a warning so that events can have extra params added without breaking old code?
self:Error("Event '" .. name .. "' missing argument #" .. k .. " of type " .. tps_pretty(typeid), args)
end

local param_id = wire_expression_types[hargs[k][2]][1]
if typeid ~= param_id then
self:Error("Mismatched event argument: " .. tps_pretty(arg) .. " vs " .. tps_pretty(param_id), args)
end
end

if (self.registered_events[name] and self.registered_events[name][self.include or "__main__"]) then
self:Error("You can only register one event callback per file", args)
end

self.registered_events[name] = self.registered_events[name] or {}

local OldScopes = self:SaveScopes()
self:InitScope()
self:PushScope()
for k, typeid in ipairs(event.args) do
self:SetLocalVariableType(hargs[k][1], typeid, args)
end

local block = self:EvaluateStatement(args, 3)
self:LoadScopes(OldScopes)

self.registered_events[name][self.include or "__main__"] = function(self, args)
for i, arg in ipairs(hargs) do
local name = arg[1]
self.Scope[name] = args[i]
end

block[1](self, block)
end
end
24 changes: 24 additions & 0 deletions lua/entities/gmod_wire_expression2/base/parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Stmt10 ← (FunctionStmt / ReturnStmt)? Stmt11
Stmt11 ← ("#include" String)? Stmt12
Stmt12 ← ("try" Block "catch" "(" Var ")" Block)? Stmt13
Stmt13 ← ("do" Block "while" Cond)? Expr1
Stmt14 ← ("event" Fun "(" FunctionArgs Block)

FunctionStmt ← "function" FunctionHead "(" FunctionArgs Block
FunctionHead ← (Type Type ":" Fun / Type ":" Fun / Type Fun / Fun)
Expand Down Expand Up @@ -813,6 +814,29 @@ function Parser:Stmt13()
return whl
end

return self:Stmt14()
end

function Parser:Stmt14()
if self:AcceptRoamingToken(TokenVariant.Keyword, Keyword.Event) then
local trace = self:GetTokenTrace()

local name = self:AcceptRoamingToken(TokenVariant.LowerIdent)
if not name then
self:Error("Expected event name after 'event' keyword")
end
local name = self:GetTokenData()

if not self:AcceptRoamingToken(TokenVariant.Grammar, Grammar.LParen) then
self:Error("Left parenthesis (() must appear after event name")
end

local temp, args = {}, {}
self:FunctionArgs(temp, args)

return self:Instruction(trace, "event", name, args, self:Block("event block"))
end

return self:Expr1()
end

Expand Down
51 changes: 29 additions & 22 deletions lua/entities/gmod_wire_expression2/core/chat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ local TextList = {
last = { "", 0, nil }
}
local ChatAlert = {}
local chipHideChat = false
local chipChatReplacement = false

local chatAuthor
local chipHideChat
local chipChatReplacement

--[[************************************************************************]]--

Expand All @@ -21,34 +23,24 @@ hook.Add("PlayerSay","Exp2TextReceiving", function(ply, text, teamchat)
TextList[ply:EntIndex()] = entry
TextList.last = entry

chipHideChat = false
chipChatReplacement = false
chatAuthor = ply
E2Lib.triggerEvent("chat", { ply, text, teamchat and 1 or 0 })

local hideCurrent = false
local replacementCurrent = false
for e,_ in pairs(ChatAlert) do
for e, _ in pairs(ChatAlert) do
if IsValid(e) then
chipHideChat = nil
chipChatReplacement = nil

e.context.data.runByChat = entry
e:Execute()
e.context.data.runByChat = nil
--if chipHideChat ~= nil and ply == e.player then
if chipHideChat and ply == e.player then
hideCurrent = chipHideChat
end

if chipChatReplacement and ply == e.player then
replacementCurrent = chipChatReplacement
end
else
ChatAlert[e] = nil
end
end

if hideCurrent then return "" end
if replacementCurrent then return replacementCurrent end
local hide, repl = chipHideChat, chipChatReplacement
chipHideChat, chipChatReplacement = nil, nil

if hide then return "" end
return repl
end)

hook.Add("EntityRemoved","Exp2ChatPlayerDisconnect", function(ply)
Expand All @@ -59,6 +51,7 @@ end)
__e2setcost(3)

--- If <activate> == 0, the chip will no longer run on chat events, otherwise it makes this chip execute when someone chats. Only needs to be called once, not in every execution.
[nodiscard, deprecated = "Use the chat event instead"]
e2function void runOnChat(activate)
if activate ~= 0 then
ChatAlert[self.entity] = true
Expand All @@ -68,11 +61,13 @@ e2function void runOnChat(activate)
end

--- Returns 1 if the chip is being executed because of a chat event. Returns 0 otherwise.
[nodiscard, deprecated = "Use the chat event instead"]
e2function number chatClk()
return self.data.runByChat and 1 or 0
end

--- Returns 1 if the chip is being executed because of a chat event by player <ply>. Returns 0 otherwise.
[nodiscard, deprecated = "Use the chat event instead"]
e2function number chatClk(entity ply)
if not IsValid(ply) then return self:throw("Invalid player!", 0) end
local cause = self.data.runByChat
Expand All @@ -81,17 +76,22 @@ end

--- If <hide> != 0, hide the chat message that is currently being processed.
e2function void hideChat(hide)
chipHideChat = hide ~= 0
if self.player == chatAuthor then
chipHideChat = hide ~= 0
end
end

--- Changes the chat message, if the chat message was written by the E2 owner.
e2function void modifyChat(string new)
chipChatReplacement = new
if self.player == chatAuthor then
chipChatReplacement = new
end
end

--[[************************************************************************]]--

--- Returns the last player to speak.
[nodiscard, deprecated = "Use the chat event instead"]
e2function entity lastSpoke()
local entry = TextList.last
if not entry then return nil end
Expand All @@ -104,6 +104,7 @@ e2function entity lastSpoke()
end

--- Returns the last message in the chat log.
[nodiscard, deprecated = "Use the chat event instead"]
e2function string lastSaid()
local entry = TextList.last
if not entry then return "" end
Expand All @@ -120,6 +121,7 @@ e2function number lastSaidWhen()
end

--- Returns 1 if the last message was sent in the team chat, 0 otherwise.
[nodiscard, deprecated = "Use the chat event instead"]
e2function number lastSaidTeam()
local entry = TextList.last
if not entry then return 0 end
Expand All @@ -128,6 +130,7 @@ e2function number lastSaidTeam()
end

--- Returns what the player <this> last said.
[nodiscard, deprecated = "Use the chat event instead"]
e2function string entity:lastSaid()
if not IsValid(this) then return self:throw("Invalid entity!", "") end
if not this:IsPlayer() then return self:throw("Not a player", "") end
Expand All @@ -150,6 +153,7 @@ e2function number entity:lastSaidWhen()
end

--- Returns 1 if the last message was sent in the team chat, 0 otherwise.
[nodiscard, deprecated = "Use the chat event instead"]
e2function number entity:lastSaidTeam()
if not IsValid(this) then return self:throw("Invalid entity!", 0) end
if not this:IsPlayer() then return self:throw("Not a player", 0) end
Expand All @@ -159,3 +163,6 @@ e2function number entity:lastSaidTeam()

return entry[4] and 1 or 0
end

-- Ply: entity, Msg: string, Team: number
E2Lib.registerEvent("chat", { "e", "s", "n" })
Loading