testing "= > same priority" "expr 0 = 2 \> 3" "0\n" "" ""
testing "> = same priority" "expr 3 \> 2 = 1" "1\n" "" ""
-testing "/ by zero" "expr 1 / 0; echo \$?" "2\n" "" ""
-testing "% by zero" "expr 1 % 0; echo \$?" "2\n" "" ""
+testing "/ by zero" "expr 1 / 0 2>/dev/null; echo \$?" "2\n" "" ""
+testing "% by zero" "expr 1 % 0 2>/dev/null; echo \$?" "2\n" "" ""
testing "regex position" "expr ab21xx : '[^0-9]*[0-9]*'" "4\n" "" ""
testing "regex extraction" "expr ab21xx : '[^0-9]*\([0-9]*\).*'" "21\n" "" ""
testing "exit code when true" "expr a; echo \$?" "a\n0\n" "" ""
testing "exit code when false" "expr 0; echo \$?" "0\n1\n" "" ""
-testing "exit code with syntax error" "expr \(; echo \$?" "2\n" "" ""
+testing "exit code with syntax error" "expr \( 2>/dev/null; echo \$?" \
+ "2\n" "" ""
testing "exit code when evaluating to 0" "expr -1 + 1; echo \$?" "0\n1\n" "" ""
# BUG: segfaults because '3' is coerced to integer and regexc gets NULL
testing "regex segfault" "expr 3 : '\(.\)'" "3\n" "" ""
# syntax errors
-testing "no expression" "expr; echo \$?" "2\n" "" ""
-testing "empty ()" "expr \( \); echo \$?" "2\n" "" ""
-testing "missing )" "expr \( 1; echo \$?" "2\n" "" ""
-testing "missing outer )" "expr \( 1 + \( 2 \* 3 \); echo \$?" "2\n" "" ""
-testing "bad operator" "expr 1 @ 2; echo \$?" "2\n" "" ""
-testing "adjacent literals" "expr 1 2; echo \$?" "2\n" "" ""
-testing "non-integer argument" "expr 1 + a; echo \$?" "2\n" "" ""
+testing "no expression" "expr 2>/dev/null; echo \$?" "2\n" "" ""
+testing "empty ()" "expr \( \) 2>/dev/null; echo \$?" "2\n" "" ""
+testing "missing )" "expr \( 1 2>/dev/null; echo \$?" "2\n" "" ""
+testing "missing outer )" "expr \( 1 + \( 2 \* 3 \) 2>/dev/null; echo \$?" \
+ "2\n" "" ""
+testing "bad operator" "expr 1 @ 2 2>/dev/null; echo \$?" "2\n" "" ""
+testing "adjacent literals" "expr 1 2 2>/dev/null; echo \$?" "2\n" "" ""
+testing "non-integer argument" "expr 1 + a 2>/dev/null; echo \$?" "2\n" "" ""
*
* The web standard is incomplete (precedence grouping missing), see:
* http://permalink.gmane.org/gmane.comp.standards.posix.austin.general/10141
+ *
+ * eval_expr() uses the recursive "Precedence Climbing" algorithm:
+ *
+ * Clarke, Keith. "The top-down parsing of expressions." University of London.
+ * Queen Mary College. Department of Computer Science and Statistics, 1986.
+ *
+ * http://www.antlr.org/papers/Clarke-expr-parsing-1986.pdf
+ *
+ * Nice explanation and Python implementation:
+ * http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing
USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN))
#include "toys.h"
GLOBALS(
- char* tok; // current token, not on the stack since recursive calls mutate it
+ char *tok; // current token, not on the stack since recursive calls mutate it
)
// Scalar value. If s != NULL, it's a string, otherwise it's an int.
long long i;
};
-void syntax_error(char *msg, ...) {
- if (0) { // detailed message for debugging. TODO: add CFG_ var to enable
- va_list va;
- va_start(va, msg);
- verror_msg(msg, 0, va);
- va_end(va);
- xexit();
- } else
- error_exit("syntax error");
-}
-
#define LONG_LONG_MAX_LEN 21
// Get the value as a string.
-void get_str(struct value *v, char** ret)
+void get_str(struct value *v, char **ret)
{
- if (v->s)
- *ret = v->s;
+ if (v->s) *ret = v->s;
else {
*ret = xmalloc(LONG_LONG_MAX_LEN);
snprintf(*ret, LONG_LONG_MAX_LEN, "%lld", v->i);
{
if (v->s) {
char *endp;
+
*ret = strtoll(v->s, &endp, 10);
- return *endp ? 0 : 1; // If endp points to NUL, all chars were converted
- } else {
- *ret = v->i;
- return 1;
- }
+
+ if (*endp) return 0; // If endp points to NUL, all chars were converted
+ } else *ret = v->i;
+
+ return 1;
}
// Preserve the invariant that v.s is NULL when the value is an integer.
// Check if v is 0 or the empty string.
static int is_false(struct value *v)
{
- if (v->s)
- return !*v->s || !strcmp(v->s, "0"); // get_int("0") -> 0
- else
- return !v->i;
+ if (v->s) return !*v->s || !strcmp(v->s, "0"); // get_int("0") -> 0
+ return !v->i;
}
// 'ret' is filled with a string capture or int match position.
xregcomp(&pat, pattern, 0);
if (!regexec(&pat, target, 2, m, 0) && m[0].rm_so == 0) { // match at pos 0
- regmatch_t* g1 = &m[1]; // group capture 1
+ regmatch_t *g1 = &m[1]; // group capture 1
if (pat.re_nsub > 0 && g1->rm_so >= 0) // has capture
ret->s = xmprintf("%.*s", g1->rm_eo - g1->rm_so, target + g1->rm_so);
- else
- assign_int(ret, m[0].rm_eo);
+ else assign_int(ret, m[0].rm_eo);
} else { // no match
- if (pat.re_nsub > 0) // has capture
- ret->s = "";
- else
- assign_int(ret, 0);
+ // has capture
+ if (pat.re_nsub > 0) ret->s = "";
+ else assign_int(ret, 0);
}
}
{NULL, 0, 0, 0}, // sentinel
};
-void eval_op(struct op_def *o, struct value *ret, struct value *rhs) {
+void eval_op(struct op_def *o, struct value *ret, struct value *rhs)
+{
long long a, b, x = 0; // x = a OP b for ints.
char *s, *t; // string operands
int cmp;
}
}
-// Point TT.tok at the next token. It's NULL when there are no more tokens.
-void advance() {
- TT.tok = *toys.optargs++;
-}
-
-// Evalute a compound expression, setting 'ret'.
-//
-// This function uses the recursive "Precedence Climbing" algorithm:
-//
-// Clarke, Keith. "The top-down parsing of expressions." University of London.
-// Queen Mary College. Department of Computer Science and Statistics, 1986.
-//
-// http://www.antlr.org/papers/Clarke-expr-parsing-1986.pdf
-//
-// Nice explanation and Python implementation:
-// http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing
+// Evalute a compound expression using recursive "Precedence Climbing"
+// algorithm, setting 'ret'.
static void eval_expr(struct value *ret, int min_prec)
{
- if (!TT.tok) syntax_error("Unexpected end of input");
+ if (!TT.tok) error_exit("Unexpected end of input");
// Evaluate LHS atom, setting 'ret'.
if (!strcmp(TT.tok, "(")) { // parenthesized expression
- advance(); // consume (
+ TT.tok = *toys.optargs++; // consume (
eval_expr(ret, 1); // We're inside ( ), so min_prec = 1
- if (!TT.tok) syntax_error("Expected )");
- if (strcmp(TT.tok, ")")) syntax_error("Expected ) but got %s", TT.tok);
- advance(); // consume )
+ if (!TT.tok) error_exit("Expected )"); // TODO: Also says that for ()
+ if (strcmp(TT.tok, ")")) error_exit("Expected ) but got %s", TT.tok);
+ TT.tok = *toys.optargs++; // consume )
} else { // simple literal
ret->s = TT.tok; // all values start as strings
- advance();
+ TT.tok = *toys.optargs++;
}
// Evaluate RHS and apply operator until precedence is too low.
}
if (!o->tok) break; // Not an operator (extra input will fail later)
if (o->prec < min_prec) break; // Precedence too low, pop a stack frame
- advance();
+ TT.tok = *toys.optargs++;
eval_expr(&rhs, o->prec + 1); // Evaluate RHS, with higher min precedence
eval_op(o, ret, &rhs); // Apply operator, setting 'ret'
{
struct value ret = {0};
toys.exitval = 2; // if exiting early, indicate error
-
- advance(); // initialize global token
+ TT.tok = *toys.optargs++; // initialize global token
eval_expr(&ret, 1);
- if (TT.tok) syntax_error("Unexpected extra input '%s'\n", TT.tok);
+ if (TT.tok) error_exit("Unexpected extra input '%s'\n", TT.tok);
if (ret.s) printf("%s\n", ret.s);
else printf("%lld\n", ret.i);