ASF — Syntax Reference
This document defines the concrete syntax supported by the Advanced Scripting Framework (ASF) and contains a compact BNF grammar, operator precedence, and examples.
Note: ASF is embedded in VBA — scripts are supplied to the ASF compiler which tokenizes and produces an AST that the VM executes. Semicolons (
;) are used as statement separators and are required in ambiguous cases; comma (,) is only used as an argument or element separator.
Quick summary
- Statement separator:
; - Argument/element separator:
, - Single-line comment:
/* ... */(also supports C-style token comments in parser) - Function literal:
fun (params...) { ... } - Top-level function declaration:
fun name(params...) { ... } - COM prototype method declaration:
prototype.COM.ObjectType methodName(params...) { ... }(v3.1.2+) - Anonymous function values are closures with shared-write semantics (they capture the current runtime scope by reference).
- Array literal:
[ elem1, elem2, ... ] - Object literal:
{ key1: value1, key2: value2 } - VBExpression block:
@(...)— raw VBAexpressions block evaluated via the VBA-Expressions bridge. - Null literal:
null(literal representing absence of value) - Boolean literals:
true,false - Module system:
import/exportstatements for organizing code across.vasfiles - Working directory builtins:
cwd()andscwd(path)for managing module resolution paths
BNF grammar (compact)
This BNF uses a mixture of concrete tokens and non-terminals to show the language shape.
<program> ::= <stmts>
<stmts> ::= <stmt> ( ';' <stmt> )* [ ';' ]
<stmt> ::= <if-stmt>
| <for-stmt>
| <while-stmt>
| <try-stmt>
| <switch-stmt>
| <return-stmt>
| <break-stmt>
| <continue-stmt>
| <expr-stmt>
| <print-stmt>
| <func-decl>
| <class-decl>
| <prototype-decl>
| <let-decl>
| <import-stmt>
| <export-stmt>
| <array-destructuring>
<if-stmt> ::= 'if' '(' <expr> ')' <block>
( 'elseif' '(' <expr> ')' <block> )*
[ 'else' <block> ]
<for-stmt> ::= 'for' '(' <expr> ',' <expr> ',' <expr> ')' <block>
| 'for' '(' IDENT 'in' <expr> ')' <block>
| 'for' '(' IDENT 'of' <expr> ')' <block>
<while-stmt> ::= 'while' '(' <expr> ')' <block>
<try-stmt> ::= 'try' <block> 'catch' '(' IDENT ')' <block>
| 'try' <block> 'catch' <block>
<switch-stmt> ::= 'switch' '(' <expr> ')' '{'
( 'case' <expr> <block> )*
[ 'default' <block> ]
'}'
<return-stmt> ::= 'return' [ '(' <expr> ')' | <expr> ]
<break-stmt> ::= 'break'
<continue-stmt> ::= 'continue'
<expr-stmt> ::= <expr>
| 'super' '(' <arglist> ')'
<print-stmt> ::= 'print' '(' <arglist> ')'
<func-decl> ::= 'fun' IDENT '(' <paramlist> ')' <block>
<class-decl> ::= 'class' IDENT [ 'extends' IDENT ] '{' <class-body> '}'
<prototype-decl> ::= 'prototype' '.' 'COM' '.' IDENT IDENT '(' <paramlist> ')' <block>
<class-body> ::= <class-member>*
<class-member> ::= <field-decl>
| <constructor-decl>
| <method-decl>
| <static-method-decl>
<field-decl> ::= 'field' <field-list> ';'
<field-list> ::= <field-item> ( ',' <field-item> )*
<field-item> ::= IDENT [ '=' <expr> ]
<constructor-decl> ::= 'constructor' '(' <paramlist> ')' <block>
<method-decl> ::= IDENT '(' <paramlist> ')' <block>
<static-method-decl> ::= 'static' IDENT '(' <paramlist> ')' <block>
<let-decl> ::= 'let' IDENT [ '=' <expr> ]
<import-stmt> ::= 'import' <import-clause> 'from' STRING
<import-clause> ::= IDENT // default import
| '{' <named-imports> '}' // named imports
| '*' 'as' IDENT // namespace import
| IDENT ',' '{' <named-imports> '}' // mixed import
<named-imports> ::= <import-specifier> ( ',' <import-specifier> )*
<import-specifier> ::= IDENT [ 'as' IDENT ]
<export-stmt> ::= 'export' '{' <named-exports> '}' // named exports
| 'export' 'default' <expr> // default export
| 'export' 'fun' IDENT '(' <paramlist> ')' <block> // function export
<named-exports> ::= <export-specifier> ( ',' <export-specifier> )*
<export-specifier> ::= IDENT [ 'as' IDENT ]
<array-destructuring> ::= '[' <target-list> ']' '=' <expr>
<target-list> ::= IDENT ( ',' IDENT )*
| IDENT ( ',' IDENT )* ',' '...' IDENT // with rest element
<block> ::= '{' <stmts> '}'
| <stmt>
<expr> ::= <assignment>
| <ternary>
<assignment> ::= <postfix> <assign-op> <expr>
<assign-op> ::= '='
| '+='
| '-='
| '*='
| '/='
| '%='
<ternary> ::= <logical-or> [ '?' <expr> ':' <expr> ]
<logical-or> ::= <logical-and> ( '||' <logical-and> )*
<logical-and> ::= <bitwise-or> ( '&&' <bitwise-or> )*
<bitwise-or> ::= <bitwise-xor> ( '|' <bitwise-xor> )*
<bitwise-xor> ::= <bitwise-and> ( '^' <bitwise-and> )*
<bitwise-and> ::= <equality> ( '&' <equality> )*
<equality> ::= <relational> ( ('==' | '!=') <relational> )*
<relational> ::= <shift> ( ('<' | '>' | '<=' | '>=') <shift> )*
<shift> ::= <add> ( ('<<'|'>>') <add> )*
<add> ::= <mul> ( ('+'|'-') <mul> )*
<mul> ::= <unary> ( ('*'|'/'|'%') <unary> )*
<unary> ::= ('+' | '-' | '!' | 'typeof') <unary>
| <power>
<power> ::= <postfix> ( '^' <power> )?
<postfix> ::= <primary> { <postfix-op> }*
<postfix-op> ::= '.' IDENT
| '[' <expr> ']'
| '(' <arglist> ')'
<primary> ::= NUMBER
| STRING
| 'true'
| 'false'
| 'null'
| IDENT
| '[' <elemlist> ']'
| '{' <obj-items> '}'
| 'fun' '(' <paramlist> ')' <block>
| '(' <expr> ')'
| '@' '(' VBA_EXPR ')'
| '`' <template-parts>
| 'new' IDENT '(' <arglist> ')'
| 'this'
| 'super'
<arglist> ::= [ <arg-item> ( ',' <arg-item> )* ]
<arg-item> ::= '...' <expr> // spread operator
| <expr>
<elemlist> ::= [ <elem-item> ( ',' <elem-item> )* ]
<elem-item> ::= '...' <expr> // spread operator
| <expr>
<obj-items> ::= [ (<IDENT | STRING> ':' <expr>)
(',' (<IDENT|STRING> ':' <expr>))* ]
<paramlist> ::= [ <param-item> ( ',' <param-item> )* ]
<param-item> ::= IDENT
| '...' IDENT // rest parameter
<string-escape> ::= '\\'
| '\''
| '\"'
| '\n'
| '\t'
| '\r'
<comment> ::= '//' ( any-char-except-newline )* newline
| '/*' ( any-char )* '*/'
<template-parts> ::= ( <template-char>* | '${' <expr> '}' )*
<template-char> ::= any character except '`' or '$'
| '$' not-followed-by '{'
IDENT ::= letter followed by letters/digits/underscore
NUMBER ::= decimal or float
STRING ::= '...' or "..."
VBA_EXPR ::= any raw text until matching ')'
Notes:
- The parser also accepts top-level function declarations using the same
funsyntax with a name:fun name(params) { body }— these are converted to program-level function definitions and stored in the global program table. - Prototype methods:
prototype.COM.ObjectType methodName(params) { body }declarations are compiled into internal functions and registered for COM object method dispatch (v3.1.2+). - Collapsed identifiers like
o.a[2].bare parsed into nested AST nodes (Variable/Member/Index) by the compiler helperParseCollapsedIdentToNode. - Module imports/exports:
importandexportstatements enable code organization across multiple.vasfiles with caching and circular-dependency detection. - Spread/rest operators: The
...operator expands arrays in literals/calls and collects remaining elements in destructuring/parameters.
Operator precedence & associativity
From highest precedence to lowest:
- Parentheses
()(grouping) - Postfix: calls
(), indexes[], member access.prop(left-to-right chaining) - Exponentiation
^(right-associative) - Unary
+ - ! - Multiplicative
* / % - Additive
+ - - Shifts
<< >> - Relational
< <= > >= - Equality
== != - Bitwise/logic levels (
|, ^, &, ...) - Logical AND
&& - Logical OR
|| - Ternary
?:
Literals
- Number:
123,3.14 - String:
'hello'or"hello" - Boolean:
true,false - Null:
null - Array:
[1, 2, [3], 'x'] - Object:
{ k: 1, s: 'x' } - VBExpression:
@({1;0;4})— raw block passed to VBA-expressions evaluator
Constructors
- Array:
[] - String:
'' - Regular Expressions:
regex(<params>?)
Module system
ASF v3.0.0 introduces a full ECMAScript-style module system using import and export statements.
File extension
.vas— VBA Advanced Scripting source files- Module paths use
.vasextension (auto-appended if omitted) - Relative paths (
./,../) resolve against current working directory
Imports
Named imports
import { add, multiply, PI } from './math.vas';
Default import
import Calculator from './calculator.vas';
Namespace import
import * as utils from './utils.vas';
name = utils.formatName('John', 'Doe');
Mixed import
import mainFunc, { helper, VERSION } from './lib.vas';
Import with aliases
import { add as sum, multiply as times } from './math.vas';
Exports
Named exports
fun add(a, b) { return a + b; };
PI = 3.14159;
export { add, PI };
Default export
export default Calculator;
Function export
export fun processData(data) {
return data.map(fun(x) { return x * 2; });
};
Export with aliases
export { localName as publicName };
Module features
- Caching: Modules execute once; subsequent imports return cached exports
- Circular dependency detection: Runtime error if module re-enters during load
- Path resolution: Relative paths (
./,../) resolved againstcwd()
Working directory builtins
cwd()
Returns the current working directory as a string.
currentPath = cwd();
scwd(path)
Sets the current working directory. Affects relative module path resolution.
scwd(wd); // Set working directory
import { add } from './math.vas'; // Resolved relative to cwd()
VBA usage pattern:
Dim eng As New ASF
eng.InjectVariable "wd", ThisWorkbook.Path
result = eng.Execute(ThisWorkbook.Path & "\main.vas")
Functions & closures
fun(x,y) { return x+y }produces a closure value.- Top-level functions:
fun add(a,b) { return a + b }— compiled into the global program table and callable by name. - Closures implement shared-write semantics: they capture the runtime scope by reference. Mutations to outer-scope variables are visible across all closures that share that scope.
Rest parameters
Functions can collect remaining arguments into an array:
fun sum(first, ...rest) {
total = first;
rest.forEach(fun(n) { total = total + n; });
return total;
};
sum(1, 2, 3, 4); // => 10
Call semantics
CallClosure(closureMap, argsCollection, thisVal):- The runtime binds parameters in a new scope linked to the closure’s environment.
- Callback functions receive
(value, index, array)when called by array-methods. thisis supported when calling closures via bound method objects or when athisArgis supplied.
Spread/rest operators
Spread in arrays
arr1 = [1, 2, 3];
arr2 = [0, ...arr1, 4, 5]; // => [0, 1, 2, 3, 4, 5]
Spread in function calls
fun add(a, b, c) { return a + b + c; };
numbers = [1, 2, 3];
result = add(...numbers); // => 6
Spread strings
chars = [...'hello']; // => ['h', 'e', 'l', 'l', 'o']
Rest in destructuring
[first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]
Array destructuring
// Basic destructuring
[a, b, c] = [1, 2, 3]; // a=1, b=2, c=3
// With rest element
[head, ...tail] = myArray;
// Variable swapping
[a, b] = [b, a];
// Fewer targets than elements
[x, y] = [10, 20, 30, 40]; // x=10, y=20
// More targets than elements
[p, q, r] = [100, 200]; // p=100, q=200, r=Empty
Postfix chaining (member/call/index)
- Postfix chaining is supported for arbitrary primaries. Examples:
a.b.c(d)[i].x()- Special-case:
.lengthon an index or array is compiled into.__len__builtin call at compile-time.
Statement rules & semicolons
;separates statements. The parser enforces semicolons more strictly to disambiguate nested constructs (recommended: terminate statements with;when inline or in compact code).- Inside
{ ... }semicolons are not required strictly before}but are required between adjacent statements when ambiguous.
AST node types (high level)
ASF uses Map-based AST nodes internally. Common node type values include:
Literal— { type: “Literal”, value: … }Variable— { type: “Variable”, name: “x” }Member— { type: “Member”, base:, prop: "x" } Index— { type: “Index”, base:, index: } Call— { type: “Call”, callee:, args: [ , ...] } FuncLiteral— { type: “FuncLiteral”, params: […], body:} Array— { type: “Array”, items: [, ...] } Object— { type: “Object”, items: [ (key,node), … ] }VBAexpr— { type: “VBAexpr”, expr: “…” }BuiltinMethod— runtime-built map representing a bound method (whena.mapis evaluated andais an array)Import— { type: “Import”, source: “…”, defaultImport?: “…”, namespaceImport?: “…”, namedImports?: […] }Export— { type: “Export”, isDefault: bool, expression?:, namedExports?: [...], declarationType?: "function", declarationName?: "..." } ArrayDestructuring— { type: “ArrayDestructuring”, targets: […], restTarget?: “…”, source:} PrototypeDeclaration— { type: “PrototypeDeclaration”, objectType: “…”, methodName: “…”, params: […], body:}
Example snippets
// arithmetic + precedence
return(1 + 2 * 3);
// function + closures (shared-write)
a = 1;
f = fun() { a = a + 1; return a; };
print(f()); // PRINT:2
print(a); // PRINT:2
// arrays / map
a = [1, [2,3]];
b = a.map(fun(x) { if (IsArray(x)) { return x } else { return x * 2 } });
print(b);
// object literal & member call
o = { v: 10, incr: fun(x) { return x + 1 } };
print(o.incr(o.v)); // PRINT:11
// VBExpr embedding (evaluated by VBAexpressions)
a = @({1;0;4});
print(a);
// spread/rest operators
arr1 = [1, 2];
arr2 = [3, 4];
combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
fun sum(...numbers) {
return numbers.reduce(fun(acc, x) { return acc + x; }, 0);
};
print(sum(1, 2, 3, 4, 5)); // PRINT:15
// array destructuring
[a, b] = [10, 20];
[first, ...rest] = [1, 2, 3, 4];
// module system
scwd(wd);
import { add, multiply } from './math.vas';
import * as utils from './utils.vas';
result = add(5, 3);
name = utils.formatName('John', 'Doe');
// COM object prototype extension (v3.1.2+)
prototype.COM.Range formatCurrency() {
this.NumberFormat = "$#,##0.00";
this.Font.Bold = true;
return this; // Enable method chaining
};
prototype.COM.ListRow asDictionary() {
let headers = this.parent.listcolumns;
let values = this.range.value2;
let result = {};
for (let i = 1, i <= headers.count, i+=1) {
result.set(headers.item(i).name, values[1][i]);
}
return result;
};
// Usage of prototype methods
$1.Range('A1:A10').formatCurrency();
let rowData = $1.ListObjects('Table1').ListRows(1).asDictionary();
Notes & hints
- null is a valid literal returned by expressions and used to represent absence of value.
- Arrays in ASF are implemented as Variant arrays and honor __option_base (runtime option that sets the base index).
- The compiler will attempt to expand collapsed identifiers like a.b[3].c into nested AST nodes so the VM can handle LValue semantics correctly.
- Module caching: Each
.vasfile executes once per session;ClearModuleCache()resets the cache. - Working directory: Use
scwd(path)to set the base directory for relative imports before loading modules. - Prototype methods: COM object prototype extension requires
AppAccess = TrueandOverrideCollMethods = Truefor collection integration (v3.1.2+).
References
See TestRunner.bas for a comprehensive test-driven specification (373+ tests that exercise syntax and runtime behavior).