commit ce22557fc8cb164acd92288a244669cc4651ec03
parent dca693c3073799b8d46cc9f6479839ea4c8d913d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 23 Apr 2026 17:04:40 -0700
m1pp: add strlen expression op
Diffstat:
4 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/m1pp/m1pp.c b/m1pp/m1pp.c
@@ -102,6 +102,7 @@ enum ExprOp {
EXPR_LE,
EXPR_GT,
EXPR_GE,
+ EXPR_STRLEN,
EXPR_INVALID
};
@@ -804,6 +805,9 @@ static enum ExprOp expr_op_code(const struct Token *tok)
if (token_text_eq(tok, ">=")) {
return EXPR_GE;
}
+ if (token_text_eq(tok, "strlen")) {
+ return EXPR_STRLEN;
+ }
return EXPR_INVALID;
}
@@ -932,6 +936,7 @@ static int apply_expr_op(enum ExprOp op, const long long *args, int argc, long l
}
*out = (args[0] >= args[1]);
return 1;
+ case EXPR_STRLEN:
case EXPR_INVALID:
break;
}
@@ -1021,13 +1026,34 @@ static int eval_expr_range(struct TokenSpan span, long long *out)
if (op == EXPR_INVALID) {
return fail("bad expression");
}
+ pos++;
+ if (op == EXPR_STRLEN) {
+ /* strlen is degenerate: argument is a TOK_STRING atom,
+ * not a recursive expression. Handle inline and yield
+ * the string's raw byte count (span.len - 2). */
+ skip_expr_newlines(&pos, span.end);
+ if (pos >= span.end || pos->kind != TOK_STRING) {
+ return fail("bad expression");
+ }
+ if (pos->text.len < 2 || pos->text.ptr[0] != '"') {
+ return fail("bad expression");
+ }
+ value = (long long)(pos->text.len - 2);
+ pos++;
+ skip_expr_newlines(&pos, span.end);
+ if (pos >= span.end || pos->kind != TOK_RPAREN) {
+ return fail("bad expression");
+ }
+ pos++;
+ have_value = 1;
+ continue;
+ }
if (frame_top >= MAX_EXPR_FRAMES) {
return fail("expression overflow");
}
frames[frame_top].op = op;
frames[frame_top].argc = 0;
frame_top++;
- pos++;
continue;
}
diff --git a/tests/m1pp/04-expr-ops.M1pp b/tests/m1pp/04-expr-ops.M1pp
@@ -33,4 +33,11 @@ $((>= 5 6))
# nested expressions
$((+ (* 2 3) (- 7 4) (/ 12 3)))
+
+# strlen: raw byte count between the quotes (matches what M1's "..." emits
+# before appending NUL). Composes with arithmetic like any other op.
+%((strlen "hello"))
+%((+ (strlen "hello") 1))
+!((strlen "x"))
+%((strlen ""))
END
diff --git a/tests/m1pp/04-expr-ops.expected b/tests/m1pp/04-expr-ops.expected
@@ -33,4 +33,11 @@
'0D00000000000000'
+
+
+
+'05000000'
+'06000000'
+'01'
+'00000000'
END
diff --git a/tests/m1pp/_04-strlen-badarg.M1pp b/tests/m1pp/_04-strlen-badarg.M1pp
@@ -0,0 +1,7 @@
+# Malformed: strlen requires a double-quoted TOK_STRING argument.
+# Single-quoted '...' hex literals are meaningless for strlen and must
+# be rejected. Expected behavior: non-zero exit from the expander.
+# (Underscore-prefixed filename so test.sh skips this fixture.)
+
+%((strlen 'deadbeef'))
+END