Commit Graph

3 Commits

Author SHA1 Message Date
bahdotsh
24c3636075 refactor(executor): route all expressions through the evaluator
The expression evaluator was added in 2954b05, but it only handled
"complex" expressions as a fallback. Simple context references like
${{ env.FOO }} and ${{ runner.os }} were still resolved by six
separate regex preprocessors — each duplicating context resolution
logic that the evaluator already handles perfectly well.

This is the kind of redundancy that makes you maintain the same
mapping in two places and then wonder why they drift apart.

Rip out the individual regex preprocessors (preprocess_env_context,
preprocess_inputs_context, preprocess_github_context,
preprocess_runner_context, preprocess_step_outputs, and five regex
patterns). Now preprocess_expressions does exactly two things:
resolve hashFiles() (needs filesystem access), then route *all*
remaining ${{ }} through the expression evaluator. Net -46 lines.

While at it, fix three issues from code review:

- Add debug logging when expression evaluation fails in the
  substitution path, instead of silently swallowing the error
- Document the surprising Bool/String coercion in expr_eq (where
  false == "random" is true per GitHub Actions semantics)
- Add test coverage for that coercion edge case
2026-04-03 13:03:37 +05:30
bahdotsh
f4fbe57d36 fix(executor): fix correctness bugs in expression evaluator
The expression evaluator landed with a few landmines worth defusing
before they bite someone in production.

First, the EXPRESSION_FALLBACK regex used [^}]* to match expression
content, which breaks the *moment* someone uses format placeholders
like {0} inside an expression. The single } in {0} would terminate
the match early, producing garbage. Fix the regex to
(?:[^}]|\}[^}])* so it only stops at the actual }} delimiter.

Second, NaN was truthy. In IEEE 754, NaN != 0.0 is true, so
is_truthy() would happily report NaN as truthy. GitHub Actions
disagrees. Add !n.is_nan() to match the spec. While at it, guard
to_output_string() with n.is_finite() before the as-i64 cast to
prevent overflow on non-finite values.

Third, preprocess_fallback was dead code — evaluate_remaining_
expressions completely replaced its role in the pipeline. Remove it
and its tests rather than leaving a function that exists only to
confuse future readers.
2026-04-03 12:34:11 +05:30
bahdotsh
2954b05bb2 feat(executor): add GitHub Actions expression evaluator
Add a full expression evaluator for GitHub Actions `${{ }}` syntax,
replacing the regex-only substitution that failed on complex expressions
like those in dtolnay/rust-toolchain.

- Add `inputs.*`, `github.*`, `runner.*` context substitution patterns
- Build recursive-descent expression evaluator with support for:
  - Operators: `==`, `!=`, `<`, `<=`, `>`, `>=`, `&&`, `||`, `!`
  - String/number/boolean/null literals with GHA-compatible truthiness
  - Context resolution: inputs, env, github, runner, matrix, steps
  - Built-in functions: contains, startsWith, endsWith, format,
    success, failure, always, cancelled
- Integrate evaluator into substitution pipeline for complex expressions
- Replace hardcoded `evaluate_job_condition` with evaluator for `if:`
- Add missing RUNNER_OS, RUNNER_ARCH, RUNNER_NAME env vars
- Add catch-all fallback to prevent bash "bad substitution" errors
2026-04-03 12:22:55 +05:30