Skip to content

Repository files navigation

Reqlang Expression Language

A small (tiny) WIP expression language for reqlang's templating engine.

Install

Crates.io Version

[dependencies]
reqlang-expr = "0.9.0"
cargo add reqlang-expr

Usage

See USAGE.md and examples for usage examples.

Project

Verify

Syntax

The syntax is s-expression like. There are only builtin functions, identifiers and string literals.

SyntaxDescription
:aReference to the variable a
?bReference to the prompt b
!cReference to the secret c
idReference to the builtin id
@keyReference to the client context value key
(id :a)Call to builtin id with arguments: :a
`foo`String literal
12345, 456.789Number literal
trueLiteral boolean value true
falseLiteral boolean value false
StringLiteral type String
BoolLiteral type Bool
Type<String>Literal type Type of type String
Fn() -> BoolLiteral type for builtin functions (no args)
Fn(Value) -> BoolLiteral type for builtin functions (1 arg)
Fn(Value, Bool, String) -> ValueLiteral type for builtin functions (n args)
Fn(...String) -> StringLiteral type for builtin functions (variadic arg)
Fn(String, ...String) -> StringLiteral type for builtin functions (args and variadic arg)

See /spec for more syntax examples.

Builtin Functions

FnDescription
id(value: Value) -> ValueReturns the string arugment passed to it
noop() -> StringReturns the string "noop"
is_empty(value: String) -> BoolChecks if the given string is empty
and(a: Bool, b: Bool) -> BoolLogical AND operation between two booleans
or(a: Bool, b: Bool) -> BoolLogical OR operation between two booleans
cond(cond: Bool, then: Value, else: Value) -> BoolConditional expression
to_str(value: Value) -> StringConverts a value to its string representation
concat(a: String, b: String, ...rest: String) -> StringConcatenates a list of values in to a string
contains(needle: String, haystack: String) -> BoolChecks for a substring match
trim(value: String) -> StringTrim whitespace from a string
trim_start(value: String) -> StringTrim whitespace from the start of a string
trim_end(value: String) -> StringTrim whitespace from the end of a string
lowercase(value: String) -> StringReturn a lowercase version of a string
uppercase(value: String) -> StringReturn a uppercase version of a string
type(value: Value) -> TypeGet the string representation of a value's type
eq(a: Value, b: Value) -> BoolCompare two values for equality
not(value: Bool) -> BoolLogical NOT operation on a boolean value
base64encode(value: String) -> StringBase64 encode a string value
base64decode(value: String) -> StringBase64 decode a string value

Why Backticks For Strings?

These expressions will be embedded in places where double quotes are common (e.g. JSON). Single quotes weren't chosen due to their use in prose e.g. weren't

Built With

Running Examples

Lexer

Lex an expression in to a list of tokens.

cargo run -q --example lexer spec/valid/call_id.expr

stderr

[
Ok(
(
0,
LParan,
1,
),
),
Ok(
(
1,
Identifier(
"id",
),
3,
),
),
Ok(
(
4,
LParan,
5,
),
),
Ok(
(
5,
Identifier(
"noop",
),
9,
),
),
Ok(
(
9,
RParan,
10,
),
),
Ok(
(
10,
RParan,
11,
),
),
]

Parser

Parse an expression into an AST.

cargo run -q --example parser spec/valid/call_id.expr

stderr

Ok(
Call(
ExprCall {
callee: (
Identifier(
ExprIdentifier(
"id",
),
),
1..3,
),
args: [
(
Call(
ExprCall {
callee: (
Identifier(
ExprIdentifier(
"noop",
),
),
5..9,
),
args: [],
},
),
4..10,
),
],
},
),
)

Compiler

Compile an expression into bytecode to stdout.

cargo run -q --example compiler -- spec/valid/variable.expr \
--vars b \
> output.exprbin

stderr

ExprByteCode {
codes: [
1,
1,
0,
],
strings: [],
}

stdout

...BYTECODE...

Disassembler

Compile expression and disassemble it.

cargo run -q --example disassembler -- spec/valid/call_id.expr

stderr

VERSION 0800
----
0000 GET BUILTIN 0 == 'id'
0003 GET BUILTIN 1 == 'noop'
0006 CALL (0 args)
0008 CALL (1 args)

Interpreter

Interpret an expression.

cargo run -q --example interpreter -- spec/valid/greeting_name.expr \
--vars greeting=Hello \
--prompts name=World

stdout

`Hello World`

REPL

A simple REPL to interpret expressions.

cargo run -q --example repl

Or using Docker:

docker build -t reqlang-expr-repl:0.9.0 .
docker run -it --rm --read-only reqlang-expr-repl:0.9.0

Repl Mode

The REPL works in different modes:

  1. Interpret: Fully interpret an expression using the VM. This is the default.
  2. Compile: Compile an expression into its bytecode
  3. Disassemble: Compile and disassemble an expression
  4. Parse: Parse an expression into an AST
  5. Lex: Lex an expression into tokens
interpret > /mode
Current Mode: Interpret
interpret > /mode compile
compile > /mode disassemble
disassemble > /mode parse
parse > /mode lex
lex > /mode interpret
interpret > /mode
Current Mode: Interpret

Reference Last String Value

The last returned string value can be referenced using @_.

interpret > `value`
interpret > @_
`value`
interpret > (id @_)
`value`
interpret > id
builtin id(1)
interpret > (@_ `value`)
`value`

Set Variable

interpret > /set var key = value
interpret > :key
`value`

Set Prompt

interpret > /set prompt key = value
interpret > ?key
`value`

Set Secret

interpret > /set secret key = value
interpret > !key
`value`

Set Client Context

interpret > /set client key = value
interpret > @key
`value`

Print Current Environment

interpret > /env
Env {
builtins: [
BuiltinFn {
name: "id",
arity: 1,
},
BuiltinFn {
name: "noop",
arity: 0,
},
],
vars: [],
prompts: [],
secrets: [],
}
> /set var isActive = true
> /env
Env {
builtins: [
BuiltinFn {
name: "id",
arity: 1,
},
BuiltinFn {
name: "noop",
arity: 0,
},
],
vars: ["isActive"],
prompts: [],
secrets: [],
}

Exit

interpret > /exit

Example

interpret > (id :foo)
`bar`

Tests

Integration Tests

A suite of programmatic tests that validate the behavior of the lexer, parser, compiler, disassembler, and VM.

./tests/integration_tests.rs

Specification Tests

These tests read in *.expr files in the spec/ directory and compare the output against their corrosponding expected result files (e.g. *.expr.tokens, *.expr.interpreted, *.expr.disasssembled). These spec files are split between valid and invalid examples.

./tests/spec_tests.rs

Compiler and Runtime Environments

The expected result files *.expr.interpreted, *.expr.disasssembled accept a CLI formatted list of arguments from the first line. The frist line must be prefixed with //. The arguments are passed to the compiler and VM.

These CLI arguments work the same as the other reqlang-expr CLIs

--vars <VARS>... List of indexed variable names
--prompts <PROMPTS>... List of indexed prompt names
--secrets <SECRETS>... List of indexed secret names
--client-context <CLIENT_CONTEXT>... List of indexed client context names
Valid Example

./spec/valid/greeting_name.expr

(concat :greeting ` ` ?name)

./spec/valid/greeting_name.expr.interpreted

//--vars greeting=Hello --prompts name=World
`Hello World`
Invalid Example

./spec/invalid/eq_no_args.expr

(eq)

./spec/invalid/eq_no_args.expr.interpreted

[
(
CompileError(
WrongNumberOfArgs {
expected: 2,
actual: 0,
},
),
0..4,
),
]

About

A tiny (bytecode compiled, stack VM interpreted) expression language for reqlang's templating engine.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages