-
-
Save PMArkive/d46987d3eafe56d2cac3fd242d8484b2 to your computer and use it in GitHub Desktop.
a VScript-like scripting language exclusively made for SourcePawn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * cfgscript.inc | |
| * | |
| * Copyright [2023] Assyrianic aka Nergal | |
| * | |
| * 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 ANDNONINFRINGEMENT. | |
| * | |
| * 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. | |
| * | |
| */ | |
| #if defined _cfgscript_included | |
| #endinput | |
| #endif | |
| #define _cfgscript_included | |
| #include <adt> | |
| #include <datapack> | |
| #include <plugin_utils> | |
| enum { | |
| OBJ_NIL, | |
| OBJ_INT, | |
| OBJ_FLT, | |
| OBJ_STR, | |
| OBJ_TAB, | |
| }; | |
| enum struct CSLObj { | |
| any val; | |
| int size; | |
| int tag; | |
| void init(int tag) { | |
| if( tag==OBJ_STR ) { | |
| DataPack dp = new DataPack(); | |
| this.val = dp; | |
| } else if( tag==OBJ_TAB ) { | |
| StringMap map = new StringMap(); | |
| this.val = map; | |
| } | |
| this.tag = tag; | |
| } | |
| void kill() { | |
| if( tag==OBJ_STR || tag==OBJ_TAB ) { | |
| Handle h = this.val; | |
| delete h; | |
| } | |
| this.val = 0; | |
| this.size = 0; | |
| this.tag = 0; | |
| } | |
| bool IsNil() { | |
| return this.tag==OBJ_NIL; | |
| } | |
| bool GetInt(int& i) { | |
| if( this.tag != OBJ_INT ) { | |
| return false; | |
| } | |
| i = this.val; | |
| return true; | |
| } | |
| bool GetFloat(float& f) { | |
| if( this.tag != OBJ_FLT ) { | |
| return false; | |
| } | |
| f = this.val; | |
| return true; | |
| } | |
| int GetStrLen() { | |
| if( this.tag != OBJ_STR ) { | |
| return 0; | |
| } | |
| return this.size; | |
| } | |
| bool GetStr(char[] buf, int len) { | |
| if( this.tag != OBJ_STR ) { | |
| return false; | |
| } | |
| DataPack dp = this.val; | |
| if( dp==null ) { | |
| return false; | |
| } | |
| dp.Reset(); | |
| dp.ReadString(buf, len); | |
| return true; | |
| } | |
| StringMap GetMap() { | |
| if( this.tag != OBJ_TAB ) { | |
| return null; | |
| } | |
| StringMap map = this.val; | |
| return( map==null )? null : map; | |
| } | |
| bool SetInt(int i) { | |
| if( this.tag != OBJ_INT ) { | |
| return false; | |
| } | |
| this.val = i; | |
| return true; | |
| } | |
| bool SetFloat(float f) { | |
| if( this.tag != OBJ_FLT ) { | |
| return false; | |
| } | |
| this.val = f; | |
| return true; | |
| } | |
| bool SetString(const char[] str, int len=0) { | |
| if( this.tag != OBJ_STR ) { | |
| return false; | |
| } | |
| DataPack dp = this.val; | |
| dp.Reset(true); | |
| dp.WriteString(str); | |
| if( len <= 0 ) { | |
| len = strlen(str); | |
| } | |
| this.size = len + 1; | |
| return true; | |
| } | |
| bool SetMap(StringMap map) { | |
| if( this.tag != OBJ_TAB ) { | |
| return false; | |
| } | |
| this.val = map; | |
| return true; | |
| } | |
| } | |
| enum struct CSLFunc { | |
| StringMap vars; /// map[string]CSLObj | |
| StringMap params; /// map[string]CSLObj | |
| ArrayList instrs; | |
| void init() { | |
| } | |
| /// map[VarName]Value | |
| bool Invoke(StringMap params, CSLObj ret) { | |
| this.params = params; | |
| return true; | |
| } | |
| } | |
| enum struct CSLScript { | |
| StringMap funcs; | |
| ArrayList strs; /// []string, to refer to strings by index. | |
| void init() { | |
| this.funcs = new StringMap(); | |
| } | |
| bool GetFunc(const char[] name, CSLFunc buffer) { | |
| return this.funcs.GetArray(name, buffer, sizeof(buffer)); | |
| } | |
| bool Invoke(const char[] name, const CSLObj[] args, int len_args, CSLObj ret) { | |
| return false; | |
| } | |
| } | |
| enum struct CSLSys { | |
| StringMap scripts; | |
| StringMap builtins; /// map[string]FuncObj | |
| void init() { | |
| this.scripts = new StringMap(); | |
| this.builtins = new StringMap(); | |
| } | |
| } | |
| /// ---------- LEXER ---------- | |
| /* | |
| stock bool _IsCharNumeric(int c) { | |
| return( c >= '0' && c <= '9' ); | |
| } | |
| stock bool _IsCharAlpha(int c) { | |
| return( c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' ); | |
| } | |
| stock bool _StrEqual(const char[] a, const char[] b) { | |
| int i, j; | |
| while( a[i] != 0 && b[j] != 0 && a[i]==b[j] ) { | |
| i++; j++; | |
| } | |
| return i - j; | |
| } | |
| */ | |
| enum { | |
| TokenInvalid, | |
| /// literals | |
| TokenInt, /// 1, 3, 0x2f | |
| TokenFloat, /// 0.4 | |
| TokenString, /// "abc", 'abc' | |
| TokenIdent, /// a, b, i, j | |
| /// keywords | |
| TokenIf, TokenElse, TokenFor, TokenIn, | |
| /// delimiters | |
| TokenLParen, TokenRParen, /// () | |
| TokenLBrack, TokenRBrack, /// {} | |
| TokenComma, TokenColon, /// , : | |
| /// operators | |
| TokenAdd, /// + | |
| TokenSub, /// - | |
| TokenMul, /// * | |
| TokenDiv, /// / | |
| TokenMod, /// % | |
| TokenPow, /// ** | |
| TokenDot, /// . | |
| TokenNot, /// ! | |
| TokenLSH, /// << | |
| TokenRSHA, /// >> | |
| TokenRSHL, /// >>> | |
| TokenXor, /// ^ | |
| TokenAnd, /// & | |
| TokenAndXor, /// &^ | |
| TokenOr, /// | | |
| TokenAssign, /// = | |
| TokenDecl, /// := | |
| TokenCmp, /// == | |
| TokenNotCmp, /// != | |
| TokenLT, /// < | |
| TokenLE, /// <= | |
| TokenGT, /// > | |
| TokenGE, /// >= | |
| TokenInc, /// ++ | |
| TokenDec, /// -- | |
| TokenLogicAnd, /// && | |
| TokenLogicOr, /// || | |
| TokenAddAssign, /// += | |
| TokenSubAssign, /// -= | |
| TokenMulAssign, /// *= | |
| TokenDivAssign, /// /= | |
| TokenModAssign, /// %= | |
| TokenPowAssign, /// **= | |
| TokenLSHAssign, /// <<= | |
| TokenRSHAAssign, /// >>= | |
| TokenRSHLAssign, /// >>>= | |
| TokenXorAssign, /// ^= | |
| TokenAndAssign, /// &= | |
| TokenAndXorAssign, /// &^= | |
| TokenOrAssign, /// |= | |
| }; | |
| enum { | |
| LEXEME_SIZE = 64, | |
| FLAG_DOT = 1, | |
| }; | |
| enum struct CSLToken { | |
| int size; | |
| int tag; | |
| any val; | |
| char lexeme[LEXEME_SIZE]; | |
| } | |
| enum struct CSLLexState { | |
| CSLToken curr_tok; | |
| int src_idx; | |
| bool LexBinary(const char[] src) { | |
| while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx])) ) { | |
| switch( src[this.src_idx] ) { | |
| case '0', '1': { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| } | |
| default: { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| LogError("ConfigScript :: invalid binary literal: '%s'", this.curr_tok.lexeme); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool LexHex(const char[] src) { | |
| while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx]) || IsCharAlpha(src[this.src_idx])) ) { | |
| switch( src[this.src_idx] ) { | |
| case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', | |
| 'a', 'b', 'c', 'd', 'e', 'f', | |
| 'A', 'B', 'C', 'D', 'E', 'F': { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| } | |
| default: { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| LogError("ConfigScript :: invalid hex literal: '%s'", this.curr_tok.lexeme); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool LexDec(const char[] src, bool& is_flt) { | |
| int lit_flags = 0; | |
| while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx]) || src[this.src_idx]=='.') ) { | |
| switch( src[this.src_idx] ) { | |
| case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| } | |
| case '.': { | |
| if( lit_flags & FLAG_DOT ) { | |
| LogError("ConfigScript :: extra dot in decimal literal"); | |
| return false; | |
| } | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| lit_flags |= FLAG_DOT; | |
| is_flt = true; | |
| } | |
| default: { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| LogError("ConfigScript :: invalid decimal literal: '%s'", this.curr_tok.lexeme); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void GetToken(const char[] src, int src_len) { | |
| CSLToken empty; | |
| this.curr_tok = empty; | |
| while( this.src_idx < src_len ) { | |
| switch( src[this.src_idx] ) { | |
| case ' ', '\t', '\n': { | |
| this.src_idx++; | |
| continue; | |
| } | |
| case '#': { | |
| /// single-line comment | |
| while( this.src_idx < src_len && src[this.src_idx] != '\n' ) { | |
| this.src_idx++; | |
| } | |
| this.src_idx++; | |
| continue; | |
| } | |
| case '0': { /// possible hex, octal, binary, or float. | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| switch( src[this.src_idx] ) { | |
| case 'b', 'B': { | |
| /// Binary. | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| if( this.LexBinary(src) ) { | |
| this.curr_tok.val = StringToInt(this.curr_tok.lexeme[2], 2); | |
| } | |
| this.curr_tok.tag = TokenInt; | |
| return; | |
| } | |
| case 'x', 'X': { | |
| /// Hex. | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| if( this.LexHex(src) ) { | |
| this.curr_tok.val = StringToInt(this.curr_tok.lexeme, 16); | |
| } | |
| this.curr_tok.tag = TokenInt; | |
| return; | |
| } | |
| case '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': { | |
| /// Decimal/Float. | |
| bool is_float = false; | |
| if( this.LexDec(src, is_float) ) { | |
| this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme); | |
| this.curr_tok.tag = is_float? TokenFloat : TokenInt; | |
| } | |
| return; | |
| } | |
| default: { | |
| this.curr_tok.tag = TokenInt; | |
| return; | |
| } | |
| } | |
| } | |
| case '.': { | |
| if( this.src_idx+1 < src_len && IsCharNumeric(src[this.src_idx+1]) ) { | |
| /// Decimal/Float. | |
| bool is_float = false; | |
| if( this.LexDec(src, is_float) ) { | |
| this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme); | |
| this.curr_tok.tag = is_float? TokenFloat : TokenInt; | |
| } | |
| } else { | |
| this.curr_tok.tag = TokenDot; | |
| this.src_idx++; | |
| } | |
| return; | |
| } | |
| case '1', '2', '3', '4', '5', '6', '7', '8', '9': { | |
| bool is_float = false; | |
| if( this.LexDec(src, is_float) ) { | |
| this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme); | |
| this.curr_tok.tag = is_float? TokenFloat : TokenInt; | |
| } | |
| return; | |
| } | |
| case '\'', '"': { | |
| char quote = src[this.src_idx]; | |
| this.src_idx++; | |
| while( src[this.src_idx] != 0 && src[this.src_idx] != quote ) { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenString; | |
| return; | |
| } | |
| case 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', '_', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z': { | |
| while( this.src_idx < src_len && (IsCharAlpha(src[this.src_idx]) || src[this.src_idx]=='_') ) { | |
| this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++]; | |
| } | |
| if( StrEqual(this.curr_tok.lexeme, "if") ) { | |
| this.curr_tok.tag = TokenIf; | |
| } else if( StrEqual(this.curr_tok.lexeme, "else") ) { | |
| this.curr_tok.tag = TokenElse; | |
| } else if( StrEqual(this.curr_tok.lexeme, "in") ) { | |
| this.curr_tok.tag = TokenIn; | |
| } else if( StrEqual(this.curr_tok.lexeme, "for") ) { | |
| this.curr_tok.tag = TokenFor; | |
| } else { | |
| this.curr_tok.tag = TokenIdent; | |
| } | |
| return; | |
| } | |
| case '(': { | |
| this.curr_tok.tag = TokenLParen; | |
| this.src_idx++; | |
| return; | |
| } | |
| case ')': { | |
| this.curr_tok.tag = TokenRParen; | |
| this.src_idx++; | |
| return; | |
| } | |
| case ',': { | |
| this.curr_tok.tag = TokenComma; | |
| this.src_idx++; | |
| return; | |
| } | |
| case '{': { | |
| this.curr_tok.tag = TokenLBrack; | |
| this.src_idx++; | |
| return; | |
| } | |
| case '}': { | |
| this.curr_tok.tag = TokenRBrack; | |
| this.src_idx++; | |
| return; | |
| } | |
| case '+': { | |
| if( src[this.src_idx+1]=='+' ) { | |
| this.curr_tok.tag = TokenInc; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenAddAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenAdd; | |
| return; | |
| } | |
| case '-': { | |
| if( src[this.src_idx+1]=='-' ) { | |
| this.curr_tok.tag = TokenDec; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenSubAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenSub; | |
| return; | |
| } | |
| case '*': { | |
| if( src[this.src_idx+1]=='*' ) { | |
| if( src[this.src_idx+2]=='=' ) { | |
| this.curr_tok.tag = TokenPowAssign; | |
| this.src_idx += 3; | |
| return; | |
| } | |
| this.curr_tok.tag = TokenPow; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenMulAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenMul; | |
| return; | |
| } | |
| case '/': { | |
| if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenDivAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenDiv; | |
| return; | |
| } | |
| case '!': { | |
| if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenNotCmp; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenNot; | |
| return; | |
| } | |
| case '<': { | |
| if( src[this.src_idx+1]=='<' ) { | |
| if( src[this.src_idx+2]=='=' ) { | |
| this.curr_tok.tag = TokenLSHAssign; | |
| this.src_idx += 3; | |
| return; | |
| } | |
| this.curr_tok.tag = TokenLSH; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenLE; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenLT; | |
| return; | |
| } | |
| case '>': { | |
| if( src[this.src_idx+1]=='>' ) { | |
| if( src[this.src_idx+2]=='=' ) { | |
| this.curr_tok.tag = TokenRSHAAssign; | |
| this.src_idx += 3; | |
| return; | |
| } else if( src[this.src_idx+2]=='>' ) { | |
| if( src[this.src_idx+3]=='=' ) { | |
| this.curr_tok.tag = TokenRSHLAssign; | |
| this.src_idx += 4; | |
| return; | |
| } | |
| this.curr_tok.tag = TokenRSHL; | |
| this.src_idx += 3; | |
| return; | |
| } | |
| this.curr_tok.tag = TokenRSHA; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenGE; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenGT; | |
| return; | |
| } | |
| case '|': { | |
| if( src[this.src_idx+1]=='|' ) { | |
| this.curr_tok.tag = TokenLogicOr; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenOrAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenOr; | |
| return; | |
| } | |
| case '&': { | |
| if( src[this.src_idx+1]=='&' ) { | |
| this.curr_tok.tag = TokenLogicAnd; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenAndAssign; | |
| this.src_idx += 2; | |
| return; | |
| } else if( src[this.src_idx+1]=='^' ) { | |
| if( src[this.src_idx+2]=='=' ) { | |
| this.curr_tok.tag = TokenAndXorAssign; | |
| this.src_idx += 3; | |
| return; | |
| } | |
| this.curr_tok.tag = TokenAndXor; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenAnd; | |
| return; | |
| } | |
| case '^': { | |
| if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenXorAssign; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenXor; | |
| return; | |
| } | |
| case ':': { | |
| if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenDecl; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenColon; | |
| return; | |
| } | |
| case '=': { | |
| if( src[this.src_idx+1]=='=' ) { | |
| this.curr_tok.tag = TokenCmp; | |
| this.src_idx += 2; | |
| return; | |
| } | |
| this.src_idx++; | |
| this.curr_tok.tag = TokenAssign; | |
| return; | |
| } | |
| default: { | |
| this.src_idx++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// -------------- PARSER -------------- | |
| /** | |
| * CSL "Config Scripting Language" grammar: | |
| * | |
| * script = +(func | stmt) . | |
| * func = ident '(' *names ')' block . | |
| * names = ident *( ',' ident ) . | |
| * block = '{' +stmt '}' . | |
| * | |
| * stmt = if | while | decl | assign | inc | expr_stmt. | |
| * if = 'if' expr block [ 'else' (if | block) ] . | |
| * for = 'for' expr block . | |
| * decl = idents ':=' exprs . | |
| * assign = idents ( 'op=' | '=' ) exprs . | |
| * inc = ident ( '++' | '--' ) . | |
| * expr_stmt = expr . | |
| * | |
| * exprs = expr *( ',' expr ) . | |
| * expr = in_expr . | |
| * in_expr = or_expr *( 'in' or_expr ) . | |
| * or_expr = and_expr *( '||' and_expr ) . | |
| * and_expr = cmp_expr *( '&&' cmp_expr ) . | |
| * cmp_expr = add_expr *( ('==' | '!=' | '<' | '<=' | '>' | '>=') add_expr ) . | |
| * add_expr = mul_expr *( ('+' | '-' | '|' | '^') mul_expr ) . | |
| * mul_expr = pow_expr *( ('*' | '/' | '<<' | '>>' | '>>>' | '&' | '&^') pow_expr ) . | |
| * pow_expr = pre_expr *( '**' pre_expr ) . | |
| * pre_expr = ( "+" | "-" | "!" | "^" ) post_expr . | |
| * post_expr = factor [ '(' expr ')' | '.' post_expr ] . | |
| * factor = number | ident | '(' expr ')' . | |
| * comment = '#' ... . | |
| * | |
| ```py | |
| # comment. | |
| on_hurt_player(event, players) { | |
| player := get_uid(event.userid) | |
| player.dmg += event.damageamount | |
| } | |
| ``` | |
| */ | |
| enum struct CSLParser { | |
| CSLLexState lexer; | |
| StringMap vars; /// map[string]CSLObj | |
| StringMap funcs; /// map[string]CSLObj | |
| void init(const char[] src, int src_len) { | |
| this.lexer.GetToken(src, src_len); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment