SELSimple Expression Language
GitHub

A REPL in thirty lines

The smallest useful SEL program: read a line, run it, print the result, keep the variables for the next line. It uses nearly the whole host API — compile, run against a context that persists, dependencies(), and errors with their codes and positions — and nothing else.

Two commands besides SEL itself: :deps <expression> lists what an expression reads, and :reset empties the context.

python
def show(value):
    if value.is_bool():
        return 'TRUE' if value.as_bool() else 'FALSE'
    if value.is_null():
        return 'NULL'
    if value.size() > 0 or value.is_bin():
        return value.dump()
    return value.as_text()


context = Value.none()
for line in sys.stdin:
    line = line.rstrip('\n')
    if line.strip() == '':
        continue
    print('sel>', line)
    try:
        if line == ':reset':
            context = Value.none()
        elif line.startswith(':deps '):
            print(' '.join(compile(line[6:]).dependencies()))
        else:
            print(show(compile(line).run(context)))
    except SelError as e:
        print(f'{e.code} at {e.line}:{e.col}')

Four details every version shares, because the five print the same bytes:

  • One context lives across lines, so A = (1, 2, 3) on one line is A on the next. :reset replaces it with an empty one.
  • A result is shown in SEL's own spelling: TRUE/FALSE rather than the host's true, 1 or T; a structure as its dump; text as it is.
  • An error prints its code and position, not its message. The code and the position are part of the language's contract and identical everywhere; the message is human text that may be worded differently.
  • Compiling is separate from running — IF(1, "a", "b") compiles and then fails when run, and MISSING + 1 compiles and then finds nothing to read.

A session

Fed session.txt, every version prints:

text
sel> 2.50 + 2.50
5.00
sel> A = (1, 2, 3)
-{"1"=t"1", "2"=t"2", "3"=t"3"}
sel> SUM(A, _ * 10)
60
sel> "total: {SUM(A, _)}"
total: 6
sel> A .> MAP(_ * _) .> JOIN(", ")
1, 4, 9
sel> 1 / 3
0.3333333333
sel> A[2] == 2
TRUE
sel> IF(1, "a", "b")
E_NOT_BOOL at 1:4
sel> MISSING + 1
E_UNDEF_VAR at 1:1
sel> :deps QTY * PRICE > LIMIT
LIMIT PRICE QTY
sel> :reset
sel> A
E_UNDEF_VAR at 1:1
sel> RMATCH('^\d{2}-\d{3}$', "31-874")
TRUE

The project's own command-line tools — node js/bin/sel.mjs, php php/bin/sel, cpp/build/sel, lisp/bin/sel, python3 -m sel — are this loop with line editing, -e 'expr' for one-shot use and --deps for dependencies.

View this page's Markdown on GitHub