Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 442 lines (377 sloc) 8.206 kb
#ifndef com_sleepless_fun_cpp
#define com_sleepless_fun_cpp
/*
Copyright 3000 Sleepless Software Inc. All Rights Reserved
A Fun language interpreter
To use
- override the defines immediately below if you choose
- Create your own subclass of Fun with defined versions of virtual methods
The Fun Language
Text of script is interpreted at run time.
The only supported format is 7 bit ascii text.
Lines are separated by the ascii newline (\n) char.
Leading whitespace can consist ONLY of TAB chars.
Trailing whitespace is ignored.
Blank lines are ignored.
"Code blocks" are defined as a series of adjacent lines, all indented by the same # of tabs
Each line is broken up into arguments.
Each argument is any series of non-whitespace characters.
Argument can contain whitespace if it's enclosed by double-quotes.
Lines with 0 arguments are ignored.
The only character that can exist before the first argument on a line is the TAB char
There are no comment lines unless you choose to interpret them yourself in your eval().
Built-in commands
LOOP [count]
BREAK
IF condition
ELSE
FUN function_name
SET variable value
EXIT [integer]
Operators
$ Variable introducer, i.e., $num
Reserved keywords
throw
catch
return
Custom commands
For each line:
Look for a built in command
if not found, look for a defined function
if not found, call developer defined eval() function (you do what you want)
program = (line)(line)*
line = \s*(arg) +(arg)*\s*\n
arg = ((char+)|("char*"))
*/
#ifndef FUN_MAX_SCRIPT_SIZE
# define FUN_MAX_SCRIPT_SIZE 10000
#endif
#ifndef FUN_MAX_LINES
# define FUN_MAX_LINES 500
#endif
#ifndef FUN_MAX_LINE_ARGS
# define FUN_MAX_LINE_ARGS 20
#endif
#ifndef FUN_MAX_STACK_DEPTH
# define FUN_MAX_STACK_DEPTH 20
#endif
#include "exception.cpp"
#include "log.cpp"
#include "string.cpp"
#include "args.cpp"
#include "taglist.cpp"
EXCEPT(FunError);
EXCEPT(FunExit);
#define FUN_EXIT(s) throw new FunExit(s)
#define FUN_FAIL(s) throw new FunError(s)
#define FUN_FAILIF(b,s) { if(b) throw new FunError(s); }
typedef struct FunStringArray
{
S a[FUN_MAX_LINE_ARGS];
int len;
FunStringArray(const char *s)
{
Args args(s);
len = args.length();
if(len > FUN_MAX_LINE_ARGS)
len = FUN_MAX_LINE_ARGS;
for(int i = 0; i < len; i++)
a[i] = args.get(i);
}
S & operator[](int i)
{
FUN_FAILIF(i < 0 || i >= len, "index out of bounds");
return a[i];
}
} FSA;
struct Fun
{
int lnum;
int ltot;
char *lines[FUN_MAX_LINES];
int tabs[FUN_MAX_LINES];
char script[FUN_MAX_SCRIPT_SIZE];
int stack[FUN_MAX_STACK_DEPTH];
int depth;
TagList funcs;
TagList vars;
Fun()
{
lnum = 0;
ltot = 0;
memset(lines, 0, sizeof(lines));
memset(tabs, 0, sizeof(tabs));
memset(script, 0, sizeof(script));
depth = 0;
}
virtual ~Fun()
{
}
void vsubst(S &s)
{
//TRACE("vsubst in: '%s'\n", s.chars);
S n;
char l = 0;
char *cp = s.chars;
while(*cp)
{
n.cat(*cp);
if(*cp == '$')
{
if(*(cp+1) != '$')
{
int nv = vars.ta.length();
int i;
for(i = 0; i < nv; i++)
{
const char *tag = vars.ta.getTag(i);
//TRACE("tag=%s\n", tag);
if(Str::startsWith(cp + 1, tag))
{
n.chars[strlen(n.chars) - 1] = 0; // remove last added $
const char *val = vars.get(tag); // get this by index XXX
n.cat(val);
cp += strlen(tag);
}
}
}
else
{
cp++;
}
}
l = *cp;
cp++;
}
s = n;
//TRACE(" : '%s'\n", s.chars);
}
// variable substitution
void vsubst(FSA &sa)
{
for(int i = 0; i < sa.len; i++)
vsubst(sa[i]);
}
int eval(int tl)
{
int r = 0;
while(lnum < ltot && tabs[lnum] == tl)
{
TRACE("fun: line %d: %s\n", lnum, lines[lnum]);
//for(int z = 0; z < tl; z++)
// TRACE("__%d__ ", (z+1) % 10);
//TRACE("%s\n", lines[lnum]);
FSA sa(lines[lnum]); // i have to do this every time cause var values may change
vsubst(sa);
TRACE("line %d:", lnum);
for(int j = 0; j < sa.len; j++)
TRACE(" \"%s\"", sa[j].chars);
TRACE("\n");
if(sa.len == 0)
{
// blank line
r = 0;
lnum++;
}
else
if(sa[0] == "fun")
{
FUN_FAILIF(sa.len < 2, "no function name");
FUN_FAILIF(lnum >= (ltot - 1), "unexpected end of script");
FUN_FAILIF(tabs[lnum + 1] <= tl, "empty function body");
funcs.set(sa[1], lnum + 1); // note the line number of where the function starts
lnum++;
while(tabs[lnum] > tl && lnum < ltot) // skip over the block
lnum++;
r = 0;
}
else
if(sa[0] == "if")
{
FUN_FAILIF(sa.len < 2, "no test condition")
bool b;
if(sa[1] == "fail" || sa[1] == "not" || sa[1] == "no" || sa[1] == "false")
b = (r != 0);
else
if(sa[1] == "ok" || sa[1] == "so" || sa[1] == "yes" || sa[1] == "true")
b = (r == 0);
else
{
FUN_FAIL("invalid test condition");
}
lnum++;
r = 0;
if(b)
{
r = eval(tl + 1);
if(Str::equals(lines[lnum], "else"))
{
lnum++;
while(tabs[lnum] > tl && lnum < ltot) // skip block
lnum++;
}
}
else
{
while(tabs[lnum] > tl && lnum < ltot) // skip block
lnum++;
if(Str::equals(lines[lnum], "else"))
{
lnum++;
r = eval(tl + 1);
}
}
}
else
if(sa[0] == "loop")
{
lnum++;
int count = 0;
if(sa.len > 1)
count = atoi(sa[1]);
int ltop = lnum;
r = 0;
while(true)
{
r = eval(tl + 1);
if(lnum < ltot && Str::equals(lines[lnum], "break"))
{
while(tabs[lnum] > tl && lnum < ltot) // skip to end of block
lnum++;
break;
}
if(count > 0)
{
count--;
if(count == 0)
break;
}
lnum = ltop; // repeat block
}
}
else
if(sa[0] == "break")
{
break;
}
else
if(sa[0] == "is" || sa[0] == "are")
{
FUN_FAILIF(sa.len < 3, "missing arguments for comparison");
if(sa[1] != sa[2])
{
TRACE("## NOTEQ\n");
r = 1;
}
else
TRACE("## ==\n");
lnum++;
}
else
if(sa[0] == "exit")
{
if(sa.len >= 2)
FUN_EXIT(sa[1]);
FUN_EXIT("");
}
else
if(sa[0] == "set")
{
FUN_FAILIF(sa.len < 3, "missing arguments for assignment");
FUN_FAILIF(sa[1].len < 1, "0-length variable name");
vars.set(sa[1].chars, sa[2].chars);
TRACE("set variable $%s to \"%s\"\n", sa[1].chars, sa[2].chars);
lnum++;
}
else
{
const char *cp = funcs.get(sa[0]);
if(cp)
{
int flnum = atoi(cp);
FUN_FAILIF((flnum < 0 || flnum >= ltot), "bad function lookup result");
TRACE("calling function %s @ %d\n", sa[0].chars, flnum);
if(depth >= FUN_MAX_STACK_DEPTH)
FUN_FAIL("stack overflow");
stack[depth++] = lnum;
int lnow = lnum;
lnum = flnum;
r = eval(tabs[flnum]);
depth--;
lnum = lnow;
}
else
{
r = eval(sa);
}
lnum++;
}
}
return r;
}
/*
run a script.
argument 's' will be unmolested.
returns -1 if an error ocurred, or the return code of script otherwise
*/
int eval(const char *s)
{
try
{
STR_CPY(script, s);
char *cp = script;
ltot = 0;
lnum = 0;
while(true)
{
// cut script up at line boundaries
lines[ltot] = cp;
cp = strchr(cp, '\n');
if(!cp)
break;
*cp++ = 0;
// count leading tabs on each line
int c = 0;
char *lp = lines[ltot];
while(isspace(*lp))
{
if(*lp == '\t')
c++;
else
FUN_FAIL("non-tab leading character");
lp++;
}
tabs[ltot] = c;
Str::trim(lines[ltot]);
ltot++;
lnum++;
}
TRACE("script contains %d lines\n", ltot);
lnum = 0;
funcs.clear();
vars.clear();
depth = 0;
return eval(0);
}
catch(FunExit *e)
{
int n = atoi(e->text);
delete e;
return n;
}
catch(FunError *e)
{
error(e->text);
delete e;
}
return -1;
}
// obsolete
int run(const char *s)
{
return eval(s);
}
virtual void error(const char *s) = 0;
virtual int eval(FSA &sa) = 0;
};
#endif // com_sleepless_fun_cpp
Jump to Line
Something went wrong with that request. Please try again.