-
Notifications
You must be signed in to change notification settings - Fork 0
/
calc.pike
148 lines (134 loc) · 6.21 KB
/
calc.pike
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#if constant(G)
inherit builtin_command;
#else
string expand_variables(string text, mapping|void vars, mapping|void users) {
string result = "42";
werror("Substituting vars into %O users %O --> %O\n", text, users, result);
return result;
}
string set_variable(string var, string val, string action, mapping|void users) {
werror("Setting %O to %O users %O\n", var, val, users);
return "278";
}
int main(int argc, array(string) argv) {
mixed ex = catch {write("Result: %O\n", evaluate(argv[1..] * " ", ({this, (["users": (["": "49497888"])])})));};
if (ex) write("Invalid expression: %s\n", (describe_error(ex) / "\n")[0]);
}
#endif
constant KEYWORDS = (<"x">);
//Define functions here that can be called in expressions
//Note that the function name (after the "func_" prefix) must match the sscanf
//in evaluate()'s inner tokenizer function.
int|float func_random(array(int|float) args) {
if (sizeof(args) != 1) error("random() requires precisely one argument\n");
//If it looks like an integer, return a random integer.
if (args[0] == (int)args[0]) return random((int)args[0]);
return random(args[0]); //Otherwise a float.
}
int func_int(array(string) args) {
if (sizeof(args) != 1) error("int() requires precisely one argument\n");
return (int)args[0];
}
float func_float(array(string) args) {
if (sizeof(args) != 1) error("float() requires precisely one argument\n");
return (float)args[0];
}
string func_string(array(string) args) {
if (sizeof(args) != 1) error("string() requires precisely one argument\n");
return (string)args[0];
}
int|float func(string word, string open, array(int|float) args, string close) {
function func = this["func_" + word];
if (!func) error("Unknown function " + word + "\n");
return func(args);
}
int|float func_nullary(string word, string open, string close) {return func(word, open, ({ }), close);}
int|float binop(int|float left, string op, int|float right) {
switch (op) {
#define BINARY(o) case #o: return left o right
BINARY(+); BINARY(-); BINARY(*); BINARY(/); BINARY(%);
BINARY(<); BINARY(<=); BINARY(==); BINARY(!=); BINARY(>=); BINARY(>);
BINARY(&&); BINARY(||); BINARY(**);
#undef BINARY
//Some aliases for the convenience of humans
//We won't have assignment here (or if we do, bring on the
//walrus operator), nor bitwise operations, so it's nicer
//to let people use these in other natural ways.
case "=": return left == right;
case "&": return left && right;
case "|": return left || right;
case "^": return left ** right;
case "=>": return left >= right;
case "x": return left * right;
}
}
string stitch(string ... parts) {return parts * "";}
int makeint(string digits) {sscanf(digits, "%d", int ret); return ret;} //Always parse as decimal
float makefloat(string digits) {return (float)digits;}
int|float parens(string open, int|float val, string close) {return val;}
array(int|float) make_array(int|float val) {return ({val});}
array(int|float) prepend_array(int|float val, string _, array(int|float) arr) {return ({val}) + arr;}
//To allow variable lookups, the @ token must be adorned with a context.
//Thus "@spam" will carry with it, at very least, the channel to which the
//variable belongs. Note that all use of variable lookups/assignments is
//undocumented and unstable, and should not be depended upon.
int|float|string varlookup(array context, string varname) {
if (!context) error("Variable references require full context\n");
return context[0]->expand_variables("$" + varname + "$", ([]), context[1]->users);
}
int|float|string varassign(array context, string eq1, string varname, string eq2, int|float|string value) {
if (!context) error("Variable references require full context\n");
//Assignment returns the RHS.
return context[0]->set_variable(varname, (string)value, "set", context[1]->users);
}
Parser.LR.Parser parser = Parser.LR.GrammarParser.make_parser_from_file("modules/calc.grammar");
void throw_errors(int level, string subsystem, string msg, mixed ... args) {if (level >= 2) error(msg, @args);}
int|float evaluate(string formula, mixed|void ctx) {
parser->set_error_handler(throw_errors);
array|string next() {
sscanf(formula, "%*[ \t\n]%s", formula);
if (formula == "") return "";
sscanf(formula, "%[*&|<=>!]%s", string token, formula); //All characters that can be part of multi-character tokens
if (token != "") return token;
sscanf(formula, "%[a-zA-Z]%s", token, formula);
if (KEYWORDS[token]) return token; //Special keywords are themselves and can't be function names.
if (token != "") return ({"word", token}); //Possibly a function/variable name.
if (formula[0] == '"' && sscanf(formula, "%O%s", token, formula)) return ({"string", token}); //String literal
sscanf(formula, "%1s%s", token, formula); //Otherwise, grab a single character
if (token == "@") return ({token, ctx}); //Variable references require context
return token;
}
//array|string shownext() {array|string ret = next(); werror("TOKEN: %O\n", ret); return ret;}
return parser->parse(next, this);
}
constant builtin_description = "Perform arithmetic calculations";
constant builtin_name = "Calculator";
constant builtin_param = "Expression";
constant vars_provided = ([
"{result}": "The result of the calculation",
]);
constant command_suggestions = (["!calc": ([
"_description": "Calculate a simple numeric expression/formula",
"conditional": "catch",
"message": ([
"builtin": "calc", "builtin_param": ({"%s"}),
"message": "@$$: {result}",
]),
"otherwise": "@$$: {error}",
])]);
mapping message_params(object channel, mapping person, array params, mapping cfg) {
string param = params[0];
if (param == "") error("Usage: !calc 1+2\n");
if (person->badges->?_mod) param = channel->expand_variables(param);
mixed ex = catch {
int|float|string result = evaluate(param, ({channel, cfg}));
//"!calc 1.5 + 2.5" will give a result of 4.0, but it's nicer to say "4"
if (floatp(result) && result == (float)(int)result) result = (int)result;
if (!stringp(result)) result = sprintf("%O", result);
return (["{result}": result]);
};
error("Invalid expression [" + (describe_error(ex)/"\n")[0] + "]"); //The default errors don't really explain that they're expression parse errors.
}
#if constant(G)
protected void create(string name) {::create(name); G->G->evaluate_expr = evaluate;}
#endif