Lightweight, extensible, text-based scripting engine written in C++17.
This project demonstrates a simple command registration pattern where commands are defined in C++ and executed from plain-text script files.
“Ghetto BASIC with a stack frame” - OpenAI Codex
This project is intentionally built around a very small core runtime.
The core is responsible for:
- registering commands
- parsing each line into a command name plus raw argument text
- owning runtime program state
- exposing execution control and variable state
- dispatching commands
New language features should be implemented as commands whenever possible.
That means control flow, syntax conventions, and higher-level language behavior are meant to grow additively through command implementations rather than through constant core refactors.
The design goal is a tiny, stable kernel with a wide command surface, so commands can be implemented independently and in parallel.
- C++17 implementation with CMake build
- Line-based script parsing
- Built-in commands:
echo <text>wait <seconds>var <name> <value>
- Variable system with
$dereferencing - Simple macro-based command registration for adding new commands
- CMake 3.10+
- C++17-compatible compiler
- MSVC, GCC, or Clang
cmake -S . -B build
cmake --build build --config Releasecmake -S . -B build
cmake --build build -jFor this repo on Linux, prefer Ninja + ccache:
cmake --preset linux-ninja-debug
cmake --build --preset build-debugRelease build:
cmake --preset linux-ninja-release
cmake --build --preset build-releaseFrom the workspace root, you can also use the helper wrapper:
/workspace/build-fastThis temporarily switches powerprofilesctl to performance for the build and restores the previous profile on exit.
For other repos, use the generic wrapper from the workspace root:
/workspace/repo-build-fast /path/to/repoAggressive build boost plus hard drop back to lounge mode afterward:
AFTER_BUILD_MODE=lounge /workspace/repo-build-fast /path/to/repoThe simpler shell command is:
build-fast script
build-fast debug script
build-fast target script scriptWith no arguments, build-fast tries to detect the current repo from your working directory.
Detection order:
CMakePresets.jsonCMakeLists.txtmeson.buildMakefile
The executable expects a script file path as the first argument.
./build/script examples/helloWorld.scriptOn multi-config generators (for example Visual Studio), run the binary from the selected configuration directory.
Each line is interpreted as:
<command> <args>
Example (examples/helloWorld.script):
var helloWhat World!
echo Hello
wait 1
echo .
wait 1
echo .
wait 1
echo .
wait 1
echo $helloWhat
wait 2
ret 0
Variables can be defined and referenced in scripts:
Defining variables:
var <name> <value>
Using variables:
echo $variableName
Features:
- Variable names support alphanumeric characters and underscores
- Values can contain spaces if quoted:
var message "Hello World" - Variables are dereferenced automatically before command execution
- Variables persist throughout the script's lifetime
- Variables are implicitly stored as string values
Example:
var greeting "Hello, World!"
var count 42
echo $greeting
echo Count: $count
Commands are registered through the REGISTER_COMMAND macro in files under src/commands/.
All variables will be dereferenced prior to the invocation of the command callback.
Minimal example:
#include <command>
#include <iostream>
REGISTER_COMMAND
{
std::cout << args;
std::cout.flush();
};Best practices:
- Split and validate your own arguments early; use helpers from
utilswhen they fit. - Fail with a clear message when input is invalid; prefer
ERROR(...)over silent returns. - Keep command bodies small and focused on one job.
- Treat
argsas already-expanded script input, not raw source text. - Use
try/catchwhen a command can throw on normal bad input.
How registration works:
- The command name is derived from the source file basename.
- Example:
src/commands/echo.cppregisters commandecho.
- Example:
- Command files are added to
COMMAND_SOURCE_FILESinCMakeLists.txtviaGLOB.
User-facing command macros are defined in src/commands/command.
Macro implementation details live in src/commands/command_impl.h.
Primary macro for command authors.
Use this in a command source file under src/commands/ to register one command callback.
- Input: a C++ lambda body that can use
args(const std::string&) - Behavior: creates a static registration object at startup
- Command name: inferred from the file basename via
FILE_BASENAME
Use this macro for normal command implementation work.
Expands to the current command name inferred from the source file basename.
Useful in diagnostics and logging.
Expands to the current script line number at execution time.
Use this in command error messages.
Expands to the current instruction index in the active program.
This is intended for control-flow commands that need execution position.
Pushes the current instruction index onto the call stack.
Use this when implementing command-driven call/return behavior.
Pops the most recent instruction index from the call stack and restores it as the current instruction.
Returns false if the stack is empty.
Sets or overwrites a variable.
Returns the value of a variable.
This throws if the variable does not exist.
Returns whether a variable exists.
Removes a variable and returns whether one was removed.
Writes a standardized command error message including the command name and current line number.
src/
main.cpp # entry point
parser.* # line-based script parser
command.* # command line splitting
script.* # script execution
commands/ # built-in and custom command implementations
examples/
helloWorld.script # sample script
- Lines are expected to include both a command and arguments.
- Unknown command names currently result in a failed callback lookup.
compile_commands.jsonis generated by the Linux presets forclangdsupport.- The presets use
ccachewhen it is installed.
- Streamlined error handling
- Command help registration
- More commands
- Math
- Loops
- Conditionals
- Scopes
MIT. See LICENSE.