ASF — Syntax Reference

GitHub release (latest by date) Tests

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/export statements for organizing code across .vas files
  • Working directory builtins: cwd() and scwd(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 fun syntax 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].b are parsed into nested AST nodes (Variable/Member/Index) by the compiler helper ParseCollapsedIdentToNode.
  • Module imports/exports: import and export statements enable code organization across multiple .vas files 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:

  1. Parentheses () (grouping)
  2. Postfix: calls (), indexes [], member access .prop (left-to-right chaining)
  3. Exponentiation ^ (right-associative)
  4. Unary + - !
  5. Multiplicative * / %
  6. Additive + -
  7. Shifts << >>
  8. Relational < <= > >=
  9. Equality == !=
  10. Bitwise/logic levels (|, ^, &, ...)
  11. Logical AND &&
  12. Logical OR ||
  13. 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 .vas extension (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 against cwd()

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.
    • this is supported when calling closures via bound method objects or when a thisArg is 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: .length on 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 (when a.map is evaluated and a is 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 .vas file 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 = True and OverrideCollMethods = True for collection integration (v3.1.2+).

References

See TestRunner.bas for a comprehensive test-driven specification (373+ tests that exercise syntax and runtime behavior).