Skip to content

Instantly share code, notes, and snippets.

@PMArkive
Forked from kevyonan/cfgscript.inc
Created June 9, 2023 23:28
Show Gist options
  • Select an option

  • Save PMArkive/d46987d3eafe56d2cac3fd242d8484b2 to your computer and use it in GitHub Desktop.

Select an option

Save PMArkive/d46987d3eafe56d2cac3fd242d8484b2 to your computer and use it in GitHub Desktop.
a VScript-like scripting language exclusively made for SourcePawn
/**
* cfgscript.inc
*
* Copyright [2023] Assyrianic aka Nergal
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"),
* to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE ANDNONINFRINGEMENT.
*
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#if defined _cfgscript_included
#endinput
#endif
#define _cfgscript_included
#include <adt>
#include <datapack>
#include <plugin_utils>
enum {
OBJ_NIL,
OBJ_INT,
OBJ_FLT,
OBJ_STR,
OBJ_TAB,
};
enum struct CSLObj {
any val;
int size;
int tag;
void init(int tag) {
if( tag==OBJ_STR ) {
DataPack dp = new DataPack();
this.val = dp;
} else if( tag==OBJ_TAB ) {
StringMap map = new StringMap();
this.val = map;
}
this.tag = tag;
}
void kill() {
if( tag==OBJ_STR || tag==OBJ_TAB ) {
Handle h = this.val;
delete h;
}
this.val = 0;
this.size = 0;
this.tag = 0;
}
bool IsNil() {
return this.tag==OBJ_NIL;
}
bool GetInt(int& i) {
if( this.tag != OBJ_INT ) {
return false;
}
i = this.val;
return true;
}
bool GetFloat(float& f) {
if( this.tag != OBJ_FLT ) {
return false;
}
f = this.val;
return true;
}
int GetStrLen() {
if( this.tag != OBJ_STR ) {
return 0;
}
return this.size;
}
bool GetStr(char[] buf, int len) {
if( this.tag != OBJ_STR ) {
return false;
}
DataPack dp = this.val;
if( dp==null ) {
return false;
}
dp.Reset();
dp.ReadString(buf, len);
return true;
}
StringMap GetMap() {
if( this.tag != OBJ_TAB ) {
return null;
}
StringMap map = this.val;
return( map==null )? null : map;
}
bool SetInt(int i) {
if( this.tag != OBJ_INT ) {
return false;
}
this.val = i;
return true;
}
bool SetFloat(float f) {
if( this.tag != OBJ_FLT ) {
return false;
}
this.val = f;
return true;
}
bool SetString(const char[] str, int len=0) {
if( this.tag != OBJ_STR ) {
return false;
}
DataPack dp = this.val;
dp.Reset(true);
dp.WriteString(str);
if( len <= 0 ) {
len = strlen(str);
}
this.size = len + 1;
return true;
}
bool SetMap(StringMap map) {
if( this.tag != OBJ_TAB ) {
return false;
}
this.val = map;
return true;
}
}
enum struct CSLFunc {
StringMap vars; /// map[string]CSLObj
StringMap params; /// map[string]CSLObj
ArrayList instrs;
void init() {
}
/// map[VarName]Value
bool Invoke(StringMap params, CSLObj ret) {
this.params = params;
return true;
}
}
enum struct CSLScript {
StringMap funcs;
ArrayList strs; /// []string, to refer to strings by index.
void init() {
this.funcs = new StringMap();
}
bool GetFunc(const char[] name, CSLFunc buffer) {
return this.funcs.GetArray(name, buffer, sizeof(buffer));
}
bool Invoke(const char[] name, const CSLObj[] args, int len_args, CSLObj ret) {
return false;
}
}
enum struct CSLSys {
StringMap scripts;
StringMap builtins; /// map[string]FuncObj
void init() {
this.scripts = new StringMap();
this.builtins = new StringMap();
}
}
/// ---------- LEXER ----------
/*
stock bool _IsCharNumeric(int c) {
return( c >= '0' && c <= '9' );
}
stock bool _IsCharAlpha(int c) {
return( c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' );
}
stock bool _StrEqual(const char[] a, const char[] b) {
int i, j;
while( a[i] != 0 && b[j] != 0 && a[i]==b[j] ) {
i++; j++;
}
return i - j;
}
*/
enum {
TokenInvalid,
/// literals
TokenInt, /// 1, 3, 0x2f
TokenFloat, /// 0.4
TokenString, /// "abc", 'abc'
TokenIdent, /// a, b, i, j
/// keywords
TokenIf, TokenElse, TokenFor, TokenIn,
/// delimiters
TokenLParen, TokenRParen, /// ()
TokenLBrack, TokenRBrack, /// {}
TokenComma, TokenColon, /// , :
/// operators
TokenAdd, /// +
TokenSub, /// -
TokenMul, /// *
TokenDiv, /// /
TokenMod, /// %
TokenPow, /// **
TokenDot, /// .
TokenNot, /// !
TokenLSH, /// <<
TokenRSHA, /// >>
TokenRSHL, /// >>>
TokenXor, /// ^
TokenAnd, /// &
TokenAndXor, /// &^
TokenOr, /// |
TokenAssign, /// =
TokenDecl, /// :=
TokenCmp, /// ==
TokenNotCmp, /// !=
TokenLT, /// <
TokenLE, /// <=
TokenGT, /// >
TokenGE, /// >=
TokenInc, /// ++
TokenDec, /// --
TokenLogicAnd, /// &&
TokenLogicOr, /// ||
TokenAddAssign, /// +=
TokenSubAssign, /// -=
TokenMulAssign, /// *=
TokenDivAssign, /// /=
TokenModAssign, /// %=
TokenPowAssign, /// **=
TokenLSHAssign, /// <<=
TokenRSHAAssign, /// >>=
TokenRSHLAssign, /// >>>=
TokenXorAssign, /// ^=
TokenAndAssign, /// &=
TokenAndXorAssign, /// &^=
TokenOrAssign, /// |=
};
enum {
LEXEME_SIZE = 64,
FLAG_DOT = 1,
};
enum struct CSLToken {
int size;
int tag;
any val;
char lexeme[LEXEME_SIZE];
}
enum struct CSLLexState {
CSLToken curr_tok;
int src_idx;
bool LexBinary(const char[] src) {
while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx])) ) {
switch( src[this.src_idx] ) {
case '0', '1': {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
}
default: {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
LogError("ConfigScript :: invalid binary literal: '%s'", this.curr_tok.lexeme);
return false;
}
}
}
return true;
}
bool LexHex(const char[] src) {
while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx]) || IsCharAlpha(src[this.src_idx])) ) {
switch( src[this.src_idx] ) {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'A', 'B', 'C', 'D', 'E', 'F': {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
}
default: {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
LogError("ConfigScript :: invalid hex literal: '%s'", this.curr_tok.lexeme);
return false;
}
}
}
return true;
}
bool LexDec(const char[] src, bool& is_flt) {
int lit_flags = 0;
while( src[this.src_idx] != 0 && (IsCharNumeric(src[this.src_idx]) || src[this.src_idx]=='.') ) {
switch( src[this.src_idx] ) {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
}
case '.': {
if( lit_flags & FLAG_DOT ) {
LogError("ConfigScript :: extra dot in decimal literal");
return false;
}
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
lit_flags |= FLAG_DOT;
is_flt = true;
}
default: {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
LogError("ConfigScript :: invalid decimal literal: '%s'", this.curr_tok.lexeme);
return false;
}
}
}
return true;
}
void GetToken(const char[] src, int src_len) {
CSLToken empty;
this.curr_tok = empty;
while( this.src_idx < src_len ) {
switch( src[this.src_idx] ) {
case ' ', '\t', '\n': {
this.src_idx++;
continue;
}
case '#': {
/// single-line comment
while( this.src_idx < src_len && src[this.src_idx] != '\n' ) {
this.src_idx++;
}
this.src_idx++;
continue;
}
case '0': { /// possible hex, octal, binary, or float.
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
switch( src[this.src_idx] ) {
case 'b', 'B': {
/// Binary.
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
if( this.LexBinary(src) ) {
this.curr_tok.val = StringToInt(this.curr_tok.lexeme[2], 2);
}
this.curr_tok.tag = TokenInt;
return;
}
case 'x', 'X': {
/// Hex.
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
if( this.LexHex(src) ) {
this.curr_tok.val = StringToInt(this.curr_tok.lexeme, 16);
}
this.curr_tok.tag = TokenInt;
return;
}
case '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': {
/// Decimal/Float.
bool is_float = false;
if( this.LexDec(src, is_float) ) {
this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme);
this.curr_tok.tag = is_float? TokenFloat : TokenInt;
}
return;
}
default: {
this.curr_tok.tag = TokenInt;
return;
}
}
}
case '.': {
if( this.src_idx+1 < src_len && IsCharNumeric(src[this.src_idx+1]) ) {
/// Decimal/Float.
bool is_float = false;
if( this.LexDec(src, is_float) ) {
this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme);
this.curr_tok.tag = is_float? TokenFloat : TokenInt;
}
} else {
this.curr_tok.tag = TokenDot;
this.src_idx++;
}
return;
}
case '1', '2', '3', '4', '5', '6', '7', '8', '9': {
bool is_float = false;
if( this.LexDec(src, is_float) ) {
this.curr_tok.val = is_float? StringToFloat(this.curr_tok.lexeme) : StringToInt(this.curr_tok.lexeme);
this.curr_tok.tag = is_float? TokenFloat : TokenInt;
}
return;
}
case '\'', '"': {
char quote = src[this.src_idx];
this.src_idx++;
while( src[this.src_idx] != 0 && src[this.src_idx] != quote ) {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
}
this.src_idx++;
this.curr_tok.tag = TokenString;
return;
}
case 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', '_', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z': {
while( this.src_idx < src_len && (IsCharAlpha(src[this.src_idx]) || src[this.src_idx]=='_') ) {
this.curr_tok.lexeme[this.curr_tok.size++] = src[this.src_idx++];
}
if( StrEqual(this.curr_tok.lexeme, "if") ) {
this.curr_tok.tag = TokenIf;
} else if( StrEqual(this.curr_tok.lexeme, "else") ) {
this.curr_tok.tag = TokenElse;
} else if( StrEqual(this.curr_tok.lexeme, "in") ) {
this.curr_tok.tag = TokenIn;
} else if( StrEqual(this.curr_tok.lexeme, "for") ) {
this.curr_tok.tag = TokenFor;
} else {
this.curr_tok.tag = TokenIdent;
}
return;
}
case '(': {
this.curr_tok.tag = TokenLParen;
this.src_idx++;
return;
}
case ')': {
this.curr_tok.tag = TokenRParen;
this.src_idx++;
return;
}
case ',': {
this.curr_tok.tag = TokenComma;
this.src_idx++;
return;
}
case '{': {
this.curr_tok.tag = TokenLBrack;
this.src_idx++;
return;
}
case '}': {
this.curr_tok.tag = TokenRBrack;
this.src_idx++;
return;
}
case '+': {
if( src[this.src_idx+1]=='+' ) {
this.curr_tok.tag = TokenInc;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenAddAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenAdd;
return;
}
case '-': {
if( src[this.src_idx+1]=='-' ) {
this.curr_tok.tag = TokenDec;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenSubAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenSub;
return;
}
case '*': {
if( src[this.src_idx+1]=='*' ) {
if( src[this.src_idx+2]=='=' ) {
this.curr_tok.tag = TokenPowAssign;
this.src_idx += 3;
return;
}
this.curr_tok.tag = TokenPow;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenMulAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenMul;
return;
}
case '/': {
if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenDivAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenDiv;
return;
}
case '!': {
if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenNotCmp;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenNot;
return;
}
case '<': {
if( src[this.src_idx+1]=='<' ) {
if( src[this.src_idx+2]=='=' ) {
this.curr_tok.tag = TokenLSHAssign;
this.src_idx += 3;
return;
}
this.curr_tok.tag = TokenLSH;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenLE;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenLT;
return;
}
case '>': {
if( src[this.src_idx+1]=='>' ) {
if( src[this.src_idx+2]=='=' ) {
this.curr_tok.tag = TokenRSHAAssign;
this.src_idx += 3;
return;
} else if( src[this.src_idx+2]=='>' ) {
if( src[this.src_idx+3]=='=' ) {
this.curr_tok.tag = TokenRSHLAssign;
this.src_idx += 4;
return;
}
this.curr_tok.tag = TokenRSHL;
this.src_idx += 3;
return;
}
this.curr_tok.tag = TokenRSHA;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenGE;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenGT;
return;
}
case '|': {
if( src[this.src_idx+1]=='|' ) {
this.curr_tok.tag = TokenLogicOr;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenOrAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenOr;
return;
}
case '&': {
if( src[this.src_idx+1]=='&' ) {
this.curr_tok.tag = TokenLogicAnd;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenAndAssign;
this.src_idx += 2;
return;
} else if( src[this.src_idx+1]=='^' ) {
if( src[this.src_idx+2]=='=' ) {
this.curr_tok.tag = TokenAndXorAssign;
this.src_idx += 3;
return;
}
this.curr_tok.tag = TokenAndXor;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenAnd;
return;
}
case '^': {
if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenXorAssign;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenXor;
return;
}
case ':': {
if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenDecl;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenColon;
return;
}
case '=': {
if( src[this.src_idx+1]=='=' ) {
this.curr_tok.tag = TokenCmp;
this.src_idx += 2;
return;
}
this.src_idx++;
this.curr_tok.tag = TokenAssign;
return;
}
default: {
this.src_idx++;
}
}
}
}
}
/// -------------- PARSER --------------
/**
* CSL "Config Scripting Language" grammar:
*
* script = +(func | stmt) .
* func = ident '(' *names ')' block .
* names = ident *( ',' ident ) .
* block = '{' +stmt '}' .
*
* stmt = if | while | decl | assign | inc | expr_stmt.
* if = 'if' expr block [ 'else' (if | block) ] .
* for = 'for' expr block .
* decl = idents ':=' exprs .
* assign = idents ( 'op=' | '=' ) exprs .
* inc = ident ( '++' | '--' ) .
* expr_stmt = expr .
*
* exprs = expr *( ',' expr ) .
* expr = in_expr .
* in_expr = or_expr *( 'in' or_expr ) .
* or_expr = and_expr *( '||' and_expr ) .
* and_expr = cmp_expr *( '&&' cmp_expr ) .
* cmp_expr = add_expr *( ('==' | '!=' | '<' | '<=' | '>' | '>=') add_expr ) .
* add_expr = mul_expr *( ('+' | '-' | '|' | '^') mul_expr ) .
* mul_expr = pow_expr *( ('*' | '/' | '<<' | '>>' | '>>>' | '&' | '&^') pow_expr ) .
* pow_expr = pre_expr *( '**' pre_expr ) .
* pre_expr = ( "+" | "-" | "!" | "^" ) post_expr .
* post_expr = factor [ '(' expr ')' | '.' post_expr ] .
* factor = number | ident | '(' expr ')' .
* comment = '#' ... .
*
```py
# comment.
on_hurt_player(event, players) {
player := get_uid(event.userid)
player.dmg += event.damageamount
}
```
*/
enum struct CSLParser {
CSLLexState lexer;
StringMap vars; /// map[string]CSLObj
StringMap funcs; /// map[string]CSLObj
void init(const char[] src, int src_len) {
this.lexer.GetToken(src, src_len);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment