Last active
July 29, 2023 18:50
-
-
Save assyrianic/7032a8b003a1b617cde9c8ffef8974db 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 <plugin_utils> | |
enum { | |
OBJ_NIL, | |
OBJ_INT, | |
OBJ_FLT, | |
OBJ_STR, | |
OBJ_TAB, | |
OBJ_FNC, | |
//OBJ_TUP, /// tuple type. | |
OBJ_VEC, | |
}; | |
typeset SPFFIFunc { | |
/// for general functions: | |
/// math.sin(x) | |
/// mod.fn(a,b,c,d) | |
function void(ArrayList csl_obj_args, CSLObj retval); | |
/// for binary operator style functions: | |
/// a do_thing b | |
function void(const CSLObj a, const CSLObj b, CSLObj retval); | |
}; | |
enum struct CSLObj { | |
float vec[3]; | |
any val; | |
SPFFIFunc f; | |
int size; | |
int tag; | |
void Init(int tag) { | |
if( tag==OBJ_STR || tag==OBJ_TAB ) { | |
StringMap map = new StringMap(); | |
this.val = map; | |
} | |
this.tag = tag; | |
} | |
void Destroy(bool deep=false) { | |
if( this.tag==OBJ_STR || this.tag==OBJ_TAB ) { | |
if( deep && OBJ_TAB ) { | |
StringMap h = this.val; | |
StringMapSnapshot snap = h.Snapshot(); | |
if( snap != null ) { | |
int len = snap.Length; | |
for( int i; i < len; i++ ) { | |
int keysize = snap.KeyBufferSize(i) + 1; | |
char[] key = new char[keysize]; | |
snap.GetKey(i, key, keysize); | |
CSLObj a; h.GetArray(key, a, sizeof(a)); | |
a.Destroy(true); | |
} | |
delete snap; | |
} | |
} | |
Handle h = this.val; | |
delete h; | |
} | |
this.val = 0; | |
this.size = 0; | |
this.tag = 0; | |
this.f = INVALID_FUNCTION; | |
for( int i; i < 3; i++ ) { | |
this.vec[i] = 0.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=0) { | |
if( this.tag != OBJ_STR ) { | |
return false; | |
} | |
StringMap map = this.val; | |
if( map==null ) { | |
return false; | |
} | |
if( len==0 ) { | |
len = this.size - 1; | |
} | |
map.GetString("str", buf, len); | |
return true; | |
} | |
StringMap GetMap() { | |
if( this.tag != OBJ_TAB ) { | |
return null; | |
} | |
StringMap map = this.val; | |
return( map==null )? null : map; | |
} | |
SPFFIFunc GetFunc() { | |
if( this.tag != OBJ_FNC ) { | |
return INVALID_FUNCTION; | |
} | |
return this.f; | |
} | |
bool GetVec(float buf[3]) { | |
if( this.tag != OBJ_VEC ) { | |
return false; | |
} | |
buf = this.vec; | |
return true; | |
} | |
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 SetStr(const char[] str, int len=0) { | |
if( this.tag != OBJ_STR ) { | |
return false; | |
} | |
StringMap map = this.val; | |
map.SetString("str", 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; | |
} | |
bool SetFunc(SPFFIFunc f=INVALID_FUNCTION, Handle pl_of_f=null) { | |
if( this.tag != OBJ_FNC ) { | |
return false; | |
} | |
this.f = f; | |
this.val = pl_of_f; | |
return true; | |
} | |
bool SetVec(const float v[3]) { | |
if( this.tag != OBJ_VEC ) { | |
return false; | |
} | |
this.vec = v; | |
return true; | |
} | |
bool IsArithmeticType() { | |
return( this.tag==OBJ_INT || this.tag==OBJ_FLT ); | |
} | |
bool AreSameType(CSLObj b) { | |
return( this.tag==b.tag ); | |
} | |
bool AreIntType(CSLObj b) { | |
return( this.tag==OBJ_INT && b.tag==OBJ_INT ); | |
} | |
bool AreFloatType(CSLObj b) { | |
return( this.tag==OBJ_FLT && b.tag==OBJ_FLT ); | |
} | |
bool AreStrType(CSLObj b) { | |
return( this.tag==OBJ_STR && b.tag==OBJ_STR ); | |
} | |
bool AreMapType(CSLObj b) { | |
return( this.tag==OBJ_TAB && b.tag==OBJ_TAB ); | |
} | |
bool AreFuncType(CSLObj b) { | |
return( this.tag==OBJ_FNC && b.tag==OBJ_FNC ); | |
} | |
bool AreVecType(CSLObj b) { | |
return( this.tag==OBJ_VEC && b.tag==OBJ_VEC ); | |
} | |
bool AreArithmetible(CSLObj b) { | |
return( this.IsArithmeticType() && b.IsArithmeticType() ); | |
} | |
bool ConvertToFloat() { | |
if( this.tag==OBJ_FLT ) { | |
return true; | |
} | |
switch( this.tag ) { | |
case OBJ_NIL: { | |
this.tag = OBJ_FLT; | |
float f = 0.0; | |
this.val = f; | |
} | |
case OBJ_INT: { | |
this.tag = OBJ_FLT; | |
int i = this.val; | |
float f = float(i); | |
this.val = f; | |
} | |
case OBJ_STR: { | |
char[] str = new char[this.size + 1]; | |
StringMap str_data = this.val; | |
str_data.GetString("str", str, this.size); | |
float f = StringToFloat(str); | |
delete str_data; | |
this.val = f; | |
this.size = 0; | |
this.tag = OBJ_FLT; | |
} | |
case OBJ_TAB, OBJ_FNC: { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool ConvertToInt() { | |
if( this.tag==OBJ_INT ) { | |
return true; | |
} | |
switch( this.tag ) { | |
case OBJ_NIL: { | |
this.tag = OBJ_INT; | |
this.val = 0; | |
} | |
case OBJ_FLT: { | |
this.tag = OBJ_INT; | |
float f = this.val; | |
this.val = RoundFloat(f); | |
} | |
case OBJ_STR: { | |
char[] str = new char[this.size + 1]; | |
StringMap str_data = this.val; | |
str_data.GetString("str", str, this.size); | |
int i = StringToInt(str, 0); | |
delete str_data; | |
this.val = i; | |
this.size = 0; | |
this.tag = OBJ_INT; | |
} | |
case OBJ_TAB, OBJ_FNC: { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool ConvertToStr() { | |
if( this.tag==OBJ_STR ) { | |
return true; | |
} | |
switch( this.tag ) { | |
case OBJ_NIL: { | |
this.Init(OBJ_STR); | |
this.SetStr(""); | |
} | |
case OBJ_FLT: { | |
float f = this.val; | |
char flt_str[30]; | |
int len = FloatToString(f, flt_str, sizeof(flt_str)) + 1; | |
this.Init(OBJ_STR); | |
this.SetStr(flt_str, len); | |
} | |
case OBJ_INT: { | |
char int_str[30]; | |
int len = IntToString(this.val, int_str, sizeof(int_str)) + 1; | |
this.Init(OBJ_STR); | |
this.SetStr(int_str, len); | |
} | |
case OBJ_TAB, OBJ_FNC: { | |
return false; | |
} | |
} | |
return true; | |
} | |
bool ConcatStr(CSLObj b) { | |
if( this.tag != OBJ_STR || b.tag != OBJ_STR ) { | |
return false; | |
} | |
char[] first_str = new char[this.size + 1]; | |
char[] second_str = new char[b.size + 1]; | |
StringMap first_str_data = this.val; | |
StringMap second_str_data = b.val; | |
first_str_data.GetString("str", first_str, this.size); | |
second_str_data.GetString("str", second_str, b.size); | |
char[] merged_str = new char[this.size + b.size + 1]; | |
int len = Format(merged_str, this.size + b.size, "%s%s", first_str, second_str); | |
this.SetStr(merged_str, this.size + b.size); | |
return len > 0; | |
} | |
int StrCmp(CSLObj b) { | |
if( this.tag != OBJ_STR || b.tag != OBJ_STR ) { | |
return false; | |
} | |
char[] first_str = new char[this.size + 1]; | |
char[] second_str = new char[b.size + 1]; | |
StringMap first_str_data = this.val; | |
StringMap second_str_data = b.val; | |
first_str_data.GetString("str", first_str, this.size); | |
second_str_data.GetString("str", second_str, b.size); | |
return strcmp(first_str, second_str); | |
} | |
bool MakeInt(int i) { | |
this.Init(OBJ_INT); | |
return this.SetInt(i); | |
} | |
bool MakeFloat(float f) { | |
this.Init(OBJ_FLT); | |
return this.SetFloat(f); | |
} | |
bool MakeStr(const char[] s, int len=0) { | |
this.Init(OBJ_STR); | |
return this.SetStr(s, len); | |
} | |
bool MakeMap(StringMap m) { | |
this.Init(OBJ_TAB); | |
return this.SetMap(m); | |
} | |
bool MakeFunc(SPFFIFunc f, Handle plugin=null) { | |
this.Init(OBJ_FNC); | |
return this.SetFunc(f, plugin); | |
} | |
bool MakeVec(const float v[3]) { | |
this.Init(OBJ_VEC); | |
return this.SetVec(v); | |
} | |
void PrintType() { | |
switch( this.tag ) { | |
case OBJ_NIL: PrintToServer("objtype :: nil"); | |
case OBJ_INT: PrintToServer("objtype :: int"); | |
case OBJ_FLT: PrintToServer("objtype :: float"); | |
case OBJ_STR: PrintToServer("objtype :: string"); | |
case OBJ_TAB: PrintToServer("objtype :: table"); | |
case OBJ_FNC: PrintToServer("objtype :: func"); | |
case OBJ_VEC: PrintToServer("objtype :: vec"); | |
} | |
} | |
} | |
bool ConcatStrs(CSLObj a, CSLObj b, CSLObj res) { | |
if( a.tag != OBJ_STR || b.tag != OBJ_STR ) { | |
return false; | |
} | |
char[] first_str = new char[a.size + 1]; | |
char[] second_str = new char[b.size + 1]; | |
StringMap first_str_data = a.val; | |
StringMap second_str_data = b.val; | |
first_str_data.GetString("str", first_str, a.size); | |
second_str_data.GetString("str", second_str, b.size); | |
char[] merged_str = new char[a.size + b.size + 1]; | |
int len = Format(merged_str, a.size + b.size, "%s%s", first_str, second_str); | |
res.Init(OBJ_STR); | |
res.SetStr(merged_str, a.size + b.size); | |
return len > 0; | |
} | |
/* | |
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 | |
TokenBreak, TokenContinue, TokenIf, TokenElse, TokenDefined, | |
TokenFor, TokenIn, TokenNil, TokenVar, | |
/// delimiters | |
TokenLParen, TokenRParen, /// () | |
TokenLBrace, TokenRBrace, /// {} | |
TokenLSq, TokenRSq, /// [] | |
TokenComma, TokenColon, /// , : | |
/// operators | |
TokenAdd, /// + | |
TokenSub, /// - | |
TokenMul, /// * | |
TokenDiv, /// / | |
TokenMod, /// % | |
TokenPow, /// ** | |
TokenDot, /// . | |
TokenNot, /// ! | |
TokenLSH, /// << | |
TokenRSHA, /// >> | |
TokenRSHL, /// >>> | |
TokenXor, /// ^ | |
TokenAnd, /// & | |
TokenAndXor, /// &^ | |
TokenOr, /// | | |
TokenCmp, /// == | |
TokenNotCmp, /// != | |
TokenLT, /// < | |
TokenLE, /// <= | |
TokenGT, /// > | |
TokenGE, /// >= | |
TokenLogicAnd, /// && | |
TokenLogicOr, /// || | |
TokenAssign, /// = | |
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; | |
int line; | |
void Init() { | |
this.line = 1; | |
} | |
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': { | |
if( src[this.src_idx]=='\n' ) { | |
this.line++; | |
} | |
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 if( StrEqual(this.curr_tok.lexeme, "nil") ) { | |
this.curr_tok.tag = TokenNil; | |
} else if( StrEqual(this.curr_tok.lexeme, "defined") ) { | |
this.curr_tok.tag = TokenDefined; | |
} else if( StrEqual(this.curr_tok.lexeme, "break") ) { | |
this.curr_tok.tag = TokenBreak; | |
} else if( StrEqual(this.curr_tok.lexeme, "continue") ) { | |
this.curr_tok.tag = TokenContinue; | |
} else if( StrEqual(this.curr_tok.lexeme, "var") ) { | |
this.curr_tok.tag = TokenVar; | |
} 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 = TokenLBrace; | |
this.src_idx++; | |
return; | |
} | |
case '}': { | |
this.curr_tok.tag = TokenRBrace; | |
this.src_idx++; | |
return; | |
} | |
case '[': { | |
this.curr_tok.tag = TokenLSq; | |
this.src_idx++; | |
return; | |
} | |
case ']': { | |
this.curr_tok.tag = TokenRSq; | |
this.src_idx++; | |
return; | |
} | |
case '+': { | |
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 = 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 ':': { | |
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++; | |
} | |
} | |
} | |
this.curr_tok.tag = TokenInvalid; | |
} | |
} | |
/// -------------- PARSER -------------- | |
/** | |
* CSL "Config Scripting Language" grammar: | |
* | |
* script = stmts EOF . | |
* | |
* === Statements === | |
* stmt = if | while | foreach | decl | assign | expr_stmt | flow . | |
* stmts = +stmt . | |
* block = '{' *stmt '}' . | |
* if = 'if' expr block [ 'else' ( if | block ) ] . | |
* while = 'for' [ expr ] block . | |
* decl = 'var' ident '=' expr . | |
* assign = ident ( [op +] '=' ) expr . | |
* foreach = 'for' expr block . // requires 'in' expression for this one. | |
* flow = 'break' | 'continue' . | |
* expr_stmt = expr . | |
* | |
* === Expressions === | |
* 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 = rel_expr *( ('==' | '!=') rel_expr ) . | |
* rel_expr = add_expr *( ('<' | '<=' | '>' | '>=') add_expr ) . | |
* add_expr = mul_expr *( ('+' | '-' | '|' | '^') mul_expr ) . | |
* mul_expr = pow_expr *( ('*' | '/' | '%' | '<<' | '>>' | '>>>' | '&' | '&^') pow_expr ) . | |
* pow_expr = pre_expr *( '**' pre_expr ) . | |
* fn_expr = pre_expr *( ident pre_expr ) . /// removed for interferring with decl syntax. Need statement terminators. | |
* pre_expr = ( "+" | "-" | "!" | "^" | "defined" ) post_expr . | |
* post_expr = factor *( '[' exprs ']' | '(' exprs ')' | '.' ident ) . | |
* | |
* key_expr = ident ':' expr . | |
* key_exprs = key_expr *( ',' key_expr ) . | |
* factor = number | ident | string | 'nil' | '(' expr ')' | '{' key_exprs '}' . | |
* comment = '#' ... . | |
* | |
* symbols that are all upper-case are immutable. | |
```py | |
# comment. | |
var player = get_uid(event.userid) | |
player.dmg_counter += event.damageamount | |
if a {} | |
if a {} else {} | |
if a {} else if b {} else {} | |
for i < a.CONSTANT {} | |
for v in m {} | |
a += b | |
``` | |
*/ | |
enum { | |
NODE_INVALID, | |
NODE_INT_LIT, | |
NODE_FLOAT_LIT, | |
NODE_STR_LIT, | |
NODE_IDENT, | |
NODE_NIL, | |
NODE_BIN_OP, /// .a is left expr node, .b is right expr node. | |
NODE_UNARY_OP, /// .a is the expr we're doing the op on. | |
NODE_FUNC_CALL, /// .a is the caller, .b is the start of the arg list, .c is number of args. | |
NODE_ARRAY_IDX, /// .a is indexer, .b is expr list of exprs. | |
NODE_EXPR_LIST, /// .a is expr node, .b is next node in expr list. | |
NODE_PROP_ACCESS, /// .a is the map, .b is the property. | |
NODE_KEY_VAL, /// .a is the prop, .b is the value. | |
NODE_KEYVAL_EXPR, /// .a is the expr list of keyvals, .b is the number of keyvals. | |
NODE_VEC_EXPR, /// .a is 1st index expression, .b is second, .c is third. | |
NODE_END_NODE_EXPRS, | |
NODE_START_NODE_STMTS, | |
NODE_IF_STMT, /// .a is expr, .b is block stmt, .c is else or -1 if there was no else block. | |
NODE_BLOCK_STMT, /// .a is stmts list, .b is the number of stmts. | |
NODE_FLOW_STMT, /// .op is used for this one, either a break or continue. | |
NODE_SIMPLE_FOR, /// | |
NODE_FOR_EACH, /// | |
NODE_DECL, /// .a is identifier, .b is initializer expr. | |
NODE_ASSIGN, /// .a is the lhs expr, .b is the rhs expr, .op is operation. | |
NODE_EXPR_STMT, /// .a is the expr node. | |
NODE_STMT_LIST, /// .a is stmt node, .b is next node in stmt list. | |
NODE_END_NODE_STMTS, | |
NODE_SCRIPT, /// .a is stmts list, .b is the number of stmts. | |
}; | |
enum struct CSLNode { | |
/// value of identifier/string. | |
char s[LEXEME_SIZE]; | |
/// value of a literal. | |
any val; | |
/// used for binary ops. use `a` for unary ops. | |
/// a, b, & c are used as indexes to other AST data. | |
int a; | |
int b; | |
int c; | |
/// this uses Token enum or other values. | |
int op; | |
/// denotes what kind of node we're dealing with. | |
int node_type; | |
void MakeIntLit(int value) { | |
this.node_type = NODE_INT_LIT; | |
this.val = value; | |
} | |
void MakeFloatLit(float value) { | |
this.node_type = NODE_FLOAT_LIT; | |
this.val = value; | |
} | |
void MakeStrLit(const char strval[LEXEME_SIZE]) { | |
this.node_type = NODE_STR_LIT; | |
this.s = strval; | |
} | |
void MakeIdent(const char name[LEXEME_SIZE]) { | |
this.node_type = NODE_IDENT; | |
this.s = name; | |
} | |
void MakeNil() { | |
this.node_type = NODE_NIL; | |
} | |
void MakeBinaryNode(int node_type, int a_node, int b_node, int op) { | |
this.node_type = node_type; | |
this.a = a_node; | |
this.b = b_node; | |
this.op = op; | |
} | |
void MakeUnaryNode(int node_type, int a_node, int op) { | |
this.node_type = node_type; | |
this.a = a_node; | |
this.op = op; | |
} | |
} | |
enum struct CSL_AST { | |
ArrayList ast; /// []CSLNode | |
/// this is for keeping track of the strings & maps | |
/// so we do not leak handles. | |
ArrayList tracked_maps; | |
ArrayList tracked_strs; | |
StringMap vars; | |
void Init() { | |
this.ast = new ArrayList(sizeof(CSLNode)); | |
this.tracked_maps = new ArrayList(); | |
this.tracked_strs = new ArrayList(); | |
this.vars = new StringMap(); | |
} | |
void Destroy() { | |
delete this.ast; | |
int len = this.tracked_maps.Length; | |
for( int i; i < len; i++ ) { | |
CSLObj data; | |
this.tracked_maps.GetArray(i, data, sizeof(data)); | |
data.Destroy(true); | |
} | |
delete this.tracked_maps; | |
len = this.tracked_strs.Length; | |
for( int i; i < len; i++ ) { | |
CSLObj data; | |
this.tracked_strs.GetArray(i, data, sizeof(data)); | |
data.Destroy(true); | |
} | |
delete this.tracked_strs; | |
} | |
int InsertNode(CSLNode node) { | |
return this.ast.PushArray(node, sizeof(node)); | |
} | |
int GetNodeType(int n) { | |
if( n >= this.ast.Length ) { | |
return NODE_INVALID; | |
} | |
CSLNode node; | |
this.ast.GetArray(n, node, sizeof(node)); | |
return node.node_type; | |
} | |
bool GetNode(int n, CSLNode buffer) { | |
if( n >= this.ast.Length ) { | |
return false; | |
} | |
return this.ast.GetArray(n, buffer, sizeof(buffer))==sizeof(CSLNode); | |
} | |
bool RegisterObj(const char[] name, CSLObj o) { | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool RegisterFunc(const char[] name, SPFFIFunc fn, Handle plugin=null) { | |
CSLObj fnobj; fnobj.MakeFunc(fn, plugin); | |
return this.vars.SetArray(name, fnobj, sizeof(fnobj)); | |
} | |
bool RegisterFloat(const char[] name, float f) { | |
CSLObj o; o.MakeFloat(f); | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool RegisterInt(const char[] name, int i) { | |
CSLObj o; o.MakeInt(i); | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool RegisterStr(const char[] name, const char[] str) { | |
CSLObj o; o.MakeStr(str); | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool RegisterTable(const char[] name, StringMap map) { | |
CSLObj o; o.MakeMap(map); | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool RegisterVec(const char[] name, float vec[3]) { | |
CSLObj o; o.MakeVec(vec); | |
return this.vars.SetArray(name, o, sizeof(o)); | |
} | |
bool CreateNameSpace(const char[] name) { | |
StringMap names = new StringMap(); | |
CSLObj namespc; namespc.MakeMap(names); | |
return this.RegisterObj(name, namespc); | |
} | |
bool AddToNameSpace(const char[] name, const char[] v, CSLObj o) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool AddFuncToNameSpace(const char[] name, const char[] v, SPFFIFunc fn, Handle plugin=null) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj fnobj; fnobj.MakeFunc(fn, plugin); | |
return( m==null )? false : m.SetArray(v, fnobj, sizeof(fnobj)); | |
} | |
bool AddFloatToNameSpace(const char[] name, const char[] v, float f) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj o; o.MakeFloat(f); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool AddIntToNameSpace(const char[] name, const char[] v, int i) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj o; o.MakeInt(i); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool AddStrToNameSpace(const char[] name, const char[] v, const char[] str) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj o; o.MakeStr(str); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool AddTableToNameSpace(const char[] name, const char[] v, StringMap map) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj o; o.MakeMap(map); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool AddVecToNameSpace(const char[] name, const char[] v, float vec[3]) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
StringMap m = namespc.GetMap(); | |
CSLObj o; o.MakeVec(vec); | |
return( m==null )? false : m.SetArray(v, o, sizeof(o)); | |
} | |
bool DestroyNameSpace(const char[] name) { | |
CSLObj namespc; | |
if( !this.vars.GetArray(name, namespc, sizeof(namespc)) ) { | |
return false; | |
} | |
namespc.Destroy(true); | |
return this.vars.Remove(name); | |
} | |
void PrintAllVars() { | |
StringMapSnapshot snap = this.vars.Snapshot(); | |
if( snap==null ) { | |
return; | |
} | |
int entries = snap.Length; | |
for( int i; i < entries; i++ ) { | |
int strsize = snap.KeyBufferSize(i) + 1; | |
char[] key_buffer = new char[strsize]; | |
snap.GetKey(i, key_buffer, strsize); | |
CSLObj val; this.vars.GetArray(key_buffer, val, sizeof(val)); | |
char type_tag[64]; | |
switch( val.tag ) { | |
case OBJ_INT: type_tag = "int"; | |
case OBJ_FLT: type_tag = "float"; | |
case OBJ_TAB: type_tag = "table"; | |
case OBJ_STR: type_tag = "string"; | |
case OBJ_FNC: type_tag = "func"; | |
case OBJ_VEC: type_tag = "vec"; | |
case OBJ_NIL: type_tag = "nil"; | |
} | |
PrintToServer("'%s' :: '%s'", key_buffer, type_tag); | |
} | |
delete snap; | |
} | |
} | |
void PrintAST(CSL_AST ast, int n) { | |
if( n < 0 ) { | |
PrintToServer("PrintAST :: 'n' is negative."); | |
return; | |
} | |
CSLNode node; | |
ast.ast.GetArray(n, node, sizeof(node)); | |
switch( node.node_type ) { | |
case NODE_INVALID: { | |
PrintToServer("Invalid Node"); | |
} | |
case NODE_INT_LIT: { | |
PrintToServer("Int Literal: '%i'", node.val); | |
} | |
case NODE_FLOAT_LIT: { | |
float f = node.val; | |
PrintToServer("Float Literal: '%f'", f); | |
} | |
case NODE_STR_LIT: { | |
PrintToServer("String Literal: '%s'", node.s); | |
} | |
case NODE_IDENT: { | |
PrintToServer("Ident Expr: '%s'", node.s); | |
} | |
case NODE_NIL: { | |
PrintToServer("Nil Expr"); | |
} | |
case NODE_BIN_OP: { | |
char op_str[LEXEME_SIZE]; | |
switch( node.op ) { | |
case TokenAdd: op_str = "+"; | |
case TokenSub: op_str = "-"; | |
case TokenMul: op_str = "*"; | |
case TokenDiv: op_str = "/"; | |
case TokenMod: op_str = "%"; | |
case TokenPow: op_str = "**"; | |
case TokenLSH: op_str = "<<"; | |
case TokenRSHA: op_str = ">>"; | |
case TokenRSHL: op_str = ">>>"; | |
case TokenXor: op_str = "^"; | |
case TokenAnd: op_str = "&"; | |
case TokenAndXor: op_str = "&^"; | |
case TokenOr: op_str = "|"; | |
case TokenCmp: op_str = "=="; | |
case TokenNotCmp: op_str = "!="; | |
case TokenLT: op_str = "<"; | |
case TokenLE: op_str = "<="; | |
case TokenGT: op_str = ">"; | |
case TokenGE: op_str = ">="; | |
case TokenLogicAnd: op_str = "&&"; | |
case TokenLogicOr: op_str = "||"; | |
case TokenIn: op_str = "in"; | |
default: op_str = node.s; | |
} | |
PrintToServer("Binary Expr: '%s', printing left", op_str); | |
PrintAST(ast, node.a); | |
PrintToServer("Binary Expr: printing right"); | |
PrintAST(ast, node.b); | |
if( node.c > 0 ) { | |
PrintToServer("Binary Expr: printing extra"); | |
PrintAST(ast, node.c); | |
} | |
} | |
case NODE_UNARY_OP: { | |
char op_str[10]; | |
switch( node.op ) { | |
case TokenAdd: op_str = "+"; | |
case TokenSub: op_str = "-"; | |
case TokenXor: op_str = "^"; | |
case TokenNot: op_str = "!"; | |
case TokenDefined: op_str = "defined"; | |
} | |
PrintToServer("PrintAST :: Unary Expr: '%s'", op_str); | |
PrintAST(ast, node.a); | |
} | |
case NODE_FUNC_CALL: { | |
PrintToServer("Function Call :: Func, number of args: '%i'", node.c); | |
PrintAST(ast, node.a); | |
PrintToServer("Has Args? '%s'", node.b != -1? "yes" : "no"); | |
if( node.b != -1 ) { | |
PrintToServer("Function Call :: Arg :: "); | |
PrintAST(ast, node.b); | |
} | |
} | |
case NODE_EXPR_LIST: { | |
PrintToServer("Expr List :: "); | |
PrintAST(ast, node.a); | |
if( node.b != -1 ) { | |
PrintToServer("Expr List :: going to next."); | |
PrintAST(ast, node.b); | |
} | |
} | |
case NODE_PROP_ACCESS: { | |
PrintToServer("Map Access :: Accessor"); | |
PrintAST(ast, node.a); | |
PrintToServer("Map Access :: Prop"); | |
PrintAST(ast, node.b); | |
} | |
case NODE_KEY_VAL: { | |
PrintToServer("Key Val :: Prop"); | |
PrintAST(ast, node.a); | |
PrintToServer("Key Val :: Value"); | |
PrintAST(ast, node.b); | |
} | |
case NODE_KEYVAL_EXPR: { | |
PrintToServer("%sKey Val List :: number of keyvals '%s'", node.a == -1? "Empty " : "", node.b); | |
if( node.a != -1 ) { | |
PrintAST(ast, node.a); | |
} | |
} | |
case NODE_VEC_EXPR: { | |
PrintToServer("Vector Expression :: ["); | |
if( node.a != -1 ) { | |
PrintAST(ast, node.a); | |
} | |
if( node.b != -1 ) { | |
PrintAST(ast, node.b); | |
} | |
if( node.c != -1 ) { | |
PrintAST(ast, node.c); | |
} | |
PrintToServer("]"); | |
} | |
case NODE_IF_STMT: { | |
PrintToServer("If Statement :: Printing Condition Expression"); | |
PrintAST(ast, node.a); | |
PrintToServer("If Statement :: Printing Block"); | |
PrintAST(ast, node.b); | |
if( node.c != -1 ) { | |
PrintToServer("If Statement :: Printing Else"); | |
PrintAST(ast, node.c); | |
} | |
} | |
case NODE_STMT_LIST: { | |
PrintToServer("Statement List :: "); | |
PrintAST(ast, node.a); | |
if( node.b != -1 ) { | |
PrintToServer("Statement List :: going to next."); | |
PrintAST(ast, node.b); | |
} | |
} | |
case NODE_BLOCK_STMT: { | |
PrintToServer("Block Statement :: number of statements '%i'", node.b); | |
if( node.a != -1 ) { | |
PrintAST(ast, node.a); | |
} | |
} | |
case NODE_EXPR_STMT: { | |
PrintToServer("Expr Statement :: "); | |
PrintAST(ast, node.a); | |
} | |
case NODE_ASSIGN: { | |
char op_str[10]; | |
switch( node.op ) { | |
case TokenAdd: op_str = "+="; | |
case TokenSub: op_str = "-="; | |
case TokenMul: op_str = "*="; | |
case TokenDiv: op_str = "/="; | |
case TokenMod: op_str = "%="; | |
case TokenPow: op_str = "**="; | |
case TokenLSH: op_str = "<<="; | |
case TokenRSHA: op_str = ">>="; | |
case TokenRSHL: op_str = ">>>="; | |
case TokenXor: op_str = "^="; | |
case TokenAnd: op_str = "&="; | |
case TokenAndXor: op_str = "&^="; | |
case TokenOr: op_str = "|="; | |
case TokenAssign: op_str = "="; | |
} | |
PrintToServer("Assignment Statement :: Op: %s", op_str); | |
PrintToServer("Assignment Statement :: Printing LHS"); | |
PrintAST(ast, node.a); | |
PrintToServer("Assignment Statement :: Printing RHS"); | |
PrintAST(ast, node.b); | |
} | |
case NODE_DECL: { | |
PrintToServer("Decl Statement :: Var Expr"); | |
PrintAST(ast, node.a); | |
PrintToServer("Decl Statement :: Initializer Expr"); | |
PrintAST(ast, node.b); | |
} | |
case NODE_SCRIPT: { | |
PrintToServer("Script :: '%i' statements", node.b); | |
PrintAST(ast, node.a); | |
} | |
} | |
} | |
stock int GetListOfNodes(CSL_AST ast, int stmt_list_idx, int[] stmt_list) { | |
int num_stmts; | |
int j = stmt_list_idx; | |
while( j != -1 ) { | |
CSLNode iter; ast.GetNode(j, iter); | |
/// iter.a holds the node to the key value expression. | |
stmt_list[num_stmts] = iter.a; | |
j = iter.b; | |
num_stmts++; | |
} | |
return num_stmts; | |
} | |
bool ExecScript(CSL_AST ast, int n) { | |
if( n < 0 ) { | |
LogError("ConfigScript :: ExecScript :: Runtime Error :: bad script node (%i)", n); | |
return false; | |
} | |
CSLNode node; ast.ast.GetArray(n, node, sizeof(node)); | |
if( node.node_type != NODE_SCRIPT ) { | |
LogError("ConfigScript :: ExecScript :: Runtime Error :: node is not script node (%i)", node.node_type); | |
return false; | |
} | |
int[] stmt_list = new int[node.b]; | |
int num_stmts = GetListOfNodes(ast, node.a, stmt_list); | |
return ExecStmts(ast, stmt_list, num_stmts); | |
} | |
bool ExecStmts(CSL_AST ast, int[] stmt_list, int num_stmts) { | |
for( int curr_stmt; curr_stmt < num_stmts; curr_stmt++ ) { | |
CSLNode stmt_node; ast.GetNode(stmt_list[curr_stmt], stmt_node); | |
if( stmt_node.node_type <= NODE_START_NODE_STMTS ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: node is not statement node (%i)", stmt_node.node_type); | |
return false; | |
} | |
switch( stmt_node.node_type ) { | |
case NODE_IF_STMT: { | |
CSLObj cond; | |
if( !EvaluateExprs(ast, stmt_node.a, cond) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: failed to evaluate condition for if-statement"); | |
return false; | |
} | |
if( cond.val != 0 ) { | |
if( !ExecStmt(ast, stmt_node.b) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: failed to evaluate statements for if-statement"); | |
return false; | |
} | |
} else if( stmt_node.c != -1 ) { | |
if( !ExecStmt(ast, stmt_node.c) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: failed to evaluate condition for if-statement else block"); | |
return false; | |
} | |
} | |
} | |
case NODE_BLOCK_STMT: { | |
int[] nested_stmt_list = new int[stmt_node.b]; | |
int num_nested_stmts = GetListOfNodes(ast, stmt_node.a, nested_stmt_list); | |
if( !ExecStmts(ast, nested_stmt_list, num_nested_stmts) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: block statement failed!"); | |
return false; | |
} | |
} | |
case NODE_EXPR_STMT: { | |
CSLObj throwaway; | |
EvaluateExprs(ast, stmt_node.a, throwaway); | |
} | |
case NODE_DECL: { | |
CSLNode ident; ast.GetNode(stmt_node.a, ident); | |
if( ident.node_type != NODE_IDENT ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: cannot declare without an identifier."); | |
return false; | |
} | |
CSLObj init_expr; | |
if( !EvaluateExprs(ast, stmt_node.b, init_expr) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: failed to evaluate expression for declaration."); | |
return false; | |
} | |
return ast.vars.SetArray(ident.s, init_expr, sizeof(init_expr)); | |
} | |
case NODE_ASSIGN: { | |
/// lvalues. | |
/// a = b | |
/// a.b = c | |
/// a[b] = c | |
/// a.b[c] = d | |
/// a.b.c = d | |
CSLNode lvalue_node; ast.GetNode(stmt_node.a, lvalue_node); | |
switch( lvalue_node.node_type ) { | |
case NODE_IDENT: { | |
CSLObj rvalue_obj; EvaluateExprs(ast, stmt_node.b, rvalue_obj); | |
ast.vars.SetArray(lvalue_node.s, rvalue_obj, sizeof(rvalue_obj)); | |
} | |
case NODE_PROP_ACCESS: { | |
CSLObj map; | |
CSLNode accessor; ast.GetNode(stmt_node.a, accessor); | |
CSLNode accessee; ast.GetNode(stmt_node.b, accessee); | |
while( accessee.node_type==NODE_PROP_ACCESS ) { | |
CSLNode prop_node; ast.GetNode(accessor.b, prop_node); | |
ast.GetNode(prop_node.b, accessee); | |
accessor = accessee; | |
} | |
if( accessee.node_type==NODE_ARRAY_IDX ) { | |
} else if( accessee.node_type==NODE_IDENT ) { | |
PrintToServer("accessor.node_type == %s | accessee.node_type==NODE_IDENT", accessor.s); | |
} else { | |
/// error, not an lvalue. | |
return false; | |
} | |
} | |
case NODE_ARRAY_IDX: { | |
} | |
default: { | |
/// error, not an lvalue. | |
return false; | |
} | |
} | |
} | |
} | |
} | |
return true; | |
} | |
bool ExecStmt(CSL_AST ast, int n) { | |
if( n < 0 ) { | |
LogError("ConfigScript :: ExecStmt :: Runtime Error :: node index is bad (%i)", n); | |
return false; | |
} | |
CSLNode node; ast.ast.GetArray(n, node, sizeof(node)); | |
if( node.node_type <= NODE_START_NODE_STMTS ) { | |
LogError("ConfigScript :: ExecStmt :: Runtime Error :: node is not statement node (%i)", node.node_type); | |
return false; | |
} | |
switch( node.node_type ) { | |
case NODE_IF_STMT: { | |
CSLObj cond; | |
if( !EvaluateExprs(ast, node.a, cond) ) { | |
LogError("ConfigScript :: ExecStmt :: Runtime Error :: failed to evaluate condition for if-statement"); | |
return false; | |
} | |
if( cond.val != 0 ) { | |
if( !ExecStmt(ast, node.b) ) { | |
LogError("ConfigScript :: ExecStmt :: Runtime Error :: failed to evaluate statements for if-statement"); | |
return false; | |
} | |
} else if( node.c != -1 ) { | |
if( !ExecStmt(ast, node.c) ) { | |
LogError("ConfigScript :: ExecStmt :: Runtime Error :: failed to evaluate condition for if-statement else block"); | |
return false; | |
} | |
} | |
return true; | |
} | |
case NODE_BLOCK_STMT: { | |
int[] nested_stmt_list = new int[node.b]; | |
int num_nested_stmts = GetListOfNodes(ast, node.a, nested_stmt_list); | |
return ExecStmts(ast, nested_stmt_list, num_nested_stmts); | |
} | |
case NODE_EXPR_STMT: { | |
CSLObj throwaway; | |
return EvaluateExprs(ast, node.a, throwaway); | |
} | |
case NODE_DECL: { | |
CSLNode ident; ast.GetNode(node.a, ident); | |
if( ident.node_type != NODE_IDENT ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: cannot declare without an identifier."); | |
return false; | |
} | |
CSLObj init_expr; | |
if( !EvaluateExprs(ast, node.b, init_expr) ) { | |
LogError("ConfigScript :: ExecStmts :: Runtime Error :: failed to evaluate expression for declaration."); | |
return false; | |
} | |
return ast.vars.SetArray(ident.s, init_expr, sizeof(init_expr)); | |
} | |
} | |
return false; | |
} | |
/// 'vars' is map[string]CSLObj | |
/// TODO: make this iterative using a stack? | |
/// This would require a stack for multiple things though... | |
/** | |
func DFSIterative(root Node) { | |
stack := new(NodeStack) | |
stack.Push(root) | |
for !stack.IsEmpty() { | |
curr := stack.Pop() | |
// do thing with `curr`. | |
if curr.Left != nil { | |
stack.push(curr.Left) | |
} | |
if curr.Right != nil { | |
stack.push(curr.Right) | |
} | |
} | |
} | |
*/ | |
bool EvaluateExprs(CSL_AST ast, int n, CSLObj result) { | |
if( n < 0 ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: node index is bad (%i).", n); | |
return false; | |
} | |
CSLNode node; ast.ast.GetArray(n, node, sizeof(node)); | |
if( node.node_type >= NODE_END_NODE_EXPRS ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: node is not an expression node (%i).", node.node_type); | |
return false; | |
} | |
switch( node.node_type ) { | |
case NODE_INVALID: { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid node."); | |
return false; | |
} | |
case NODE_INT_LIT: { | |
CSLObj int_lit; | |
int_lit.Init(OBJ_INT); | |
int_lit.SetInt(node.val); | |
result = int_lit; | |
return true; | |
} | |
case NODE_FLOAT_LIT: { | |
CSLObj flt_lit; | |
flt_lit.Init(OBJ_FLT); | |
float f = node.val; | |
flt_lit.SetFloat(f); | |
result = flt_lit; | |
return true; | |
} | |
case NODE_IDENT: { | |
bool res = ast.vars.GetArray(node.s, result, sizeof(result)); | |
if( !res ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: no var '%s' exists or failed to retrieve var.", node.s); | |
/// error here, doesn't exist or couldn't grab. | |
} | |
return res; | |
} | |
case NODE_STR_LIT: { | |
result.Init(OBJ_STR); | |
result.SetStr(node.s, LEXEME_SIZE); | |
return true; | |
} | |
case NODE_NIL: { | |
result.Destroy(); | |
return true; | |
} | |
case NODE_BIN_OP: { | |
CSLObj resA, resB; | |
switch( node.op ) { | |
case TokenAdd: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val + resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a + b; | |
result.val = c; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a + b; | |
result.val = c; | |
} else if( resA.AreStrType(resB) ) { | |
ConcatStrs(resA, resB, result); | |
} else if( (resA.tag==OBJ_FLT || resA.tag==OBJ_INT) && resB.tag==OBJ_STR ) { | |
resA.ConvertToStr(); | |
ConcatStrs(resA, resB, result); | |
} else if( (resB.tag==OBJ_FLT || resB.tag==OBJ_INT) && resA.tag==OBJ_STR ) { | |
resB.ConvertToStr(); | |
ConcatStrs(resA, resB, result); | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_VEC); | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resA.vec[i] + resB.vec[i]; | |
} | |
} else if( (resA.tag==OBJ_VEC && resB.IsArithmeticType()) || (resB.tag==OBJ_VEC && resA.IsArithmeticType()) ) { | |
result.Init(OBJ_VEC); | |
if( resA.tag==OBJ_VEC ) { | |
resB.ConvertToFloat(); | |
float f = resB.val; | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resA.vec[i] + f; | |
} | |
} else { | |
resA.ConvertToFloat(); | |
float f = resA.val; | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resB.vec[i] + f; | |
} | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '+'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '+' operation failed."); | |
return false; | |
} | |
case TokenSub: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val - resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a - b; | |
result.val = c; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a - b; | |
result.val = c; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_VEC); | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resA.vec[i] - resB.vec[i]; | |
} | |
} else if( (resA.tag==OBJ_VEC && resB.IsArithmeticType()) || (resB.tag==OBJ_VEC && resA.IsArithmeticType()) ) { | |
result.Init(OBJ_VEC); | |
if( resA.tag==OBJ_VEC ) { | |
resB.ConvertToFloat(); | |
float f = resB.val; | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resA.vec[i] - f; | |
} | |
} else { | |
resA.ConvertToFloat(); | |
float f = resA.val; | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = resB.vec[i] - f; | |
} | |
} | |
} else { | |
/// error here because maps aren't arithmetible. | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '-'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '-' operation failed."); | |
return false; | |
} | |
case TokenMul: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val * resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a * b; | |
result.val = c; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = a * b; | |
result.val = c; | |
} else if( resA.AreVecType(resB) ) { | |
float f = GetVectorDotProduct(resA.vec, resB.vec); | |
result.MakeFloat(f); | |
} else if( (resA.tag==OBJ_VEC && resB.IsArithmeticType()) || (resB.tag==OBJ_VEC && resA.IsArithmeticType()) ) { | |
result.Init(OBJ_VEC); | |
float v[3], mult; | |
if( resA.tag==OBJ_VEC ) { | |
v = resA.vec; | |
mult = resB.val; | |
} else { | |
v = resB.vec; | |
mult = resA.val; | |
} | |
ScaleVector(v, mult); | |
result.vec = v; | |
} else { | |
/// error here because maps aren't arithmetible. | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '*'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '*' operation failed."); | |
return false; | |
} | |
case TokenDiv: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
if( resB.val==0 ) { | |
/// div by 0 error. | |
return false; | |
} | |
result.val = resA.val / resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
if( b==0.0 ) { | |
/// div by 0 error. | |
return false; | |
} | |
float c = a / b; | |
result.val = c; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
if( b==0.0 ) { | |
/// div by 0 error. | |
return false; | |
} | |
result.MakeFloat(a / b); | |
} else { | |
/// error here because maps aren't arithmetible. | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '/'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '/' operation failed."); | |
return false; | |
} | |
case TokenMod: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
if( resB.val==0 ) { | |
/// div by 0 error. | |
return false; | |
} | |
result.val = resA.val % resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '%'."); | |
/// Error here because modulo is an int only operation. | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '%' operation failed."); | |
return false; | |
} | |
case TokenPow: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
int a = resA.val; | |
int b = resB.val; | |
float f = Pow(a+0.0, b+0.0); | |
int i = RoundFloat(f); | |
result.val = i; | |
} else if( resA.AreFloatType(resB) ) { | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = Pow(a, b); | |
result.val = c; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
result.Init(OBJ_FLT); | |
float a = resA.val; | |
float b = resB.val; | |
float c = Pow(a, b); | |
result.val = c; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '**' aka power exponent."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '**' operation failed."); | |
return false; | |
} | |
case TokenLSH: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val << resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '<<' aka Left Bit Shift."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '<<' operation failed."); | |
return false; | |
} | |
case TokenRSHA: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val >> resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '>>' aka Right Bit Shift (Arithmetic)."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '>>' operation failed."); | |
return false; | |
} | |
case TokenRSHL: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val >>> resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '>>>' aka Right Bit Shift (Logical)."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '>>>' operation failed."); | |
return false; | |
} | |
case TokenXor: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val ^ resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '^' aka bitwise XOR."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '^' operation failed."); | |
return false; | |
} | |
case TokenAnd: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val & resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '&' aka bitwise AND."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '&' operation failed."); | |
return false; | |
} | |
case TokenAndXor: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val & ~resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '&^' aka bitwise AND XOR."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '&^' operation failed."); | |
return false; | |
} | |
case TokenOr: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val | resB.val; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '|' aka bitwise OR."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '|' operation failed."); | |
return false; | |
} | |
case TokenCmp: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val == resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a == b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a == b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB)==0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = resA.vec[i]==resB.vec[i] & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '=='."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '==' operation failed."); | |
return false; | |
} | |
case TokenNotCmp: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val != resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a != b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a != b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB) != 0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = resA.vec[i] != resB.vec[i] & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '!='."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '!=' operation failed."); | |
return false; | |
} | |
case TokenLT: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val < resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a < b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a < b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB) < 0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = (resA.vec[i] < resB.vec[i]) & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '<'."); | |
return false; | |
} | |
if( node.c != -1 ) { | |
CSLObj resC; | |
EvaluateExprs(ast, node.c, resC); | |
result.val = result.val && resC.val; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '<' operation failed."); | |
return false; | |
} | |
case TokenLE: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val <= resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a <= b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a <= b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB) <= 0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = (resA.vec[i] <= resB.vec[i]) & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '<='."); | |
return false; | |
} | |
if( node.c != -1 ) { | |
CSLObj resC; | |
EvaluateExprs(ast, node.c, resC); | |
result.val = result.val && resC.val; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '<=' operation failed."); | |
return false; | |
} | |
case TokenGT: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val > resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a > b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a > b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB) > 0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = (resA.vec[i] > resB.vec[i]) & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '>'."); | |
return false; | |
} | |
if( node.c != -1 ) { | |
CSLObj resC; | |
EvaluateExprs(ast, node.c, resC); | |
result.val = result.val && resC.val; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '>' operation failed."); | |
return false; | |
} | |
case TokenGE: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val >= resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a >= b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a >= b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.StrCmp(resB) >= 0; | |
} else if( resA.AreVecType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = 1; | |
for( int i; i < 3; i++ ) { | |
result.val = (resA.vec[i] >= resB.vec[i]) & 1; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '>='."); | |
return false; | |
} | |
if( node.c != -1 ) { | |
CSLObj resC; | |
EvaluateExprs(ast, node.c, resC); | |
result.val = result.val && resC.val; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '>=' operation failed."); | |
return false; | |
} | |
case TokenLogicAnd: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val && resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a && b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a && b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.size && resB.size; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '&&'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '&&' operation failed."); | |
return false; | |
} | |
case TokenLogicOr: { | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreIntType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val || resB.val; | |
} else if( resA.AreFloatType(resB) ) { | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a || b; | |
} else if( resA.AreArithmetible(resB) ) { | |
resA.ConvertToFloat(); | |
resB.ConvertToFloat(); | |
float a = resA.val; | |
float b = resB.val; | |
result.Init(OBJ_INT); | |
result.val = a || b; | |
} else if( resA.AreStrType(resB) ) { | |
result.Init(OBJ_INT); | |
result.val = resA.size || resB.size; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for binary '||'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary '||' operation failed."); | |
return false; | |
} | |
case TokenIn: { | |
/// a in b | |
if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
if( resA.AreStrType(resB) ) { | |
/// find substring in str | |
/// TODO: implement... | |
result.Init(OBJ_INT); | |
result.val = 1; | |
} else if( resB.tag==OBJ_TAB ) { | |
StringMap map = resB.GetMap(); | |
if( map==null ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: bad StringMap for 'in' operator."); | |
return false; | |
} | |
StringMapSnapshot snap = map.Snapshot(); | |
if( snap==null ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: failed to create Snapshot for 'in' operator."); | |
return false; | |
} | |
bool is_in_map = false; | |
int entries = snap.Length; | |
for( int i; i < entries; i++ ) { | |
int strsize = snap.KeyBufferSize(i) + 1; | |
char[] key_buffer = new char[strsize]; | |
snap.GetKey(i, key_buffer, strsize); | |
CSLObj val; | |
map.GetArray(key_buffer, val, sizeof(val)); | |
if( resA.AreSameType(val) ) { | |
switch( resA.tag ) { | |
case OBJ_INT, OBJ_FLT, OBJ_TAB: { | |
is_in_map = resA.val==val.val; | |
} | |
case OBJ_STR: { | |
is_in_map = resA.val==val.val && resA.size==val.size; | |
} | |
case OBJ_FNC: { | |
is_in_map = resA.f==val.f; | |
} | |
} | |
} | |
if( is_in_map ) { | |
break; | |
} | |
} | |
delete snap; | |
result.Init(OBJ_INT); | |
result.val = view_as< int >(is_in_map); | |
} else if( resA.tag==OBJ_FLT && resB.tag==OBJ_VEC ) { | |
result.Init(OBJ_INT); | |
float find = resA.val; | |
for( int i; i < 3; i++ ) { | |
if( find==resB.vec[i] ) { | |
result.val = 1; | |
break; | |
} | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operands for 'in' binary operator."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: binary 'in' operator failed."); | |
return false; | |
} | |
default: { | |
/* | |
if( node.s[0] != 0 ) { | |
CSLObj binary_func; | |
if( !ast.vars.GetArray(node.s, binary_func, sizeof(binary_func)) || binary_func.tag != OBJ_FNC ) { | |
/// error, missing func or something idk. | |
return false; | |
} else if( EvaluateExprs(ast, node.a, resA) && EvaluateExprs(ast, node.b, resB) ) { | |
SPFFIFunc f = binary_func.GetFunc(); | |
Handle fnc_pl = binary_func.val; | |
if( f==INVALID_FUNCTION ) { | |
/// error bad function. | |
return false; | |
} | |
Call_StartFunction(fnc_pl, f); | |
Call_PushArrayEx(resA, sizeof(resA), SM_PARAM_COPYBACK); | |
Call_PushArrayEx(resB, sizeof(resB), SM_PARAM_COPYBACK); | |
Call_PushArrayEx(result, sizeof(result), SM_PARAM_COPYBACK); | |
bool call_result = Call_Finish()==SP_ERROR_NONE; | |
return call_result; | |
} | |
} | |
*/ | |
return false; | |
} | |
} | |
} | |
case NODE_UNARY_OP: { | |
CSLObj resA; | |
switch( node.op ) { | |
case TokenAdd: { | |
/// abs Value. | |
if( EvaluateExprs(ast, node.a, resA) ) { | |
if( resA.tag==OBJ_INT ) { | |
result.Init(OBJ_INT); | |
result.val = resA.val < 0? -resA.val : resA.val; | |
} else if( resA.tag==OBJ_FLT ) { | |
result.Init(OBJ_FLT); | |
float f = result.val; | |
f = FloatAbs(f); | |
result.val = f; | |
} else if( resA.tag==OBJ_VEC ) { | |
result.Init(OBJ_VEC); | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = FloatAbs(resA.vec[i]); | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operand for unary '+' aka absolute value."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: unary '+' operation failed."); | |
return false; | |
} | |
case TokenSub: { | |
/// Negate | |
if( EvaluateExprs(ast, node.a, resA) ) { | |
if( resA.tag==OBJ_INT ) { | |
result.Init(OBJ_INT); | |
result.val = -resA.val; | |
} else if( resA.tag==OBJ_FLT ) { | |
result.Init(OBJ_FLT); | |
result.val = resA.val | 0x80000000; | |
} else if( resA.tag==OBJ_VEC ) { | |
result.Init(OBJ_VEC); | |
for( int i; i < 3; i++ ) { | |
result.vec[i] = -resA.vec[i]; | |
} | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operand for unary '-' aka negate."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: unary '-' operation failed."); | |
return false; | |
} | |
case TokenXor: { | |
/// Bitwise Complement/NOT. | |
if( EvaluateExprs(ast, node.a, resA) ) { | |
if( resA.tag==OBJ_INT ) { | |
result.Init(OBJ_INT); | |
result.val = ~resA.val; | |
} else if( resA.tag==OBJ_VEC ) { | |
result.Init(OBJ_VEC); | |
NormalizeVector(resA.vec, result.vec); | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operand for unary '^' aka integer complement/vector normalize."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: unary '^' operation failed."); | |
return false; | |
} | |
case TokenNot: { | |
/// Bitwise Complement/NOT. | |
if( EvaluateExprs(ast, node.a, resA) ) { | |
if( resA.tag==OBJ_INT ) { | |
result.Init(OBJ_INT); | |
result.val = !resA.val; | |
} else if( resA.tag==OBJ_FLT ) { | |
result.Init(OBJ_FLT); | |
float f = resA.val; | |
f = view_as< int >(!f) + 0.0; | |
result.val = f; | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid operand for unary '!'."); | |
return false; | |
} | |
return true; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: unary '!' operation failed."); | |
return false; | |
} | |
case TokenDefined: { | |
if( ast.GetNodeType(node.a)==NODE_PROP_ACCESS ) { | |
CSLNode accessor; ast.GetNode(node.a, accessor); | |
int accessor_node = accessor.a; | |
int prop_node = accessor.b; | |
/// handle nested accesses... | |
while( ast.GetNodeType(prop_node)==NODE_PROP_ACCESS ) { | |
CSLNode nested_accessor; | |
ast.GetNode(prop_node, nested_accessor); | |
accessor_node = nested_accessor.a; | |
prop_node = nested_accessor.b; | |
} | |
if( !EvaluateExprs(ast, accessor_node, resA) ) { | |
/// error here for some reason. | |
return false; | |
} | |
CSLNode prop; ast.GetNode(prop_node, prop); | |
StringMap map = resA.GetMap(); | |
if( map==null ) { | |
/// error bad map here. | |
return false; | |
} | |
bool res = map.ContainsKey(prop.s); | |
result.Init(OBJ_INT); | |
result.val = view_as< int >(res==true); | |
return res; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: 'defined' operator failed."); | |
return false; | |
} | |
} | |
} | |
case NODE_FUNC_CALL: { | |
CSLObj resF; | |
if( EvaluateExprs(ast, node.a, resF) && resF.tag==OBJ_FNC ) { | |
SPFFIFunc f = resF.GetFunc(); | |
Handle fnc_pl = resF.val; | |
if( f==INVALID_FUNCTION ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: invalid function in function call!"); | |
return false; | |
} | |
int p = node.b; | |
int c = 0; | |
Call_StartFunction(fnc_pl, f); | |
ArrayList args = new ArrayList(sizeof(CSLObj)); | |
while( p != -1 ) { | |
CSLNode expr_list_node; ast.GetNode(p, expr_list_node); | |
CSLObj arg; EvaluateExprs(ast, expr_list_node.a, arg); | |
args.PushArray(arg, sizeof(arg)); | |
c++; | |
p = expr_list_node.b; | |
} | |
Call_PushCell(args); | |
Call_PushArrayEx(result, sizeof(result), SM_PARAM_COPYBACK); | |
bool call_result = Call_Finish()==SP_ERROR_NONE; | |
int len = args.Length; | |
for( int i; i < len; i++ ) { | |
CSLObj arg; args.GetArray(i, arg, sizeof(arg)); | |
arg.Destroy(); | |
} | |
delete args; | |
return call_result; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: function call operation failed!"); | |
return false; | |
} | |
case NODE_PROP_ACCESS: { | |
CSLObj resA; | |
if( EvaluateExprs(ast, node.a, resA) && resA.tag==OBJ_TAB && ast.GetNodeType(node.b)==NODE_IDENT ) { | |
StringMap map = resA.GetMap(); | |
if( map==null ) { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: bad map in map access operation."); | |
return false; | |
} | |
CSLNode prop_name; ast.GetNode(node.b, prop_name); | |
return map.GetArray(prop_name.s, result, sizeof(result)); | |
} else { | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: map accessor is not a map."); | |
return false; | |
} | |
LogError("ConfigScript :: EvaluateExprs :: Runtime Error :: map access operation failed."); | |
return false; | |
} | |
case NODE_KEYVAL_EXPR: { | |
/// node.b holds how many key values were parsed. | |
int[] kv_nodes = new int[node.b]; | |
int kv_counter = 0; | |
int j = node.a; | |
/// first get all the kv node indexes. | |
while( j != -1 ) { | |
CSLNode iter; ast.GetNode(j, iter); | |
/// iter.a holds the node to the key value expression. | |
kv_nodes[kv_counter] = iter.a; | |
j = iter.b; | |
kv_counter++; | |
} | |
/// next get kv nodes themselves. | |
CSLNode[] kvs = new CSLNode[node.b]; | |
bool retrieval = true; | |
for( int i; i < node.b; i++ ) { | |
retrieval = ast.GetNode(kv_nodes[i], kvs[i]) && retrieval; | |
} | |
if( !retrieval ) { | |
/// error here. | |
return false; | |
} | |
/// next verify the key parts are identifiers. | |
retrieval = true; | |
for( int i; i < node.b; i++ ) { | |
retrieval = ast.GetNodeType(kvs[i].a)==NODE_IDENT && retrieval; | |
} | |
if( !retrieval ) { | |
/// error, non-ident as key. | |
return false; | |
} | |
/// now we eval the value part. | |
CSLObj[] vals = new CSLObj[node.b]; | |
retrieval = true; | |
for( int i; i < node.b; i++ ) { | |
retrieval = EvaluateExprs(ast, kvs[i].b, vals[i]) && retrieval; | |
} | |
if( !retrieval ) { | |
/// error, expr failed somewhere. | |
return false; | |
} | |
/// setup the map. | |
CSLObj new_map; | |
new_map.Init(OBJ_TAB); | |
StringMap m = new_map.GetMap(); | |
if( m==null ) { | |
/// out of memory? | |
new_map.Destroy(); | |
return false; | |
} | |
retrieval = true; | |
for( int i; i < node.b; i++ ) { | |
CSLNode key_ident; ast.GetNode(kvs[i].a, key_ident); | |
retrieval = m.SetArray(key_ident.s, vals[i], sizeof(vals[])) && retrieval; | |
} | |
if( !retrieval ) { | |
/// possibly duplicate key? | |
new_map.Destroy(); | |
return false; | |
} | |
result = new_map; | |
return true; | |
} | |
case NODE_VEC_EXPR: { | |
float v[3]; | |
int nodes[3]; | |
nodes[0] = node.a; | |
nodes[1] = node.b; | |
nodes[2] = node.c; | |
for( int i; i < 3; i++ ) { | |
CSLObj res; | |
if( nodes[i] != -1 && EvaluateExprs(ast, nodes[i], res) ) { | |
res.ConvertToFloat(); | |
v[i] = res.val; | |
} | |
} | |
result.MakeVec(v); | |
return true; | |
} | |
case NODE_ARRAY_IDX: { | |
CSLObj resA; | |
if( !EvaluateExprs(ast, node.a, resA) ) { | |
return false; | |
} else if( resA.tag != OBJ_STR && resA.tag != OBJ_TAB && resA.tag != OBJ_VEC ) { | |
/// error, can't 'index' a non-tabular type. | |
return false; | |
} | |
if( node.c==1 ) { | |
CSLObj resB; | |
CSLNode expr_of_idx; | |
ast.GetNode(node.b, expr_of_idx); | |
EvaluateExprs(ast, expr_of_idx.a, resB); | |
switch( resA.tag ) { | |
case OBJ_STR: { | |
if( resB.tag==OBJ_INT ) { | |
int len = resA.GetStrLen() | |
if( resB.val > len ) { | |
return false; | |
} | |
char[] s = new char[len]; | |
if( !resA.GetStr(s, len) ) { | |
return false; | |
} | |
result.MakeInt(s[resB.val]); | |
return true; | |
} | |
return false; | |
} | |
case OBJ_VEC: { | |
if( resB.tag==OBJ_INT ) { | |
if( resB.val >= 3 ) { | |
return false; | |
} | |
result.MakeFloat(resA.vec[resB.val]); | |
return true; | |
} | |
return false; | |
} | |
case OBJ_TAB: { | |
if( resB.IsArithmeticType() ) { | |
char buffer[CELL_KEY_SIZE]; | |
PackCellToStr(resB.val, buffer); | |
StringMap map = resA.GetMap(); | |
if( map==null ) { | |
/// error here. | |
return false; | |
} | |
return map.GetArray(buffer, result, sizeof(result)); | |
} | |
return false; | |
} | |
} | |
return false; | |
} | |
/// multi-expression indexing. | |
/// saving for stuff like slicing but keeping | |
/// commented for now to keep cfgscript small. | |
/* | |
int[] expr_nodes = new int[node.c]; | |
int expr_counter = 0; | |
int j = node.b; | |
/// first get all the indexing node indexes. | |
while( j != -1 ) { | |
CSLNode iter; ast.GetNode(j, iter); | |
/// iter.a holds the node to the expression. | |
expr_nodes[expr_counter] = iter.a; | |
j = iter.b; | |
expr_counter++; | |
} | |
*/ | |
return false; | |
} | |
} | |
return false; | |
} | |
enum struct CSLParser { | |
CSLLexState lexer; | |
CSL_AST nodes; | |
bool errored; | |
void Init(const char[] src, int src_len, const char[] filename) { | |
this.nodes.Init(); | |
this.lexer.Init(); | |
this.lexer.GetToken(src, src_len); | |
this.nodes.vars.SetString("filename", filename); | |
this.nodes.vars.SetValue("filename_size", strlen(filename) + 1); | |
} | |
void Destroy() { | |
this.nodes.Destroy(); | |
} | |
bool CreateNameSpace(const char[] name) { | |
return this.nodes.CreateNameSpace(name); | |
} | |
bool AddToNameSpace(const char[] name, const char[] v, CSLObj o) { | |
return this.nodes.AddToNameSpace(name, v, o); | |
} | |
bool DestroyNameSpace(const char[] name) { | |
return this.nodes.DestroyNameSpace(name); | |
} | |
bool RegisterObj(const char[] name, CSLObj o) { | |
return this.nodes.RegisterObj(name, o); | |
} | |
bool RegisterFunc(const char[] name, SPFFIFunc fn, Handle plugin=null) { | |
return this.nodes.RegisterFunc(name, fn, plugin); | |
} | |
bool RegisterFloat(const char[] name, float f) { | |
return this.nodes.RegisterFloat(name, f); | |
} | |
bool RegisterInt(const char[] name, int i) { | |
return this.nodes.RegisterInt(name, i); | |
} | |
bool RegisterStr(const char[] name, const char[] str) { | |
return this.nodes.RegisterStr(name, str); | |
} | |
bool RegisterTable(const char[] name, StringMap map) { | |
return this.nodes.RegisterTable(name, map); | |
} | |
bool RegisterVec(const char[] name, float vec[3]) { | |
return this.nodes.RegisterVec(name, vec); | |
} | |
bool AddFuncToNameSpace(const char[] name, const char[] v, SPFFIFunc fn, Handle plugin=null) { | |
return this.nodes.AddFuncToNameSpace(name, v, fn, plugin); | |
} | |
bool AddFloatToNameSpace(const char[] name, const char[] v, float f) { | |
return this.nodes.AddFloatToNameSpace(name, v, f); | |
} | |
bool AddIntToNameSpace(const char[] name, const char[] v, int i) { | |
return this.nodes.AddIntToNameSpace(name, v, i); | |
} | |
bool AddStrToNameSpace(const char[] name, const char[] v, const char[] str) { | |
return this.nodes.AddStrToNameSpace(name, v, str); | |
} | |
bool AddTableToNameSpace(const char[] name, const char[] v, StringMap map) { | |
return this.nodes.AddTableToNameSpace(name, v, map); | |
} | |
bool AddVecToNameSpace(const char[] name, const char[] v, float vec[3]) { | |
return this.nodes.AddVecToNameSpace(name, v, vec); | |
} | |
} | |
/// script = +stmt EOF . | |
int ParseScript(CSLParser parser, const char[] src, int src_len) { | |
CSLNode script; script.node_type = NODE_SCRIPT; | |
int first_stmt = -1; | |
int prev_stmt_node = -1; | |
int num_stmts = 0; | |
while( parser.lexer.curr_tok.tag != TokenInvalid ) { | |
int stmt_node = ParseStmt(parser, src, src_len); | |
if( stmt_node == -1 ) { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: Statement errored. (L:%i)", filename_str, parser.lexer.line); | |
break; | |
} | |
CSLNode parsed_stmt; | |
parsed_stmt.node_type = NODE_STMT_LIST; | |
parsed_stmt.a = stmt_node; | |
parsed_stmt.b = -1; | |
int stmt_node_idx = parser.nodes.InsertNode(parsed_stmt); | |
if( first_stmt == -1 ) { | |
first_stmt = stmt_node_idx; | |
} | |
if( prev_stmt_node != -1 ) { | |
CSLNode prev_parsed_stmt; | |
parser.nodes.ast.GetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
prev_parsed_stmt.b = stmt_node_idx; | |
parser.nodes.ast.SetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
} | |
prev_stmt_node = stmt_node_idx; | |
num_stmts++; | |
} | |
if( prev_stmt_node != -1 ) { | |
CSLNode prev_parsed_stmt; | |
parser.nodes.ast.GetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
prev_parsed_stmt.b = -1; | |
parser.nodes.ast.SetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
} | |
script.a = first_stmt; | |
script.b = num_stmts; | |
return parser.nodes.InsertNode(script); | |
} | |
/// Statement Rule Productions. | |
/// block = '{' +stmt '}' . | |
int ParseBlock(CSLParser parser, const char[] src, int src_len) { | |
parser.lexer.GetToken(src, src_len); /// advance past '{'. | |
CSLNode block_stmt; | |
block_stmt.node_type = NODE_BLOCK_STMT; | |
int first_stmt = -1; | |
int prev_stmt_node = -1; | |
int num_stmts = 0; | |
if( parser.lexer.curr_tok.tag != TokenRBrace ) { | |
while( parser.lexer.curr_tok.tag > TokenInvalid && parser.lexer.curr_tok.tag != TokenRBrace ) { | |
int stmt_node = ParseStmt(parser, src, src_len); | |
if( stmt_node == -1 ) { | |
/// error, stmt messed up. | |
break; | |
} | |
CSLNode parsed_stmt; | |
parsed_stmt.node_type = NODE_STMT_LIST; | |
parsed_stmt.a = stmt_node; | |
parsed_stmt.b = -1; | |
int stmt_node_idx = parser.nodes.InsertNode(parsed_stmt); | |
if( first_stmt == -1 ) { | |
first_stmt = stmt_node_idx; | |
} | |
if( prev_stmt_node != -1 ) { | |
CSLNode prev_parsed_stmt; | |
parser.nodes.ast.GetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
prev_parsed_stmt.b = stmt_node_idx; | |
parser.nodes.ast.SetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
} | |
prev_stmt_node = stmt_node_idx; | |
num_stmts++; | |
} | |
if( parser.lexer.curr_tok.tag != TokenRBrace ) { | |
/// error here. | |
return -1; | |
} | |
if( prev_stmt_node != -1 ) { | |
CSLNode prev_parsed_stmt; | |
parser.nodes.ast.GetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
prev_parsed_stmt.b = -1; | |
parser.nodes.ast.SetArray(prev_stmt_node, prev_parsed_stmt, sizeof(prev_parsed_stmt)); | |
} | |
} | |
if( parser.lexer.curr_tok.tag==TokenRBrace ) { | |
parser.lexer.GetToken(src, src_len); /// advance past '}'. | |
} | |
block_stmt.a = first_stmt; | |
block_stmt.b = num_stmts; | |
return parser.nodes.InsertNode(block_stmt); | |
} | |
/// stmt = if | while | foreach | decl | assign | expr_stmt | flow . | |
int ParseStmt(CSLParser parser, const char[] src, int src_len) { | |
switch( parser.lexer.curr_tok.tag ) { | |
case TokenIf: return ParseIfStmt(parser, src, src_len); | |
case TokenVar: return ParseDeclStmt(parser, src, src_len); | |
case TokenFor: return ParseForStmt(parser, src, src_len); | |
case TokenBreak, TokenContinue: { /// flow = 'break' | 'continue' . | |
CSLNode flow; | |
flow.node_type = NODE_FLOW_STMT; | |
flow.op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
return parser.nodes.InsertNode(flow); | |
} | |
case TokenIdent: return ParseAssignOrExprStmt(parser, src, src_len); | |
default: { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: unknown token. (L:%i)", filename_str, parser.lexer.line); | |
/// error, bad statement starter. | |
} | |
} | |
return -1; | |
} | |
/// if = 'if' expr block [ 'else' ( if | block ) ] . | |
int ParseIfStmt(CSLParser parser, const char[] src, int src_len) { | |
parser.lexer.GetToken(src, src_len); /// advance past 'if'. | |
if( parser.lexer.curr_tok.tag==TokenLBrace ) { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: if-statement missing condition expression. (L:%i)", filename_str, parser.lexer.line); | |
/// error, missing condition for if-stmt. | |
return -1; | |
} | |
CSLNode if_stmt; | |
if_stmt.node_type = NODE_IF_STMT; | |
if_stmt.a = ParseMainExpr(parser, src, src_len); | |
if_stmt.b = ParseBlock(parser, src, src_len); | |
if_stmt.c = -1; | |
if( parser.lexer.curr_tok.tag==TokenElse ) { | |
parser.lexer.GetToken(src, src_len); /// advance past 'else'. | |
if( parser.lexer.curr_tok.tag==TokenIf ) { | |
if_stmt.c = ParseIfStmt(parser, src, src_len); | |
} else if( parser.lexer.curr_tok.tag==TokenLBrace ) { | |
if_stmt.c = ParseBlock(parser, src, src_len); | |
} else { | |
/// error, bad else portion. | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: bad else block. (L:%i)", filename_str, parser.lexer.line); | |
return -1; | |
} | |
} | |
return parser.nodes.InsertNode(if_stmt); | |
} | |
/// while = 'for' expr block . | |
/// foreach = 'for' expr block . // requires 'in' expression for this one. | |
int ParseForStmt(CSLParser parser, const char[] src, int src_len) { | |
parser.lexer.GetToken(src, src_len); /// advance past 'for'. | |
/// TODO: | |
return -1; | |
} | |
/// decl = 'var' ident '=' expr . | |
int ParseDeclStmt(CSLParser parser, const char[] src, int src_len) { | |
parser.lexer.GetToken(src, src_len); /// advance past 'var'. | |
CSLNode decl_stmt; | |
decl_stmt.node_type = NODE_DECL; | |
decl_stmt.a = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag != TokenAssign ) { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing '=' for variable declaration. (L:%i)", filename_str, parser.lexer.line); | |
return -1; | |
} | |
parser.lexer.GetToken(src, src_len); /// advance past '='. | |
decl_stmt.b = ParseMainExpr(parser, src, src_len); | |
return parser.nodes.InsertNode(decl_stmt); | |
} | |
/// assign = ident ( [op +] '=' ) expr . | |
/// expr_stmt = expr . | |
/** | |
* expression stmts and assigns always start with an identifier. | |
* because of that, we can merge the two production rules into one function. | |
*/ | |
int ParseAssignOrExprStmt(CSLParser parser, const char[] src, int src_len) { | |
CSLNode stmt_with_ident; | |
int possible_ident = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag >= TokenAssign && parser.lexer.curr_tok.tag <= TokenOrAssign ) { | |
/// assign statement. | |
stmt_with_ident.node_type = NODE_ASSIGN; | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
stmt_with_ident.a = possible_ident; | |
int b = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag != TokenAssign ) { | |
CSLNode bin_op; | |
switch( op ) { | |
case TokenAddAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenAdd); | |
} | |
case TokenSubAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenSub); | |
} | |
case TokenMulAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenMul); | |
} | |
case TokenDivAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenDiv); | |
} | |
case TokenModAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenMod); | |
} | |
case TokenPowAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenPow); | |
} | |
case TokenLSHAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenLSH); | |
} | |
case TokenRSHAAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenRSHA); | |
} | |
case TokenRSHLAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenRSHL); | |
} | |
case TokenXorAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenXor); | |
} | |
case TokenAndAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenAnd); | |
} | |
case TokenAndXorAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenAndXor); | |
} | |
case TokenOrAssign: { | |
bin_op.MakeBinaryNode(NODE_BIN_OP, possible_ident, b, TokenOr); | |
} | |
} | |
b = parser.nodes.InsertNode(bin_op); | |
} | |
stmt_with_ident.b = b; | |
return parser.nodes.InsertNode(stmt_with_ident); | |
} | |
/// otherwise consider it an expression statement. | |
stmt_with_ident.node_type = NODE_EXPR_STMT; | |
stmt_with_ident.a = possible_ident; | |
return parser.nodes.InsertNode(stmt_with_ident); | |
} | |
/// Expression Rule Productions. | |
/// expr = in_expr . | |
int ParseMainExpr(CSLParser parser, const char[] src, int src_len) { | |
return ParseInExpr(parser, src, src_len); | |
} | |
/// in_expr = or_expr *( 'in' or_expr ) . | |
int ParseInExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseOrExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenIn ) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParseOrExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// or_expr = and_expr *( '||' and_expr ) . | |
int ParseOrExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseAndExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenLogicOr ) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParseAndExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// and_expr = cmp_expr *( '&&' cmp_expr ) . | |
int ParseAndExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseCmpExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenLogicAnd ) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParseCmpExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// cmp_expr = rel_expr *( ('==' | '!=') rel_expr ) . | |
int ParseCmpExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseRelExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenCmp || parser.lexer.curr_tok.tag==TokenNotCmp ) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParseRelExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// rel_expr = add_expr *( ('<' | '<=' | '>' | '>=') add_expr ) . | |
int ParseRelExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseAddExpr(parser, src, src_len); | |
/** | |
* a <= b <= c | |
* [ .a = A, .b = B, .op = OP, .c = [ .a = B, .b = C, .op = OP, .c = -1 ] ] | |
* | |
* a <= b <= c <= d | |
* [ .a = A, .b = B, .op = OP, .c = [ .a = B, .b = C, .op = OP, .c = [ .a = C, .b = D, .op = OP, .c = -1 ] ] ] | |
* | |
* parse expr like normal. Check if another op is there, if so, rerun to .c | |
*/ | |
CSLNode chain_node; | |
chain_node.node_type = NODE_BIN_OP; | |
chain_node.a = n; | |
if( parser.lexer.curr_tok.tag==TokenLT | |
|| parser.lexer.curr_tok.tag==TokenLE | |
|| parser.lexer.curr_tok.tag==TokenGT | |
|| parser.lexer.curr_tok.tag==TokenGE | |
) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
CSLLexState saved_lex; saved_lex = parser.lexer; | |
chain_node.b = ParseAddExpr(parser, src, src_len); | |
chain_node.op = op; | |
if( parser.lexer.curr_tok.tag==TokenLT | |
|| parser.lexer.curr_tok.tag==TokenLE | |
|| parser.lexer.curr_tok.tag==TokenGT | |
|| parser.lexer.curr_tok.tag==TokenGE | |
) { | |
parser.lexer = saved_lex; | |
chain_node.c = ParseRelExpr(parser, src, src_len); | |
} | |
n = parser.nodes.InsertNode(chain_node); | |
} | |
return n; | |
} | |
/// add_expr = mul_expr *( ('+' | '-' | '|' | '^') mul_expr ) . | |
int ParseAddExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseMulExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenAdd | |
|| parser.lexer.curr_tok.tag==TokenSub | |
|| parser.lexer.curr_tok.tag==TokenOr | |
|| parser.lexer.curr_tok.tag==TokenXor | |
) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParseMulExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// mul_expr = pow_expr *( ('*' | '/' | '%' | '<<' | '>>' | '>>>' | '&' | '&^') pow_expr ) . | |
int ParseMulExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParsePowExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenMul | |
|| parser.lexer.curr_tok.tag==TokenDiv | |
|| parser.lexer.curr_tok.tag==TokenMod | |
|| parser.lexer.curr_tok.tag==TokenLSH | |
|| parser.lexer.curr_tok.tag==TokenRSHA | |
|| parser.lexer.curr_tok.tag==TokenRSHL | |
|| parser.lexer.curr_tok.tag==TokenAnd | |
|| parser.lexer.curr_tok.tag==TokenAndXor | |
) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParsePowExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// pow_expr = pre_expr *( '**' pre_expr ) . | |
int ParsePowExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParsePreExpr(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenPow ) { | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int b_node = ParsePreExpr(parser, src, src_len); | |
CSLNode node; | |
node.MakeBinaryNode(NODE_BIN_OP, n, b_node, op); | |
n = parser.nodes.InsertNode(node); | |
} | |
return n; | |
} | |
/// pre_expr = *( "+" | "-" | "!" | "^" | 'defined' ) post_expr . | |
int ParsePreExpr(CSLParser parser, const char[] src, int src_len) { | |
switch( parser.lexer.curr_tok.tag ) { | |
case TokenAdd, TokenSub, TokenNot, TokenXor, TokenDefined: { | |
/// abs value, negate, logical NOT, bool NOT/complement. | |
CSLNode unary; | |
int op = parser.lexer.curr_tok.tag; | |
parser.lexer.GetToken(src, src_len); | |
int n = ParsePreExpr(parser, src, src_len); | |
unary.MakeUnaryNode(NODE_UNARY_OP, n, op); | |
return parser.nodes.InsertNode(unary); | |
} | |
} | |
return ParsePostExpr(parser, src, src_len); | |
} | |
/// post_expr = factor *( '[' exprs ']' | '(' exprs ')' | '.' ident ) . | |
int ParsePostExpr(CSLParser parser, const char[] src, int src_len) { | |
int n = ParseFactor(parser, src, src_len); | |
while( parser.lexer.curr_tok.tag==TokenLParen || parser.lexer.curr_tok.tag==TokenDot || parser.lexer.curr_tok.tag==TokenLSq ) { | |
switch( parser.lexer.curr_tok.tag ) { | |
case TokenLSq: { | |
int end_token = TokenRSq; | |
CSLNode array_idx; | |
array_idx.node_type = NODE_ARRAY_IDX; | |
array_idx.a = n; | |
array_idx.b = -1; | |
parser.lexer.GetToken(src, src_len); | |
/// gotta make an expr "list" here. | |
/// [ .a = expr1 | .b=arg2 ] -> [ .a = expr2 | .b = arg3 ] -> [ .a = expr3 | .b = -1 ] | |
int first_expr = -1; | |
int prev_expr_node = -1; | |
int num_of_exprs = 0; | |
if( parser.lexer.curr_tok.tag != end_token ) { | |
while( parser.lexer.curr_tok.tag != end_token ) { | |
int expr_node = ParseMainExpr(parser, src, src_len); | |
CSLNode parsed_expr; | |
parsed_expr.node_type = NODE_EXPR_LIST; | |
parsed_expr.a = expr_node; | |
parsed_expr.b = -1; | |
int expr_node_idx = parser.nodes.InsertNode(parsed_expr); | |
if( first_expr == -1 ) { | |
first_expr = expr_node_idx; | |
} | |
if( prev_expr_node != -1 ) { | |
CSLNode prev_parsed_expr; | |
parser.nodes.ast.GetArray(prev_expr_node, prev_parsed_expr, sizeof(prev_parsed_expr)); | |
prev_parsed_expr.b = expr_node_idx; | |
parser.nodes.ast.SetArray(prev_expr_node, prev_parsed_expr, sizeof(prev_parsed_expr)); | |
} | |
if( parser.lexer.curr_tok.tag==TokenComma ) { | |
parser.lexer.GetToken(src, src_len); | |
} | |
prev_expr_node = expr_node_idx; | |
num_of_exprs++; | |
} | |
if( parser.lexer.curr_tok.tag==end_token ) { | |
parser.lexer.GetToken(src, src_len); | |
} else { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing ending ']' in array access. (L:%i)", filename_str, parser.lexer.line); | |
break; | |
} | |
if( prev_expr_node != -1 ) { | |
CSLNode prev_parsed_expr; | |
parser.nodes.ast.GetArray(prev_expr_node, prev_parsed_expr, sizeof(prev_parsed_expr)); | |
prev_parsed_expr.b = -1; | |
parser.nodes.ast.SetArray(prev_expr_node, prev_parsed_expr, sizeof(prev_parsed_expr)); | |
} | |
} | |
array_idx.b = first_expr; | |
array_idx.c = num_of_exprs; | |
n = parser.nodes.InsertNode(array_idx); | |
} | |
case TokenLParen: { | |
int end_token = TokenRParen; | |
CSLNode func_call; | |
func_call.node_type = NODE_FUNC_CALL; | |
func_call.a = n; | |
func_call.b = -1; | |
parser.lexer.GetToken(src, src_len); | |
/// gotta make an expr "list" here. | |
/// [ .a = expr1 | .b=arg2 ] -> [ .a = expr2 | .b = arg3 ] -> [ .a = expr3 | .b = -1 ] | |
int first_arg = -1; | |
int prev_arg_node = -1; | |
int num_of_args = 0; | |
if( parser.lexer.curr_tok.tag != end_token ) { | |
while( parser.lexer.curr_tok.tag != end_token ) { | |
int expr_node = ParseMainExpr(parser, src, src_len); | |
CSLNode parsed_arg; | |
parsed_arg.node_type = NODE_EXPR_LIST; | |
parsed_arg.a = expr_node; | |
parsed_arg.b = -1; | |
int arg_node = parser.nodes.InsertNode(parsed_arg); | |
if( first_arg == -1 ) { | |
first_arg = arg_node; | |
} | |
if( prev_arg_node != -1 ) { | |
CSLNode prev_parsed_arg; | |
parser.nodes.ast.GetArray(prev_arg_node, prev_parsed_arg, sizeof(prev_parsed_arg)); | |
prev_parsed_arg.b = arg_node; | |
parser.nodes.ast.SetArray(prev_arg_node, prev_parsed_arg, sizeof(prev_parsed_arg)); | |
} | |
if( parser.lexer.curr_tok.tag==TokenComma ) { | |
parser.lexer.GetToken(src, src_len); | |
} | |
prev_arg_node = arg_node; | |
num_of_args++; | |
} | |
if( parser.lexer.curr_tok.tag==end_token ) { | |
parser.lexer.GetToken(src, src_len); | |
} else { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing ending ')' in function call. (L:%i)", filename_str, parser.lexer.line); | |
break; | |
} | |
if( prev_arg_node != -1 ) { | |
CSLNode prev_parsed_arg; | |
parser.nodes.ast.GetArray(prev_arg_node, prev_parsed_arg, sizeof(prev_parsed_arg)); | |
prev_parsed_arg.b = -1; | |
parser.nodes.ast.SetArray(prev_arg_node, prev_parsed_arg, sizeof(prev_parsed_arg)); | |
} | |
} | |
func_call.b = first_arg; | |
func_call.c = num_of_args; | |
n = parser.nodes.InsertNode(func_call); | |
} | |
case TokenDot: { | |
CSLNode node; | |
node.node_type = NODE_PROP_ACCESS; | |
node.a = n; | |
parser.lexer.GetToken(src, src_len); | |
node.b = ParseFactor(parser, src, src_len); | |
n = parser.nodes.InsertNode(node); | |
} | |
} | |
} | |
return n; | |
} | |
/// key_expr = ident ':' expr . | |
int ParseKeyExpr(CSLParser parser, const char[] src, int src_len) { | |
CSLNode kv; | |
kv.node_type = NODE_KEY_VAL; | |
kv.a = ParseFactor(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag==TokenColon ) { | |
parser.lexer.GetToken(src, src_len); | |
} else { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing ':' in key expression. (L:%i)", filename_str, parser.lexer.line); | |
return -1; | |
} | |
kv.b = ParseMainExpr(parser, src, src_len); | |
return parser.nodes.InsertNode(kv); | |
} | |
/// key_exprs = key_expr *( ',' key_expr ) . | |
/// vec_expr = '[' [number [ ',' number [ ',' number ] ] ']' . | |
/// factor = number | ident | string | 'nil' | '(' expr ')' | '{' key_exprs '}' | vec_expr . | |
/// number = float_lit | int_lit . | |
int ParseFactor(CSLParser parser, const char[] src, int src_len) { | |
int n = -1; | |
CSLNode node; | |
switch( parser.lexer.curr_tok.tag ) { | |
case TokenInt: { | |
node.MakeIntLit(parser.lexer.curr_tok.val); | |
n = parser.nodes.InsertNode(node); | |
} | |
case TokenFloat: { | |
float f = parser.lexer.curr_tok.val; | |
node.MakeFloatLit(f); | |
n = parser.nodes.InsertNode(node); | |
} | |
case TokenString: { | |
node.MakeStrLit(parser.lexer.curr_tok.lexeme); | |
n = parser.nodes.InsertNode(node); | |
} | |
case TokenIdent: { | |
node.MakeIdent(parser.lexer.curr_tok.lexeme); | |
n = parser.nodes.InsertNode(node); | |
} | |
case TokenNil: { | |
node.MakeNil(); | |
n = parser.nodes.InsertNode(node); | |
} | |
case TokenLParen: { | |
/// inner expr. | |
parser.lexer.GetToken(src, src_len); | |
n = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag != TokenRParen ) { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing ending ) (L:%i)", filename_str, parser.lexer.line); | |
} | |
} | |
case TokenLBrace: { | |
parser.lexer.GetToken(src, src_len); | |
CSLNode kv_expr; | |
kv_expr.node_type = NODE_KEYVAL_EXPR; | |
int first_kv = -1; | |
int prev_kv_node = -1; | |
int num_keyvals = 0; | |
if( parser.lexer.curr_tok.tag != TokenRBrace ) { | |
while( parser.lexer.curr_tok.tag != TokenRBrace ) { | |
int kv_node = ParseKeyExpr(parser, src, src_len); | |
CSLNode parsed_kv; | |
parsed_kv.node_type = NODE_EXPR_LIST; | |
parsed_kv.a = kv_node; | |
parsed_kv.b = -1; | |
int kvexpr_node = parser.nodes.InsertNode(parsed_kv); | |
if( first_kv == -1 ) { | |
first_kv = kvexpr_node; | |
} | |
if( prev_kv_node != -1 ) { | |
CSLNode prev_parsed_kv; | |
parser.nodes.ast.GetArray(prev_kv_node, prev_parsed_kv, sizeof(prev_parsed_kv)); | |
prev_parsed_kv.b = kvexpr_node; | |
parser.nodes.ast.SetArray(prev_kv_node, prev_parsed_kv, sizeof(prev_parsed_kv)); | |
} | |
if( parser.lexer.curr_tok.tag==TokenComma ) { | |
parser.lexer.GetToken(src, src_len); | |
} | |
prev_kv_node = kvexpr_node; | |
num_keyvals++; | |
} | |
if( parser.lexer.curr_tok.tag != TokenRBrace ) { | |
/// error here. | |
} | |
if( prev_kv_node != -1 ) { | |
CSLNode prev_parsed_kv; | |
parser.nodes.ast.GetArray(prev_kv_node, prev_parsed_kv, sizeof(prev_parsed_kv)); | |
prev_parsed_kv.b = -1; | |
parser.nodes.ast.SetArray(prev_kv_node, prev_parsed_kv, sizeof(prev_parsed_kv)); | |
} | |
} | |
kv_expr.a = first_kv; | |
kv_expr.b = num_keyvals; | |
n = parser.nodes.InsertNode(kv_expr); | |
} | |
case TokenLSq: { | |
parser.lexer.GetToken(src, src_len); | |
CSLNode vec_expr; | |
vec_expr.node_type = NODE_VEC_EXPR; | |
vec_expr.a = vec_expr.b = vec_expr.c = -1; | |
if( parser.lexer.curr_tok.tag != TokenRSq ) { | |
vec_expr.a = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag==TokenComma ) { | |
parser.lexer.GetToken(src, src_len); | |
} | |
} | |
if( parser.lexer.curr_tok.tag != TokenRSq ) { | |
vec_expr.b = ParseMainExpr(parser, src, src_len); | |
if( parser.lexer.curr_tok.tag==TokenComma ) { | |
parser.lexer.GetToken(src, src_len); | |
} | |
} | |
if( parser.lexer.curr_tok.tag != TokenRSq ) { | |
vec_expr.c = ParseMainExpr(parser, src, src_len); | |
} | |
if( parser.lexer.curr_tok.tag != TokenRSq ) { | |
int filename_len; | |
parser.nodes.vars.GetValue("filename_size", filename_len); | |
char[] filename_str = new char[filename_len + 1]; | |
parser.nodes.vars.GetString("filename", filename_str, filename_len); | |
LogError("ConfigScript :: %s -- Parse Error :: missing ending ] (L:%i)", filename_str, parser.lexer.line); | |
return -1; | |
} | |
n = parser.nodes.InsertNode(vec_expr); | |
} | |
} | |
parser.lexer.GetToken(src, src_len); /// advance lexer. | |
return n; | |
} |
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
#include <sdktools> | |
#include <cfgscript> | |
#pragma semicolon 1 | |
#pragma newdecls required | |
#define PLUGIN_VERSION "1.0.0" | |
public Plugin myinfo = { | |
name = "Cfg Script Tester", | |
author = "Nergal", | |
description = "Plugin that tests CfgScript.", | |
version = PLUGIN_VERSION, | |
url = "zzzzzzzzzzzzz" | |
}; | |
/// math.sin(float_int_or_vec_value) float|vec | |
public void exported_sin(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 1 ) { | |
return; | |
} | |
CSLObj arg1; csl_obj_args.GetArray(0, arg1, sizeof(arg1)); | |
if( arg1.IsArithmeticType() ) { | |
arg1.ConvertToFloat(); | |
float f = arg1.val; | |
retval.MakeFloat(Sine(f)); | |
} else if( arg1.tag==OBJ_VEC ) { | |
float f[3]; | |
for( int i; i < 3; i++ ) { | |
f[i] = Sine(arg1.vec[i]); | |
} | |
retval.MakeVec(f); | |
} | |
} | |
/// math.cos(float_int_or_vec_value) float|vec | |
public void exported_cos(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 1 ) { | |
return; | |
} | |
CSLObj arg1; csl_obj_args.GetArray(0, arg1, sizeof(arg1)); | |
if( arg1.IsArithmeticType() ) { | |
arg1.ConvertToFloat(); | |
float f = arg1.val; | |
retval.MakeFloat(Cosine(f)); | |
} else if( arg1.tag==OBJ_VEC ) { | |
float f[3]; | |
for( int i; i < 3; i++ ) { | |
f[i] = Cosine(arg1.vec[i]); | |
} | |
retval.MakeVec(f); | |
} | |
} | |
/// math.tan(float_int_or_vec_value) float|vec | |
public void exported_tan(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 1 ) { | |
return; | |
} | |
CSLObj arg1; csl_obj_args.GetArray(0, arg1, sizeof(arg1)); | |
if( arg1.IsArithmeticType() ) { | |
arg1.ConvertToFloat(); | |
float f = arg1.val; | |
retval.MakeFloat(Tangent(f)); | |
} else if( arg1.tag==OBJ_VEC ) { | |
float f[3]; | |
for( int i; i < 3; i++ ) { | |
f[i] = Tangent(arg1.vec[i]); | |
} | |
retval.MakeVec(f); | |
} | |
} | |
/// vec.dist(vA, vB) float | |
public void exported_vec_dist(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 2 ) { | |
return; | |
} | |
CSLObj arg1; csl_obj_args.GetArray(0, arg1, sizeof(arg1)); | |
CSLObj arg2; csl_obj_args.GetArray(1, arg2, sizeof(arg2)); | |
if( !arg1.AreVecType(arg2) ) { | |
return; | |
} | |
float f = GetVectorDistance(arg1.vec, arg2.vec); | |
retval.MakeFloat(f); | |
} | |
/// vec.cross(vA, vB) vec | |
public void exported_vec_cross(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 2 ) { | |
return; | |
} | |
CSLObj a; csl_obj_args.GetArray(0, a, sizeof(a)); | |
CSLObj b; csl_obj_args.GetArray(1, b, sizeof(b)); | |
if( !a.AreVecType(b) ) { | |
return; | |
} | |
float res[3]; GetVectorCrossProduct(a.vec, b.vec, res); | |
retval.MakeVec(res); | |
} | |
/// io.server_println(str) | |
public void exported_server_println(ArrayList csl_obj_args, CSLObj retval) { | |
if( csl_obj_args.Length < 1 ) { | |
LogError("io.server_println :: not enough args."); | |
return; | |
} | |
CSLObj arg1; csl_obj_args.GetArray(0, arg1, sizeof(arg1)); | |
if( arg1.tag != OBJ_STR && !arg1.ConvertToStr() ) { | |
LogError("io.server_println :: failed to convert arg to string."); | |
return; | |
} | |
int len = arg1.GetStrLen(); | |
char[] str = new char[len]; | |
arg1.GetStr(str, len); | |
PrintToServer("%s", str); | |
} | |
public void OnPluginStart() { | |
CSLParser parser; | |
char sauce1[] = "( (((760.8+(players-1)) * ((players-1)-1.0))**1.0341) + 2046.0 ) / boss_count"; | |
char sauce2[] = "math.sin(math.PI / 4)**2 + math.cos(math.PI / 4)**2"; | |
char sauce3[] = "vec.cross([5, 5, 3.5], ^[3, 2, 1])"; | |
char sauce4[] = "if 1 > 3 > 2 { io.server_println('1 > 3 > 2') } else if 1 < 3 > 2 { io.server_println('1 < 3 > 2') } else { io.server_println('else') }"; | |
char sauce[] = "var a = 1 io.server_println(a)" | |
//"a += 2\n" | |
; | |
parser.Init(sauce, sizeof(sauce), "tester"); | |
Handle plugin = GetMyHandle(); | |
/// make math lib. | |
parser.CreateNameSpace("math"); | |
parser.AddFuncToNameSpace("math", "sin", exported_sin, plugin); | |
parser.AddFuncToNameSpace("math", "cos", exported_cos, plugin); | |
parser.AddFuncToNameSpace("math", "tan", exported_tan, plugin); | |
//parser.AddFuncToNameSpace("math", "log", exported_log, plugin); | |
parser.AddFloatToNameSpace("math", "PI", FLOAT_PI); | |
/// make vec lib. | |
parser.CreateNameSpace("vec"); | |
parser.AddFuncToNameSpace("vec", "cross", exported_vec_cross, plugin); | |
parser.AddFuncToNameSpace("vec", "dist", exported_vec_dist, plugin); | |
/// make io lib. | |
parser.CreateNameSpace("io"); | |
parser.AddFuncToNameSpace("io", "server_println", exported_server_println, plugin); | |
int parsed_node = ParseScript(parser, sauce, sizeof(sauce)); | |
PrintAST(parser.nodes, parsed_node); | |
///* | |
PrintToServer(ExecScript(parser.nodes, parsed_node)? "'%s' :: exec succeeded!" : "'%s' :: failed exec!", sauce); | |
//*/ | |
parser.nodes.PrintAllVars(); | |
/* | |
CSLObj res; | |
if( EvaluateExprs(parser.nodes, parsed_node, res) ) { | |
switch( res.tag ) { | |
case OBJ_STR: { | |
char[] str = new char[res.size]; | |
res.GetStr(str); | |
PrintToServer("result of '%s' :: %s", sauce, str); | |
res.Destroy(); | |
} | |
case OBJ_INT: { | |
PrintToServer("result of '%s' :: %i", sauce, res.val); | |
} | |
case OBJ_FLT: { | |
float f = res.val; | |
PrintToServer("result of '%s' :: %f", sauce, f); | |
} | |
case OBJ_VEC: { | |
PrintToServer("result of '%s' :: [%f, %f, %f]", sauce, res.vec[0], res.vec[1], res.vec[2]); | |
} | |
case OBJ_NIL: { | |
PrintToServer("result of '%s' :: nil", sauce); | |
} | |
case OBJ_TAB: { | |
PrintToServer("result of '%s' :: <table>", sauce); | |
} | |
case OBJ_FNC: { | |
PrintToServer("result of '%s' :: <func>", sauce); | |
} | |
} | |
} else { | |
PrintToServer("'%s' :: failed to evaluate!", sauce); | |
} | |
//*/ | |
parser.Destroy(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment