Vidyax Language Reference (v1.1)¶
This document lists all of Vidyax's syntax, types, built-in functions, CLI commands, and error messages. To learn from scratch, see GUIDE.md.
Execution model¶
Vidyax has two execution engines that share one runtime:
| Command | Engine | Notes |
|---|---|---|
vidyax run |
transpiler → Python | the main path; translates .vx to Python, then runs it |
vidyax walk |
tree-walker | interprets the syntax tree directly; useful for debugging/comparison |
Both call the same runtime helpers, so their results are identical by
construction. This is tested automatically: vidyax test runs every case
on both engines and requires the output to match exactly.
There is also a third engine — the C Virtual Machine (see VVM_SPEC.md) — validated against these two with the same test suite.
Basic syntax¶
- One statement per line. No semicolons.
- Blocks are marked by indentation (spaces), like Python.
- Comments start with
#and run to the end of the line.
Value types¶
| Type | Example literal | Notes |
|---|---|---|
| number | 42, 3.14, -7 |
integers & decimals used together |
| text | "hello" |
double quotes |
| boolean | true, false |
|
| empty | null |
absence of a value |
| list | [1, 2, 3], ["a","b"] |
may mix types |
type(x) returns the type name as text: "number", "text", "bool",
"null", or "list".
Variables¶
- Assignment uses
:(not=). - Built-in function names are forbidden as variable names, function
names, parameters, loop variables, or
catchvariables. This avoids cross-engine mismatches and is caught early byvidyax check. - Scoping rule: a name assigned inside a function is local to that function. Reading a local variable before it has a value is an error. Reading a name never assigned locally falls through to the outer scope (so you can read globals).
Operators¶
Arithmetic: + - * / %
+concatenates text as well as adding numbers. If one side is text, the other is converted to text automatically.+also joins two lists./always produces a decimal; division by zero raisescannot divide by 0.
Comparison: == != < <= > >= → produce booleans.
Logic: and or not.
Index: list[i] or text[i], counting from 0. Out-of-range index
raises index out of range.
Member access: object.member. Only Vidyax runtime modules (namely
ai) have members. Underscore-prefixed members (_) cannot be accessed
(this prevents leaking Python internals).
Conditionals¶
elif and else are optional. A value is considered "true" when: the
boolean true, a non-zero number, or a non-empty text/list.
Loops¶
Repeat N times — N must be a number (otherwise →
'rpt' needs a number):
Iterate a list or text — the source must be a list/text (otherwise →
'for ... in' needs a list or text):
Loop control:
break— exit the loop.continue— skip to the next iteration.
break/continue outside a loop is a parse-time error
('break' only works inside a loop). A break inside a function cannot
target a loop outside that function.
Functions¶
returnmay omit a value (returningnull), or return a value.returnoutside a function is a parse-time error ('return' only works inside a function).- The number of arguments must match the number of parameters; otherwise
→
function 'name' needs N args, got M. - Recursive functions are supported.
Error handling¶
The catch variable is optional:
All runtime errors (division by zero, out-of-range index, get failure,
etc.) can be caught. Error messages are identical across both execution
engines.
Input¶
Displays the prompt and reads one line of user input as text.
Built-in functions¶
| Function | Result |
|---|---|
len(x) |
length of a text or list |
range(n) |
list [0, 1, ..., n-1] |
range(a, b) |
list [a, ..., b-1] |
text(x) |
convert a value to text |
num(x) |
convert text/value to a number |
upper(s) |
text to UPPERCASE |
lower(s) |
text to lowercase |
split(s, sep=" ") |
split text into a list by a separator |
join(lst, sep="") |
join a list into text with a separator |
push(lst, x) |
append x to the end of a list |
abs(x) |
absolute value |
sum(x) |
sum of all items in a list |
min(...) |
smallest value |
max(...) |
largest value |
type(x) |
type name as text |
get(url) |
fetch a URL's contents as text (raises on failure) |
readfile(path) |
read a text file's contents (raises on failure) |
writefile(path, x) |
write x (as text) to a file, replacing it |
floor(x) |
round down to a whole number |
ceil(x) |
round up to a whole number |
round(x) |
round to the nearest whole number (half away from zero) |
round(x, digits) |
round to digits decimal places |
sqrt(x) |
square root (x must be ≥ 0) |
pow(x, y) |
x raised to the power y |
random() |
random number ≥ 0 and < 1 |
random(a, b) |
random whole number from a to b (inclusive) |
replace(s, old, new) |
replace every old in s with new |
trim(s) |
remove spaces/tabs/newlines from both ends |
contains(x, item) |
true if list x has item, or text x has that substring |
startswith(s, p) |
true if text s starts with p |
endswith(s, p) |
true if text s ends with p |
pop(lst) / pop(lst, i) |
remove & return the last item (or item i) |
remove(lst, x) |
remove the first x from the list (raises if absent) |
insert(lst, i, x) |
insert x at position i (out-of-range goes to an end) |
sort(lst) |
sort the list in place (items must be the same type) |
reverse(lst) |
reverse the list in place |
find(x, item) |
first index of item in a list/text, -1 if absent |
slice(x, a, b) |
copy of items a..b-1 of a list/text (negatives count from the end) |
sleep(secs) |
pause for that many seconds |
now() |
current time in epoch seconds (for measuring durations) |
wait(t) |
result of a task made with go (re-raises its error here) |
Tasks (go / wait)¶
t1: go ai.ask "jelaskan gravitasi" # starts a task, returns immediately
t2: go get("https://example.com") # runs concurrently with t1
print wait(t1) # blocks until t1 finishes
print wait(t2)
go f(args) runs a call as a concurrent task (a value of type
"task"). Arguments are evaluated eagerly in the caller. Tasks
interleave only while waiting on I/O (get, ai.ask, files,
sleep) — pure computation never interleaves, so data races are
impossible. An error inside a task re-raises at its wait(t) (catchable
with try/catch); a failed task nobody waited for is reported when the
program ends. Design + roadmap: docs/CONCURRENCY.md. Runs on ALL four
engines: the default engine, the walker, the VVM, and native binaries.
The names above are reserved — they cannot be overwritten.
On the VVM (
vidyax bytecode+vxvm),get()needs--allow-netandreadfile()/writefile()need--allow-fs— both are denied by default in the sandbox.
The ai module¶
Enable it with use ai. The ai object appears in the program's scope.
| Member | Function |
|---|---|
ai.ask "..." |
send a prompt, return the answer as text |
ai.system "..." |
set a system instruction (persona/rules) |
ai.open "..." |
change the model or provider |
ai.model |
the active model name |
ai.provider |
the active provider name (groq / openai) |
ai.system_prompt |
the active system instruction |
ai.open format:
ai.open "model-name"— change model, keep provider.ai.open "provider:model"— change both, e.g."openai:gpt-4o-mini".
Default: provider groq, model llama-3.1-8b-instant. Can be overridden
via the VIDYAX_MODEL environment variable.
API keys (per provider):
| Provider | Environment | URL |
|---|---|---|
| groq | GROQ_API_KEY |
api.groq.com |
| openai | OPENAI_API_KEY |
api.openai.com |
Unknown provider → unknown AI provider. Key not set → a message naming
the required environment variable.
CLI commands¶
| Command | Function |
|---|---|
vidyax run <file.vx> |
run a program (transpiler path; default) |
vidyax walk <file.vx> |
run through the tree-walker |
vidyax build <file.vx> |
translate to a standalone Python .py file |
vidyax bytecode <file.vx> |
compile to VVM bytecode .vxc |
vidyax disasm <file.vxc> |
disassemble VVM bytecode (also accepts a .vx) |
vidyax debug <file.vx> |
run under the VVM line debugger (b/c/s/n/bt/locals) |
vidyax profile <file.vx> |
run on the VVM + per-function/per-line instruction profile |
vidyax native <file.vx> [-o out] |
compile to a standalone native binary (needs a C compiler) |
vidyax check <file.vx> |
static check only, output errors as JSON (- = stdin) |
vidyax lsp |
start the Language Server Protocol server (stdio) |
vidyax install <user/repo\|url> |
download a module into vx_modules/ |
vidyax test |
run the built-in tests (both engines) |
vidyax <file.vx> |
same as run |
Running with no arguments opens the interactive REPL:
- A line that opens a block (
if x:,func f():,try:, …) switches to the...continuation prompt; keep typing the block's lines. - A blank line runs the block — unless it is still unfinished (a
try:waits for itscatch:before running). - A bare expression echoes its value, like a calculator.
- Ctrl-C cancels a half-typed block or stops a running program without leaving the REPL; Ctrl-D exits.
Roadmap commands (recognized but not yet runnable): fmt.
Sandbox (sandbox deny ...)¶
sandbox deny fs:
jawaban: agenku("ringkas data ini") # AI boleh (net), file TIDAK
...
sandbox deny net, fs:
...pure text processing, no authority at all...
sandbox deny net and/or fs: runs a block with capabilities
removed: net blocks get/ai.ask/agents, fs blocks
readfile/writefile. A sandbox can only reduce permissions — nesting
tightens further, and leaving the block (normally, via a caught error,
return, or break) restores the outer set. Functions called inside
are restricted too (the capability travels with control flow), and a
task spawned inside (go) carries the reduced set for its whole life.
Use one sandbox per agent's work zone so a hallucinating or
prompt-injected reply can never trick that code path into touching files
or the network beyond what you granted it.
Agents (agent)¶
agent guru:
model "llama-3.1-8b-instant" # optional (this is the default)
system "kamu guru fisika, jawab singkat"
print guru("apa itu gravitasi?")
print guru("jelaskan lebih sederhana") # remembers the conversation
agent name: declares a stateful AI persona: model and system
are fixed at declaration, and every call appends to a running
conversation, so the agent remembers earlier messages. An agent is
called like a one-argument function and works with go/wait
(t: go guru("...")). One agent = one conversation — for independent
parallel work, declare several agents. type(a) is "agent"; printing
one shows <agent name>. Only model and system lines are allowed in
the block, and both are optional.
Modules (use) and packages (vidyax install)¶
use name includes another Vidyax file. name is resolved, at parse
time, to name.vx — looked up next to the program, then in vx_modules/,
then in ~/.vidyax/modules/. The module's top-level definitions become
available as if written inline; a module is included once per program
(diamonds are fine), and circular use is an error. use ai stays the
built-in AI module. Because resolution happens in the shared front-end,
modules behave identically on all four engines.
vidyax install user/repo downloads a single-file module (the repo's
<repo>.vx) into vx_modules/. Variants:
vidyax install user/repo@ref— a specific branch/tag.vidyax install user/repo/path/to/lib.vx— a specific file in the repo.vidyax install https://…/mod.vx— any direct URL.
The download is parsed before it is saved, so a broken file never lands
on disk. v1 has no version resolution or dependency locking; a module's
own use lines are resolved relative to the module.
Language Server (vidyax lsp)¶
A dependency-free LSP server over stdio for any LSP-capable editor:
live diagnostics (same errors as vidyax check), completion
(keywords, all builtins with docs, names defined in the file), hover
docs for builtins, and document symbols (outline). Example client
config for Neovim 0.11+:
vim.lsp.config['vidyax'] = { cmd = { 'vidyax', 'lsp' }, filetypes = { 'vidyax' } }
vim.lsp.enable('vidyax')
Error message catalog¶
| Message | Cause |
|---|---|
variable 'X' is not defined |
reading a variable that doesn't exist |
variable 'X' is assigned in this function but used before it has a value |
reading a local before it's assigned |
'X' is a built-in function name — pick a different name |
overwriting a built-in name |
cannot divide by 0 |
division by zero |
index out of range |
list/text index out of bounds |
'rpt' needs a number |
rpt with a non-number value |
'for ... in' needs a list or text |
iterating a non-list/text source |
this is not a function |
calling a value that isn't a function |
function 'X' needs N args, got M |
wrong number of arguments |
member 'X' is private |
accessing an underscore-prefixed member |
object has no member 'X' |
accessing a member on a value with no members |
'ai' has no member 'X' |
unknown ai member |
unknown AI provider 'X' |
unknown AI provider |
'break' only works inside a loop |
break/continue outside a loop |
'return' only works inside a function |
return outside a function |
get() failed: ... |
get(url) failed (connection/HTTP) |
All messages are consistent between run and walk.