From f76b5c932422630e69a443b4893e326de4a15362 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 24 Dec 2024 13:55:28 +0000 Subject: [PATCH 01/12] add lpc submodule --- .gitmodules | 3 +++ vendor/grammars/lpc-language-server | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/grammars/lpc-language-server diff --git a/.gitmodules b/.gitmodules index 65d5ef54e2..41a017ca16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1446,3 +1446,6 @@ [submodule "vendor/grammars/zephir-sublime"] path = vendor/grammars/zephir-sublime url = https://github.com/phalcon/zephir-sublime +[submodule "vendor/grammars/lpc-language-server"] + path = vendor/grammars/lpc-language-server + url = https://github.com/jlchmura/lpc-language-server.git diff --git a/vendor/grammars/lpc-language-server b/vendor/grammars/lpc-language-server new file mode 160000 index 0000000000..9ed1d0b84a --- /dev/null +++ b/vendor/grammars/lpc-language-server @@ -0,0 +1 @@ +Subproject commit 9ed1d0b84a088907b0da35b1722a98ea3e90eba5 From 9909db87b0e8a957a5a85d51fbb743a2d2f61c6d Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 24 Dec 2024 13:55:46 +0000 Subject: [PATCH 02/12] add lpc language, samples, heuristics --- .gitmodules | 6 +- grammars.yml | 3 + lib/linguist/heuristics.yml | 10 + lib/linguist/languages.yml | 9 + samples/LPC/area_env.h | 4 + samples/LPC/convo.c | 642 ++++++++++++++++++ samples/LPC/globals.h | 15 + samples/LPC/startroom.c | 53 ++ vendor/README.md | 1 + .../git_submodule/lpc-language-server.dep.yml | 31 + 10 files changed, 771 insertions(+), 3 deletions(-) create mode 100644 samples/LPC/area_env.h create mode 100644 samples/LPC/convo.c create mode 100644 samples/LPC/globals.h create mode 100644 samples/LPC/startroom.c create mode 100644 vendor/licenses/git_submodule/lpc-language-server.dep.yml diff --git a/.gitmodules b/.gitmodules index 41a017ca16..251b048d46 100644 --- a/.gitmodules +++ b/.gitmodules @@ -881,6 +881,9 @@ [submodule "vendor/grammars/logtalk.tmbundle"] path = vendor/grammars/logtalk.tmbundle url = https://github.com/textmate/logtalk.tmbundle +[submodule "vendor/grammars/lpc-language-server"] + path = vendor/grammars/lpc-language-server + url = https://github.com/jlchmura/lpc-language-server.git [submodule "vendor/grammars/lua.tmbundle"] path = vendor/grammars/lua.tmbundle url = https://github.com/LuaLS/lua.tmbundle.git @@ -1446,6 +1449,3 @@ [submodule "vendor/grammars/zephir-sublime"] path = vendor/grammars/zephir-sublime url = https://github.com/phalcon/zephir-sublime -[submodule "vendor/grammars/lpc-language-server"] - path = vendor/grammars/lpc-language-server - url = https://github.com/jlchmura/lpc-language-server.git diff --git a/grammars.yml b/grammars.yml index 219734dcf1..33375ccd09 100644 --- a/grammars.yml +++ b/grammars.yml @@ -837,6 +837,9 @@ vendor/grammars/logos: - source.logos vendor/grammars/logtalk.tmbundle: - source.logtalk +vendor/grammars/lpc-language-server: +- documentation.injection.lpc +- source.lpc.lang-server vendor/grammars/lua.tmbundle: - source.lua vendor/grammars/m3: diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 923303e6dd..4334cc318e 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -146,6 +146,12 @@ disambiguations: rules: - language: XML pattern: '^(\s*)(?i:query_name())); + lastPlayer = p; + return p; +} +public query_lastPlayer() { + return (!lastPlayer && parentConvo) + ? parentConvo->query_lastPlayer() + : lastPlayer; +} + +/** indicates if the convo should destruct when it is finished */ +public set_destructOnEnd(int flag) { + destructOnEnd = flag; +} + +set_parentConvo(object c) { + parentConvo=c; +} +object query_parentConvo() { return parentConvo; } + +/** The owner is the actor this convo corresponds to. Usually a monster. */ +set_owner(object o) { + owner = o; + return o; +} +object query_owner() { return owner; } + +/** the operation queue for this conversation. don't mess with this */ +set_queue(mixed *q) { + queue = q; + return q; +} +query_queue() { return queue; } + +/** + * sets the autostart flag. + * the default (1) will cause the convo to autostart when an operation is added + * otherwise you will have to manually call convo->start() + */ +set_auto_start(int s) { + auto_start = s; + return 1; +} +int query_auto_start() { return auto_start; } + +/** + * (Optional) + * Sets the object in which the "on_convoFinished(convoId)" function + * function will be called when the conversation ends. + * @param ob - object or string + */ +set_onFinishOb(mixed ob) { + if (objectp(ob)) + onFinishOb = object_name(ob); + else + onFinishOb = ob; + return convoId; +} +query_onFinishOb() { return onFinishOb; } + +query_convoId() { return convoId; } + +/** + * Stops the current conversation. + * This will also fire on_convoFinished + * @param reason - the reason the convo was stopped + */ +public varargs stop(int reason) { + debug(sprintf("stop request convo %d reason %d", convoId, reason)); + + if (onFinishOb) { + onFinishOb->on_convoFinished(convoId, reason); + } + + queue_status=0; + while(remove_call_out("exec_next_op") != -1); + + if (destructOnEnd) { + destruct(TO); + } + + return 1; +} + +/********** CONVO OPS ***************/ + +/** + * Random Op + * execute a random op from a sub-convo + * @param subConvo - the convo object to execute a random op from + */ +private execOpRandom(object subConvo) { + debug(sprintf("random op %d", convoId)); + if (queue_status==1) { + mixed *ops = subConvo->query_queue(); + int opIdx = RANDBETWEEN(0,sizeof(ops)); + subConvo->execOp(ops[opIdx]); + } else { + debug(sprintf("convo stopped, aborting random op %d", convoId)); + subConvo->stop(); + } +} + +/** + * Repeat Op + * starts the convo over at the beginning, + * but does not erase the queue + */ +execOpRepeat() { + debug(sprintf("repeat convo %d", convoId)); + if (queue_status==1) { + lastIdx=0; + queue_status = 0; + start_internal(); + } else { + debug(sprintf("convo was stopped, skipping repeat %d", convoId)); + } +} + +/** + * Executes a conversation op + * @param op - the operation to execute + */ +public int execOp(mixed op) { + if (!owner) { + debug(sprintf("convo %d has no owner, aborting current op", convoId)); + stop(STOP_REASON_ABORTED); + return 0; + } else if (parentConvo && !parentConvo->query_isRunning()) { + debug(sprintf("convo %d parent has stopped, aborting current op", convoId)); + stop(STOP_REASON_ABORTED); + return 0; + } + + object room = ENV(owner); // get current room of monster + + if (op["closure"]) { + apply(op["closure"], room); + } else if (op["repeat"]) { + execOpRepeat(); + return 0; + } else if (op["delay"]) { + lastIdx++; + call_out("exec_next_op",op["delay"]); + return 0; + } else if (op["random"]) { + execOpRandom(op["random"]); + } else if (op["waitfor"]) { + // start timeout if there is one + while (remove_call_out("waitfor_ttlExpired") != -1); + int ttl = op["waitfor"]["matchset"]->query_ttl(); + if (ttl) { + call_out("waitfor_ttlExpired",ttl); + } + + // if there is a promptset, then start it + object ps = op["waitfor"]["promptset"]; + if (objectp(ps)) { + debug("got a promptset"); + ps->start_internal(); + currentPromptSet = ps; + } + + return 0; // exit here. catch_tell will advance index + } + + // check to make sure the op didn't stop the queue + if (queue_status) { + return 1; + } else { + debug(sprintf("Convo %d was stopped by op", convoId)); + if (destructOnEnd) { + destruct(TO); + } + } + + return 0; +} + +/** + * Executes the next op into the convo queue + */ +exec_next_op() { + debug(sprintf("convo=%d, exec next %d",convoId, lastIdx)); + + // exit if there is nothing left to do + if (!queue || lastIdx >= sizeof(queue)) { + if (lastIdx >= sizeof(queue)) { + // convo has finished + // notify any objects that are waiting + stop(); + } + + queue_status=0; + lastIdx=0; + queue = ({}); // cleanup + return; + } + + // get the next op and execute it + mixed op = queue[lastIdx]; + int execNext = execOp(op); + + // execute the next op if appropriate + if (execNext) { + lastIdx++; + exec_next_op(); + } +} + +public start_internal() { + + debug(sprintf("convo %d start. queue size is %d, status is %d",convoId, sizeof(queue), queue_status)); + if (queue_status) return 0; + + queue_status=1; + call_out("exec_next_op", 0); + + return queue_status; + +} + +/** + * Start the queue + */ +public start() { + return start_internal(); +} + +public try_auto_start() { + if (auto_start && !queue_status) { + debug(sprintf("convo %d autostarted", convoId)); + return start_internal(); + } + + return 0; +} + +/** + * this will stop execution of the existing conversation + * and clear any queued operations + */ +public restart() { + destructConvos(); + + queue_status = 0; + queue = ({}); + lastIdx = 0; + while(remove_call_out("exec_next_op") != -1); + + debug(sprintf("convo %d restarted", convoId)); + + return 1; +} + +/************ OP CREATION METHODS **************/ + +/** + * Speak operation + * Will display a message in the owner's current environment. + * The message will be prefixed with: [Short Name] says: + * @param s - the message to display + * @param to - an optional player name or object to speak to + */ +public varargs speak(string s, mixed to) { + if (to) { + object player; + if (stringp(to)) { + player = find_living(to); + } else if (objectp(to) && living(to)) { + player = to; + } else { + throw("to was not a string or object"); + } + + closure op = (: + if (ENV(player) == $1) { + string nm = owner->query_short(); + string pNm = player->query_name(); + string pfxO = sprintf("%s says [to you]: ", nm); + string pfxR = sprintf("%s says [to %s]: ", nm, pNm); + tell_object(to, sprintf("%s%s\n", pfxO, s), strlen(pfxO)); + tell_room( + $1, + sprintf("%s%s\n", pfxR, s), + ({ player, owner }), + strlen(pfxR) + ); + } + return 1; + :); + queue += ({ ([ "closure": op ]) }); + } else { + string pfx = sprintf("%s says: ", owner->query_short()); + string msg = sprintf("%s%s\n", pfx, s); + + closure op = (: tell_room($1, msg, ({ owner })), strlen(pfx) :); + queue += ({ ([ "closure": op ]) }); + } + + try_auto_start(); + return TO; +} + +/** + * Delay operation + * Pauses execution for t seconds + */ +public delay(int t) { + queue += ({ ([ "delay": t ]) }); + try_auto_start(); + return TO; +} + +/** + * Tell operation + * Will display message "s" to player "player" via a tell mechanism + * The message will be prefixed with: Short Name tells you: + */ +public tell(string player, string s) { + string msg = sprintf("%s tells you: %s\n", owner->query_short(), s); + + closure op = (: object p=find_player(player); if(p) { tell_object(p, msg); } return; :); + queue += ({ ([ "closure": op ]) }); + + try_auto_start(); + return TO; +} + +/** + * Emote operation + * The message e will be displayed in the owner's current environment. + * The message will be prefixed with: Short Name. + * Eg: + * ->emote("giggles") + * Output: + * Scarry Monster giggles + */ +public emote(string e) { + object room = ENV(owner); + string msg = sprintf("%s %s\n", owner->query_short(), e); + + closure op = (: tell_room($1, msg, ({ owner })) :); + queue += ({ ([ "closure": op ]) }); + + try_auto_start(); + return TO; +} + +/** + * Wait For Operation + * This will pause execution of the current convo queue until either + * a message has been received that matches one of the conditions in the + * matchset, or until the matchsets timeout has elapsed. + * + * An optional promptset can be provided which is a convo queue that will + * execute while the owner is waiting for an appropriate response. + * + * Note: For the wait_for operation to work correctly, the monster must pipe + * it's catch_tell specifically to the convo object. + * Example: + * if ("convo"::catch_tell(str)) { + * return 1; + * } else { + * return ::catch_tell(str); + * } + */ +public varargs wait_for(object _matchset, object _promptset) { + debug(sprintf("waitfor types: %d and %d", typeof(_matchset), typeof(_promptset))); + object *wf = ([ "matchset": _matchset ]); + + // TODO typecheck these objects + if (objectp(_promptset)) { + wf["promptset"] = _promptset; + } + + queue += ({ ([ "waitfor": wf ]) }); + + try_auto_start(); + return TO; +} + +/** + * Matchset operation. + * Creates a new matchset + */ +public matchset() { + object m = clone_object(MATCHSET_OB); + return m; +} + +/** + * Promptset operation + * Creates a new promptset, which is really just another convo object + */ +public promptset() { + debug("created prompset"); + // a promptset is just a convo + return convo(); +} + +/** + * Stop All operation + * Stops this convo and all upstream convo's + */ +public stopall() { + object currentConvo = TO; + closure op = (: TO->stop(STOP_REASON_STOPALL) :); + queue += ({ ([ "closure": op ]) }); + + return TO; +} + +/** + * Random operation + * Accepts a convo object as a parameter and will execute a random op from + * that sub-convo's queue. Note the ops in the sub-convo will not be executed + * sequentially. Only a single op will be executed. + */ +public random(object convo) { + + // TODO: typecheck the convo object + convo->set_parentConvo(TO); + queue += ({ ([ "random": convo ]) }); + + return TO; +} + +/** + * Repeat operation + * Causes the current convo queue to start over at the beginning. + * Make sure there is some sort of delay in your queue so that you don't + * flood the user with output + */ +public repeat() { + queue += ({ ([ "repeat": 1 ])}); + return TO; +} + +/** + * Convo operation + * Creates a new conversation queue + */ + convo() { + object c = clone_object(CONVO_OB); + c->set_owner(query_owner()); + c->set_parentConvo(TO); + c->set_auto_start(0); + + childConvos += ({ c }); + + return c; +} + +/** + * Function operation + * Will execute a function via call_other on the owner object. + */ +fn(string func, varargs mixed *args) { + + object currentConvo = TO; + closure op = (: call_other(owner, func, currentConvo, args...) :); + queue += ({ ([ "closure": op ]) }); + try_auto_start(); + return TO; + +} + +/** + * Execute a sub operation used by the wait_for mechanics + */ +execute_sub_op(mixed op) { + // what are we executing? + if (objectp(op)) debug(sprintf("exec subop type: %s", load_name(op))); + + if (objectp(op) && load_name(op)==CONVO_OB) { + debug("exec subop convo"); + // branch convo, execute and wait for response + waitForConvoToFinish = op->set_onFinishOb(TO); + op->start_internal(); + return 0; // exit here so the next op does not get executed + } else { + debug("unhandled subop"); + } + + lastIdx++; + exec_next_op(); + + return 1; +} + +/** + * called when the wait_for's ttl has expired + */ +waitfor_ttlExpired() { + catch_tell("***TTL_EXPIRED***"); +} + +/** + * Stops the current promptset, if there is one + */ +stop_promptSet() { + + debug("Stopping promptset"); + + if (currentPromptSet) { + currentPromptSet->stop(); + currentPromptSet = 0; + + debug("Promptset stopped"); + } + +} + +/** + * Handle incoming messages + */ +catch_tell(string str) { + debug("incoming tell: " + str); + + if (!TP || !interactive(TP)) { + debug("TP not interactive"); + return; + } + + if (!queue || lastIdx >= sizeof(queue)) { + debug("queue not running"); + queue_status=0; + return; + } + + // peak at the next op, if it a wait type and we're not waiting + // for a branch convo to finish, then proceed + object op = queue[lastIdx]; + if (op && mappingp(op) && op["waitfor"]) { + object matches = op["waitfor"]["matchset"]; + + // handle TTL expiration + if (matches->query_ttl() && str=="***TTL_EXPIRED***") { + debug("handling ttl expired"); + while (remove_call_out("waitfor_ttlExpired") != -1); + stop_promptSet(); + execute_sub_op(matches->query_ttl_op()); + } + + // only check if we're not waiting for a convo + if (!waitForConvoToFinish) { + foreach(mixed *m in matches->query_matchset()) { + string pat = m[0]; + mixed resp = m[1]; + if (sizeof(regexp(({str}), pat)) > 0) { + set_lastPlayer(TP); // store the player that interacted here + while (remove_call_out("waitfor_ttlExpired") != -1); + stop_promptSet(); + execute_sub_op(resp); + return; + } + } + + debug("no responses matched"); + } + } +} + +/** + * Handles the on_convoFinished fired from sub-convos (usually in the context) + * of a wait_for's promptset + */ +on_convoFinished(int convoId, int reason) { + debug(sprintf("onfinished %d reason %d",convoId, reason)); + if (waitForConvoToFinish==convoId) { + waitForConvoToFinish = 0; + if (reason == STOP_REASON_STOPALL) { + stop(STOP_REASON_STOPALL); + } else { + lastIdx++; + exec_next_op(); + } + } +} + +/** + * does some cleanup by destructing all child convos + */ +public destructConvos() { + foreach(object c in childConvos) { + if (c) c->destructConvos(); + destruct(c); + } + + childConvos -= ({ 0 }); +} \ No newline at end of file diff --git a/samples/LPC/globals.h b/samples/LPC/globals.h new file mode 100644 index 0000000000..4376f03372 --- /dev/null +++ b/samples/LPC/globals.h @@ -0,0 +1,15 @@ + +#ifndef GLOBAL_H + +#define GLOBAL_H + +#include +#include "../area.h" + +#define SETWRAP set_wrap_long(1) +#define WRAP(x) wrap_text(x) +#define AREAPATH "/d/area/" +#define ROOMPATH AREAPATH "rooms/" +#define TO this_object() + +#endif \ No newline at end of file diff --git a/samples/LPC/startroom.c b/samples/LPC/startroom.c new file mode 100644 index 0000000000..44a18e8e80 --- /dev/null +++ b/samples/LPC/startroom.c @@ -0,0 +1,53 @@ +inherit "room/room"; + +#include "globals.h" + +void reset(int arg) { + ::reset(arg); + + SETWRAP; + + if(!arg) { + + set_light(1); + no_castle_flag = 1; + + short_desc = "Along the edge of a field."; + long_desc = + "You are standing along the western edge of the fields where the " + + "city grows its crops. " + + "A long, straight path heading east has been cut deep in to the middle " + + "of the crops. " + + "Far off to the west you see the tree line of a forest." + ; + + dest_dir = ({ + "room/crop", "east", + ROOMPATH + "treeline.c", "west" + }); + + items = ({ + ({"crops", "crop","field"}), + WRAP( + "A large field of crops that extends east as far as the eye can see. "+ + "There is a path heading east, in to the field." + ), + ({"tree", "trees", "tree line"}), + "The edge of a forest made up of some species of pine tree." + }); + + search_items = ({ + ({"crop", "crops", "field"}), "It looks like corn.", + }); + + sounds = + ({ + "", "You can hear the breeze rustling through the stalks in the field.", + }); + + if (!present("bulletin board")) { + move_object(AREAPATH+"obj/board.c", TO); + } + } +} + diff --git a/vendor/README.md b/vendor/README.md index f2b1a53f67..dcc523cfec 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -308,6 +308,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LFE:** [textmate/lisp.tmbundle](https://github.com/textmate/lisp.tmbundle) - **LLVM:** [whitequark/llvm.tmbundle](https://github.com/whitequark/llvm.tmbundle) - **LOLCODE:** [KrazIvan/LOLCODE-grammar-vscode](https://github.com/KrazIvan/LOLCODE-grammar-vscode) +- **LPC:** [jlchmura/lpc-language-server](https://github.com/jlchmura/lpc-language-server) - **LSL:** [textmate/secondlife-lsl.tmbundle](https://github.com/textmate/secondlife-lsl.tmbundle) - **LTspice Symbol:** [Alhadis/language-pcb](https://github.com/Alhadis/language-pcb) - **LabVIEW:** [textmate/xml.tmbundle](https://github.com/textmate/xml.tmbundle) diff --git a/vendor/licenses/git_submodule/lpc-language-server.dep.yml b/vendor/licenses/git_submodule/lpc-language-server.dep.yml new file mode 100644 index 0000000000..98cac3f6dc --- /dev/null +++ b/vendor/licenses/git_submodule/lpc-language-server.dep.yml @@ -0,0 +1,31 @@ +--- +name: lpc-language-server +version: 9ed1d0b84a088907b0da35b1722a98ea3e90eba5 +type: git_submodule +homepage: https://github.com/jlchmura/lpc-language-server.git +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2023-2024 John Chmura + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] From 3bde0c15ed65cff53575adb113f8e1f5011c1ea3 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 24 Dec 2024 09:17:50 -0500 Subject: [PATCH 03/12] add more lpc heuristics --- lib/linguist/heuristics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 4334cc318e..4cb460cc20 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -956,7 +956,7 @@ named_patterns: - '^[ \t]*#[ \t]*(?:precache|using_animtree)[ \t]*\(' json: '\A\s*[{\[]' key_equals_value: '^[^#!;][^=]*=' - lpc: '(mixed|nosave|clone_object)|(^inherit [\"A-Za-z][A-Za-z0-9_]*)|(((public|private|protected)\s+)?((varargs|nomask|noshadow)+\s+)?((string|int|mixed|object|mapping)\s+)?[A-Za-z][A-Za-z0-9_]*\()' + lpc: '(mixed|nosave|clone_object)|(this_player\(\))|(^inherit [\"A-Za-z][A-Za-z0-9_]*)|(((public|private|protected)\s+)?((varargs|nomask|noshadow)+\s+)?((string|int|mixed|object|mapping)\s+)?[A-Za-z][A-Za-z0-9_]*\()' m68k: - '(?im)\bmoveq(?:\.l)?\s+#(?:\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\s*d[0-7]\b' - '(?im)^\s*move(?:\.[bwl])?\s+(?:sr|usp),\s*[^\s]+' From 29a0af5aa6f51687df7460aa690c541b8fd33aa9 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 24 Dec 2024 09:26:27 -0500 Subject: [PATCH 04/12] add more LPC samples from language server --- samples/LPC/array2.c | 7 +++++++ samples/LPC/callOther3.c | 7 +++++++ samples/LPC/closures.c | 8 ++++++++ samples/LPC/uniontypes.c | 5 +++++ 4 files changed, 27 insertions(+) create mode 100644 samples/LPC/array2.c create mode 100644 samples/LPC/callOther3.c create mode 100644 samples/LPC/closures.c create mode 100644 samples/LPC/uniontypes.c diff --git a/samples/LPC/array2.c b/samples/LPC/array2.c new file mode 100644 index 0000000000..8a53b4c529 --- /dev/null +++ b/samples/LPC/array2.c @@ -0,0 +1,7 @@ +string* arr = ({ "a", "b", "c" }); +test() { + arr += ({ "d" }); + // should always allow subtracting 0 elements + arr -= ({ 0 }); + arr -= ({ "a" }); +} \ No newline at end of file diff --git a/samples/LPC/callOther3.c b/samples/LPC/callOther3.c new file mode 100644 index 0000000000..20f5e959fa --- /dev/null +++ b/samples/LPC/callOther3.c @@ -0,0 +1,7 @@ +test() { + /** @type {"object.c"*} */ + object *o = ({}); + + // call other on an array is legal in fluffos + o->query_number(); +} diff --git a/samples/LPC/closures.c b/samples/LPC/closures.c new file mode 100644 index 0000000000..9eef2ee3c7 --- /dev/null +++ b/samples/LPC/closures.c @@ -0,0 +1,8 @@ +test() { + int *arr = ({1, 2, 3}); + int *i = filter(arr, (: $1 > 1 :)); +} + +private object *apply_custom_filter(object *obs, function f, object tp) { + return filter(obs, (: (*$(f))($1, $(tp)) :)) ; +} \ No newline at end of file diff --git a/samples/LPC/uniontypes.c b/samples/LPC/uniontypes.c new file mode 100644 index 0000000000..3997f7e117 --- /dev/null +++ b/samples/LPC/uniontypes.c @@ -0,0 +1,5 @@ +// unionable types +public * testUnionable() { + tmp = 0; + return ({ 1, ({ "a" }) }); +} From 05e6eeaef8220ce61161eb3e30cf7f53a9e3c6dd Mon Sep 17 00:00:00 2001 From: jlchmura Date: Mon, 12 Jan 2026 16:27:01 -0500 Subject: [PATCH 05/12] fix ReDoS in lpc regex --- lib/linguist/heuristics.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 99751dad62..8b8445e8ad 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -167,7 +167,7 @@ disambiguations: rules: - language: LPC named_pattern: lpc - negative_pattern: '(?i)([ \t]*(unsigned|uint|enum)|^\s*\*[A-Za-z]|([A-Za-z][A-Za-z0-9_]*\[\]))' + negative_pattern: '(?i:(?:[ \t]*(?:unsigned|uint|enum)|^\s*\*[A-Za-z]|[A-Za-z][A-Za-z0-9_]*\[\]))' - language: C - extensions: ['.cairo'] rules: @@ -376,7 +376,7 @@ disambiguations: named_pattern: cpp - language: LPC named_pattern: lpc - negative_pattern: '(?i)([ \t]*(unsigned|uint|enum|extern|pragma mark|__inline__)|\s*\*\(|^\s*\*[A-Za-z]|([A-Za-z][A-Za-z0-9_]*\[\]))' + negative_pattern: '(?i:(?:^[ \t]*(?:unsigned|uint|enum|extern|pragma[ \t]+mark|__inline__)\b|[ \t]*\*\(|^[ \t]*\*[A-Za-z]|[A-Za-z][A-Za-z0-9_]*\[\]))' - language: C - extensions: ['.hh'] rules: @@ -1067,7 +1067,7 @@ named_patterns: - '^[ \t]*#[ \t]*(?:precache|using_animtree)[ \t]*\(' json: '\A\s*[{\[]' key_equals_value: '^[^#!;][^=]*=' - lpc: '(mixed|nosave|clone_object)|(this_player\(\))|(^inherit [\"A-Za-z][A-Za-z0-9_]*)|(((public|private|protected)\s+)?((varargs|nomask|noshadow)+\s+)?((string|int|mixed|object|mapping)\s+)?[A-Za-z][A-Za-z0-9_]*\()' + lpc: '(?:\bmixed\b|\bnosave\b|\bclone_object\b)|(?:\bthis_player\(\))|(?:^inherit\s+"?[A-Za-z][A-Za-z0-9_]*)|(?:^(?:(?:public|private|protected)\s+)?(?:(?:(?:varargs|nomask|noshadow)\s+)*)?(?:(?:string|int|mixed|object|mapping)\s+)?[A-Za-z][A-Za-z0-9_]*(?=\())' m68k: - '(?im)\bmoveq(?:\.l)?\s+#(?:\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\s*d[0-7]\b' - '(?im)^\s*move(?:\.[bwl])?\s+(?:sr|usp),\s*[^\s]+' From a6e270229bdd0bc32b433863a9f0977bd3b60047 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Mon, 12 Jan 2026 21:38:06 +0000 Subject: [PATCH 06/12] shorten lpc file that was too long --- samples/LPC/convo.c | 521 +------------------------------------------- 1 file changed, 1 insertion(+), 520 deletions(-) diff --git a/samples/LPC/convo.c b/samples/LPC/convo.c index 8a3849e09b..cfe1a3cbd2 100644 --- a/samples/LPC/convo.c +++ b/samples/LPC/convo.c @@ -110,525 +110,6 @@ query_onFinishOb() { return onFinishOb; } query_convoId() { return convoId; } -/** - * Stops the current conversation. - * This will also fire on_convoFinished - * @param reason - the reason the convo was stopped - */ -public varargs stop(int reason) { - debug(sprintf("stop request convo %d reason %d", convoId, reason)); - - if (onFinishOb) { - onFinishOb->on_convoFinished(convoId, reason); - } - - queue_status=0; - while(remove_call_out("exec_next_op") != -1); - - if (destructOnEnd) { - destruct(TO); - } - - return 1; -} - -/********** CONVO OPS ***************/ - -/** - * Random Op - * execute a random op from a sub-convo - * @param subConvo - the convo object to execute a random op from - */ -private execOpRandom(object subConvo) { - debug(sprintf("random op %d", convoId)); - if (queue_status==1) { - mixed *ops = subConvo->query_queue(); - int opIdx = RANDBETWEEN(0,sizeof(ops)); - subConvo->execOp(ops[opIdx]); - } else { - debug(sprintf("convo stopped, aborting random op %d", convoId)); - subConvo->stop(); - } -} - -/** - * Repeat Op - * starts the convo over at the beginning, - * but does not erase the queue - */ -execOpRepeat() { - debug(sprintf("repeat convo %d", convoId)); - if (queue_status==1) { - lastIdx=0; - queue_status = 0; - start_internal(); - } else { - debug(sprintf("convo was stopped, skipping repeat %d", convoId)); - } -} - -/** - * Executes a conversation op - * @param op - the operation to execute - */ -public int execOp(mixed op) { - if (!owner) { - debug(sprintf("convo %d has no owner, aborting current op", convoId)); - stop(STOP_REASON_ABORTED); - return 0; - } else if (parentConvo && !parentConvo->query_isRunning()) { - debug(sprintf("convo %d parent has stopped, aborting current op", convoId)); - stop(STOP_REASON_ABORTED); - return 0; - } - - object room = ENV(owner); // get current room of monster - - if (op["closure"]) { - apply(op["closure"], room); - } else if (op["repeat"]) { - execOpRepeat(); - return 0; - } else if (op["delay"]) { - lastIdx++; - call_out("exec_next_op",op["delay"]); - return 0; - } else if (op["random"]) { - execOpRandom(op["random"]); - } else if (op["waitfor"]) { - // start timeout if there is one - while (remove_call_out("waitfor_ttlExpired") != -1); - int ttl = op["waitfor"]["matchset"]->query_ttl(); - if (ttl) { - call_out("waitfor_ttlExpired",ttl); - } - - // if there is a promptset, then start it - object ps = op["waitfor"]["promptset"]; - if (objectp(ps)) { - debug("got a promptset"); - ps->start_internal(); - currentPromptSet = ps; - } - - return 0; // exit here. catch_tell will advance index - } - - // check to make sure the op didn't stop the queue - if (queue_status) { - return 1; - } else { - debug(sprintf("Convo %d was stopped by op", convoId)); - if (destructOnEnd) { - destruct(TO); - } - } - - return 0; -} - -/** - * Executes the next op into the convo queue - */ -exec_next_op() { - debug(sprintf("convo=%d, exec next %d",convoId, lastIdx)); - - // exit if there is nothing left to do - if (!queue || lastIdx >= sizeof(queue)) { - if (lastIdx >= sizeof(queue)) { - // convo has finished - // notify any objects that are waiting - stop(); - } - - queue_status=0; - lastIdx=0; - queue = ({}); // cleanup - return; - } - - // get the next op and execute it - mixed op = queue[lastIdx]; - int execNext = execOp(op); - - // execute the next op if appropriate - if (execNext) { - lastIdx++; - exec_next_op(); - } -} - -public start_internal() { - - debug(sprintf("convo %d start. queue size is %d, status is %d",convoId, sizeof(queue), queue_status)); - if (queue_status) return 0; - - queue_status=1; - call_out("exec_next_op", 0); - - return queue_status; - -} - -/** - * Start the queue - */ -public start() { - return start_internal(); -} - -public try_auto_start() { - if (auto_start && !queue_status) { - debug(sprintf("convo %d autostarted", convoId)); - return start_internal(); - } - - return 0; -} - -/** - * this will stop execution of the existing conversation - * and clear any queued operations - */ -public restart() { - destructConvos(); - - queue_status = 0; - queue = ({}); - lastIdx = 0; - while(remove_call_out("exec_next_op") != -1); - - debug(sprintf("convo %d restarted", convoId)); - - return 1; -} - -/************ OP CREATION METHODS **************/ - -/** - * Speak operation - * Will display a message in the owner's current environment. - * The message will be prefixed with: [Short Name] says: - * @param s - the message to display - * @param to - an optional player name or object to speak to - */ -public varargs speak(string s, mixed to) { - if (to) { - object player; - if (stringp(to)) { - player = find_living(to); - } else if (objectp(to) && living(to)) { - player = to; - } else { - throw("to was not a string or object"); - } - - closure op = (: - if (ENV(player) == $1) { - string nm = owner->query_short(); - string pNm = player->query_name(); - string pfxO = sprintf("%s says [to you]: ", nm); - string pfxR = sprintf("%s says [to %s]: ", nm, pNm); - tell_object(to, sprintf("%s%s\n", pfxO, s), strlen(pfxO)); - tell_room( - $1, - sprintf("%s%s\n", pfxR, s), - ({ player, owner }), - strlen(pfxR) - ); - } - return 1; - :); - queue += ({ ([ "closure": op ]) }); - } else { - string pfx = sprintf("%s says: ", owner->query_short()); - string msg = sprintf("%s%s\n", pfx, s); - - closure op = (: tell_room($1, msg, ({ owner })), strlen(pfx) :); - queue += ({ ([ "closure": op ]) }); - } - - try_auto_start(); - return TO; -} - -/** - * Delay operation - * Pauses execution for t seconds - */ -public delay(int t) { - queue += ({ ([ "delay": t ]) }); - try_auto_start(); - return TO; -} - -/** - * Tell operation - * Will display message "s" to player "player" via a tell mechanism - * The message will be prefixed with: Short Name tells you: - */ -public tell(string player, string s) { - string msg = sprintf("%s tells you: %s\n", owner->query_short(), s); - - closure op = (: object p=find_player(player); if(p) { tell_object(p, msg); } return; :); - queue += ({ ([ "closure": op ]) }); - - try_auto_start(); - return TO; -} - -/** - * Emote operation - * The message e will be displayed in the owner's current environment. - * The message will be prefixed with: Short Name. - * Eg: - * ->emote("giggles") - * Output: - * Scarry Monster giggles - */ -public emote(string e) { - object room = ENV(owner); - string msg = sprintf("%s %s\n", owner->query_short(), e); - - closure op = (: tell_room($1, msg, ({ owner })) :); - queue += ({ ([ "closure": op ]) }); - - try_auto_start(); - return TO; -} - -/** - * Wait For Operation - * This will pause execution of the current convo queue until either - * a message has been received that matches one of the conditions in the - * matchset, or until the matchsets timeout has elapsed. - * - * An optional promptset can be provided which is a convo queue that will - * execute while the owner is waiting for an appropriate response. - * - * Note: For the wait_for operation to work correctly, the monster must pipe - * it's catch_tell specifically to the convo object. - * Example: - * if ("convo"::catch_tell(str)) { - * return 1; - * } else { - * return ::catch_tell(str); - * } - */ -public varargs wait_for(object _matchset, object _promptset) { - debug(sprintf("waitfor types: %d and %d", typeof(_matchset), typeof(_promptset))); - object *wf = ([ "matchset": _matchset ]); - - // TODO typecheck these objects - if (objectp(_promptset)) { - wf["promptset"] = _promptset; - } - - queue += ({ ([ "waitfor": wf ]) }); - - try_auto_start(); - return TO; -} - -/** - * Matchset operation. - * Creates a new matchset - */ -public matchset() { - object m = clone_object(MATCHSET_OB); - return m; -} - -/** - * Promptset operation - * Creates a new promptset, which is really just another convo object - */ -public promptset() { - debug("created prompset"); - // a promptset is just a convo - return convo(); -} - -/** - * Stop All operation - * Stops this convo and all upstream convo's - */ -public stopall() { - object currentConvo = TO; - closure op = (: TO->stop(STOP_REASON_STOPALL) :); - queue += ({ ([ "closure": op ]) }); - - return TO; -} - -/** - * Random operation - * Accepts a convo object as a parameter and will execute a random op from - * that sub-convo's queue. Note the ops in the sub-convo will not be executed - * sequentially. Only a single op will be executed. - */ -public random(object convo) { - - // TODO: typecheck the convo object - convo->set_parentConvo(TO); - queue += ({ ([ "random": convo ]) }); - - return TO; -} - -/** - * Repeat operation - * Causes the current convo queue to start over at the beginning. - * Make sure there is some sort of delay in your queue so that you don't - * flood the user with output - */ -public repeat() { - queue += ({ ([ "repeat": 1 ])}); - return TO; -} - -/** - * Convo operation - * Creates a new conversation queue - */ - convo() { - object c = clone_object(CONVO_OB); - c->set_owner(query_owner()); - c->set_parentConvo(TO); - c->set_auto_start(0); - - childConvos += ({ c }); - - return c; -} - -/** - * Function operation - * Will execute a function via call_other on the owner object. - */ -fn(string func, varargs mixed *args) { - - object currentConvo = TO; - closure op = (: call_other(owner, func, currentConvo, args...) :); - queue += ({ ([ "closure": op ]) }); - try_auto_start(); - return TO; - -} - -/** - * Execute a sub operation used by the wait_for mechanics - */ -execute_sub_op(mixed op) { - // what are we executing? - if (objectp(op)) debug(sprintf("exec subop type: %s", load_name(op))); - - if (objectp(op) && load_name(op)==CONVO_OB) { - debug("exec subop convo"); - // branch convo, execute and wait for response - waitForConvoToFinish = op->set_onFinishOb(TO); - op->start_internal(); - return 0; // exit here so the next op does not get executed - } else { - debug("unhandled subop"); - } - - lastIdx++; - exec_next_op(); - - return 1; -} - -/** - * called when the wait_for's ttl has expired - */ -waitfor_ttlExpired() { - catch_tell("***TTL_EXPIRED***"); -} - -/** - * Stops the current promptset, if there is one - */ -stop_promptSet() { - - debug("Stopping promptset"); - - if (currentPromptSet) { - currentPromptSet->stop(); - currentPromptSet = 0; - - debug("Promptset stopped"); - } - -} - -/** - * Handle incoming messages - */ -catch_tell(string str) { - debug("incoming tell: " + str); - - if (!TP || !interactive(TP)) { - debug("TP not interactive"); - return; - } - - if (!queue || lastIdx >= sizeof(queue)) { - debug("queue not running"); - queue_status=0; - return; - } - - // peak at the next op, if it a wait type and we're not waiting - // for a branch convo to finish, then proceed - object op = queue[lastIdx]; - if (op && mappingp(op) && op["waitfor"]) { - object matches = op["waitfor"]["matchset"]; - - // handle TTL expiration - if (matches->query_ttl() && str=="***TTL_EXPIRED***") { - debug("handling ttl expired"); - while (remove_call_out("waitfor_ttlExpired") != -1); - stop_promptSet(); - execute_sub_op(matches->query_ttl_op()); - } - - // only check if we're not waiting for a convo - if (!waitForConvoToFinish) { - foreach(mixed *m in matches->query_matchset()) { - string pat = m[0]; - mixed resp = m[1]; - if (sizeof(regexp(({str}), pat)) > 0) { - set_lastPlayer(TP); // store the player that interacted here - while (remove_call_out("waitfor_ttlExpired") != -1); - stop_promptSet(); - execute_sub_op(resp); - return; - } - } - - debug("no responses matched"); - } - } -} - -/** - * Handles the on_convoFinished fired from sub-convos (usually in the context) - * of a wait_for's promptset - */ -on_convoFinished(int convoId, int reason) { - debug(sprintf("onfinished %d reason %d",convoId, reason)); - if (waitForConvoToFinish==convoId) { - waitForConvoToFinish = 0; - if (reason == STOP_REASON_STOPALL) { - stop(STOP_REASON_STOPALL); - } else { - lastIdx++; - exec_next_op(); - } - } -} - /** * does some cleanup by destructing all child convos */ @@ -639,4 +120,4 @@ public destructConvos() { } childConvos -= ({ 0 }); -} \ No newline at end of file +} From 518a7fc615dcb7ed39b508c25cd05dc85efdee04 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Mon, 12 Jan 2026 21:38:43 +0000 Subject: [PATCH 07/12] add new LPC sample --- samples/LPC/convoMatchset.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 samples/LPC/convoMatchset.c diff --git a/samples/LPC/convoMatchset.c b/samples/LPC/convoMatchset.c new file mode 100644 index 0000000000..163c1dd00f --- /dev/null +++ b/samples/LPC/convoMatchset.c @@ -0,0 +1,28 @@ +/* + * LDMud mob dialog system + * Copyright 2022-2024 John Chmura + * + */ +#include "../../jgambit.h" + +private mixed *responses = ({}); +private int ttl = 0; +private mixed ttl_op; + +public match(string pattern, mixed response) { + responses += ({ + ({ pattern, response }) + }); + + return TO; +} + +public varargs timeout(int n, mixed op) { + ttl = n; + ttl_op = op; + return TO; +} + +public query_ttl() { return ttl; } +public query_ttl_op() { return ttl_op; } +public query_matchset() { return responses; } From 32b35d854cb980286150a2d8f526030c86c2fc77 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Mon, 12 Jan 2026 16:46:19 -0500 Subject: [PATCH 08/12] delete LPC file that is covered in other samples --- samples/LPC/array2.c | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 samples/LPC/array2.c diff --git a/samples/LPC/array2.c b/samples/LPC/array2.c deleted file mode 100644 index 8a53b4c529..0000000000 --- a/samples/LPC/array2.c +++ /dev/null @@ -1,7 +0,0 @@ -string* arr = ({ "a", "b", "c" }); -test() { - arr += ({ "d" }); - // should always allow subtracting 0 elements - arr -= ({ 0 }); - arr -= ({ "a" }); -} \ No newline at end of file From dd360fdfe56ac2ad7c72fb7525025a86f383f1f3 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Mon, 12 Jan 2026 16:46:28 -0500 Subject: [PATCH 09/12] replace LPC compiler test w/ real world code --- samples/LPC/callOther3.c | 7 ------- samples/LPC/closures.c | 8 -------- samples/LPC/connection.c | 11 +++++++++++ samples/LPC/level_list.c | 30 ++++++++++++++++++++++++++++++ samples/LPC/players.c | 12 ++++++++++++ samples/LPC/uniontypes.c | 5 ----- 6 files changed, 53 insertions(+), 20 deletions(-) delete mode 100644 samples/LPC/callOther3.c delete mode 100644 samples/LPC/closures.c create mode 100644 samples/LPC/connection.c create mode 100644 samples/LPC/level_list.c create mode 100644 samples/LPC/players.c delete mode 100644 samples/LPC/uniontypes.c diff --git a/samples/LPC/callOther3.c b/samples/LPC/callOther3.c deleted file mode 100644 index 20f5e959fa..0000000000 --- a/samples/LPC/callOther3.c +++ /dev/null @@ -1,7 +0,0 @@ -test() { - /** @type {"object.c"*} */ - object *o = ({}); - - // call other on an array is legal in fluffos - o->query_number(); -} diff --git a/samples/LPC/closures.c b/samples/LPC/closures.c deleted file mode 100644 index 9eef2ee3c7..0000000000 --- a/samples/LPC/closures.c +++ /dev/null @@ -1,8 +0,0 @@ -test() { - int *arr = ({1, 2, 3}); - int *i = filter(arr, (: $1 > 1 :)); -} - -private object *apply_custom_filter(object *obs, function f, object tp) { - return filter(obs, (: (*$(f))($1, $(tp)) :)) ; -} \ No newline at end of file diff --git a/samples/LPC/connection.c b/samples/LPC/connection.c new file mode 100644 index 0000000000..0c460d9a5f --- /dev/null +++ b/samples/LPC/connection.c @@ -0,0 +1,11 @@ +/** + * query connection properties for a player + * @return a union type of either an int or an array of strings + */ +public * queryConnectionProperties() { + object p = this_player(); + if (!p) return 0; + + object conn = p->queryConnection(); + return conn->queryProperties(); +} diff --git a/samples/LPC/level_list.c b/samples/LPC/level_list.c new file mode 100644 index 0000000000..9379580010 --- /dev/null +++ b/samples/LPC/level_list.c @@ -0,0 +1,30 @@ +string short() { + return "A list of the top players" ; +} + +void long() { + cat("/SORT_LEVEL"); +} + +void init() { + add_action("read", "read"); +} + +int id(string str) { + return str == "list" || str == "top" || str == "top players" || + str == "list of top players" || str == "top list"; +} + +int read(string str) { + if (!id(str)) + return 0; + say(this_player()->query_name() + " reads the top list.\n"); + long(); + return 1; +} + +int query_weight() { return 1; } + +int get() { return 1; } + +int query_value() { return 5; } diff --git a/samples/LPC/players.c b/samples/LPC/players.c new file mode 100644 index 0000000000..9572f77169 --- /dev/null +++ b/samples/LPC/players.c @@ -0,0 +1,12 @@ + +function getPlayersByLevel(int minLevel) { + object* players = users(); + return filter(players, (: $1->query_level() >= minLevel :)); +} + +/** Schedule a shutdown for the near future. */ +void slow_shut_down (int minutes) { + filter(users(), #'tell_object, + "Game driver shouts: The memory is getting low !\n"); + "obj/shut"->shut(minutes); +} diff --git a/samples/LPC/uniontypes.c b/samples/LPC/uniontypes.c deleted file mode 100644 index 3997f7e117..0000000000 --- a/samples/LPC/uniontypes.c +++ /dev/null @@ -1,5 +0,0 @@ -// unionable types -public * testUnionable() { - tmp = 0; - return ({ 1, ({ "a" }) }); -} From a0e4a6b88939fdd79fdcee7a8134c5ae1b0adef6 Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 13 Jan 2026 12:15:03 -0500 Subject: [PATCH 10/12] from LPC from .h heuristic --- lib/linguist/heuristics.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index a5feb4c048..61eb65fa66 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -384,10 +384,7 @@ disambiguations: - language: Objective-C named_pattern: objectivec - language: C++ - named_pattern: cpp - - language: LPC - named_pattern: lpc - negative_pattern: '(?i:(?:^[ \t]*(?:unsigned|uint|enum|extern|pragma[ \t]+mark|__inline__)\b|[ \t]*\*\(|^[ \t]*\*[A-Za-z]|[A-Za-z][A-Za-z0-9_]*\[\]))' + named_pattern: cpp - language: C - extensions: ['.hh'] rules: From db1d68c541d8566da54db66482485cd98dc5eb9e Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 13 Jan 2026 13:30:13 -0500 Subject: [PATCH 11/12] simplify LPC patterns and fix redos --- lib/linguist/heuristics.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 61eb65fa66..0e3eee5d05 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -178,7 +178,6 @@ disambiguations: rules: - language: LPC named_pattern: lpc - negative_pattern: '(?i:(?:[ \t]*(?:unsigned|uint|enum)|^\s*\*[A-Za-z]|[A-Za-z][A-Za-z0-9_]*\[\]))' - language: C - extensions: ['.cairo'] rules: @@ -385,6 +384,8 @@ disambiguations: named_pattern: objectivec - language: C++ named_pattern: cpp + - language: LPC + named_pattern: lpc - language: C - extensions: ['.hh'] rules: @@ -1087,7 +1088,9 @@ named_patterns: - '^[ \t]*#[ \t]*(?:precache|using_animtree)[ \t]*\(' json: '\A\s*[{\[]' key_equals_value: '^[^#!;][^=]*=' - lpc: '(?:\bmixed\b|\bnosave\b|\bclone_object\b)|(?:\bthis_player\(\))|(?:^inherit\s+"?[A-Za-z][A-Za-z0-9_]*)|(?:^(?:(?:public|private|protected)\s+)?(?:(?:(?:varargs|nomask|noshadow)\s+)*)?(?:(?:string|int|mixed|object|mapping)\s+)?[A-Za-z][A-Za-z0-9_]*(?=\())' + lpc: + - '(?m)^[ \t]*inherit\s+"[^"\r\n]+"' + - '\b(?:this_player|this_object|previous_object|clone_object|call_out|remove_call_out|notify_fail|seteuid|getuid)\s*\(' m68k: - '(?im)\bmoveq(?:\.l)?\s+#(?:\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\s*d[0-7]\b' - '(?im)^\s*move(?:\.[bwl])?\s+(?:sr|usp),\s*[^\s]+' From 02c2d417c84932d830fb39f6601376f9064a15de Mon Sep 17 00:00:00 2001 From: jlchmura Date: Tue, 13 Jan 2026 14:34:26 -0500 Subject: [PATCH 12/12] - tweaks to lpc patterns - add real area_env.h file --- lib/linguist/heuristics.yml | 5 ++++- samples/LPC/area_env.h | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/linguist/heuristics.yml b/lib/linguist/heuristics.yml index 0e3eee5d05..a1e6eaf810 100644 --- a/lib/linguist/heuristics.yml +++ b/lib/linguist/heuristics.yml @@ -1090,7 +1090,10 @@ named_patterns: key_equals_value: '^[^#!;][^=]*=' lpc: - '(?m)^[ \t]*inherit\s+"[^"\r\n]+"' - - '\b(?:this_player|this_object|previous_object|clone_object|call_out|remove_call_out|notify_fail|seteuid|getuid)\s*\(' + - '\b(?:this_player|this_object|previous_object|clone_object|call_out|remove_call_out|notify_fail|seteuid|getuid|find_player|tell_object|objectp|environment|present)\s*\(' + - '(?m)^[ \t]*(?:private|public|protected)\s+(?:static\s+)?object\b' + - '(?m)^[ \t]*(?:(?:public|private|protected)\s*)?(?:(?:mixed|object|int|string|mapping)\s*)?\*?(?:[A-Za-z_][A-Za-z0-9_]*\s*=)\s*\(\{.*\}\)' + - '(?mi)"[a-z0-9_/]+"\s*->\s*[a-z][a-z0-9_]*\s*\(' m68k: - '(?im)\bmoveq(?:\.l)?\s+#(?:\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\s*d[0-7]\b' - '(?im)^\s*move(?:\.[bwl])?\s+(?:sr|usp),\s*[^\s]+' diff --git a/samples/LPC/area_env.h b/samples/LPC/area_env.h index ba512cb276..213cef70fd 100644 --- a/samples/LPC/area_env.h +++ b/samples/LPC/area_env.h @@ -1,4 +1,16 @@ +int minLevel; + +public int query_minLevel() { + return minLevel; +} +public void set_minLevel(int level) { + minLevel = level; +} public string query_label() { return "Area Label"; } + +public string canEnterArea() { + return this_player()->query_level() < minLevel ? "You are not experienced enough to enter this area." : 0; +}