| Version 1.0.1 | Complete Language Reference |
Table of Contents
- Introduction
- Getting Started
- Basic Syntax
- Data Types
- Variables
- Operators
- Control Flow
- Functions
- Arrays
- Objects
- Object Member Methods
- Classes
- Module System
- Built-in Functions
- String Methods
- Array Methods
- Template Literals
- Regular Expressions
- Error Handling
- VBA Integration
- Office Application Integration
- COM Object Prototype Extension
- Best Practices
Introduction
ASF (Advanced Scripting Framework) is a JavaScript-like scripting language implemented in VBA (Visual Basic for Applications). It provides modern programming features within Excel, Access, and other Office applications.
Key Features
- JavaScript-like syntax - Familiar syntax for web developers
- Object-oriented programming - Classes with inheritance
- Functional programming - First-class functions and closures
- Modern array methods - map, filter, reduce, and more
- Template literals - String interpolation with backticks
- Regular expressions - Pattern matching support
- COM object extension - Monkey patching for Office objects (v3.1.2+)
- VBA integration - Seamless integration with VBA code
Why ASF?
- Write more expressive code in Office applications
- Leverage JavaScript patterns in VBA environment
- Build complex logic with modern language features
- Share code logic between web and Office platforms
Getting Started
Installation
- Import the ASF class modules into your VBA project:
ASF.clsASF_Compiler.clsASF_Globals.clsASF_Map.clsASF_Parser.clsASF_ScopeStack.clsASF_VM.clsASF_RegexEngine.clsUDFunctions.clsVBAcallBack.clsVBAexpressions.clsVBAexpressionsScope.cls
Hello World
Sub HelloWorld()
Dim engine As New ASF
Dim code As String
code = "print('Hello, World!');"
Dim idx As Long
idx = engine.Compile(code)
engine.Run idx
End Sub
Basic Usage Pattern
Sub RunASFCode()
' Create engine instance
Dim engine As New ASF
' Write ASF code
Dim code As String
code = "let x = 10; print(x * 2);"
' Compile and run
Dim result As Variant
result = engine.Run(engine.compile(code))
End Sub
Basic Syntax
// Single-line comment
/* Multi-line
comment */
# Python-style comment (also supported)
Statements
Statements are terminated by semicolons (;):
Semicolons are optional at the end of the script but required at end of statements blocks:
if (x > 5) {
print('Greater');
}; // Semicolon mandatory here
let y = 20; // Semicolon optional here
Case Sensitivity
ASF is case-sensitive:
let myVar = 10;
let MyVar = 20; // Different variable
Whitespace
Whitespace is generally ignored:
let x=10; // Valid
let y = 20; // More readable
Data Types
Primitive Types
Number
All numbers are floating-point:
let integer = 42;
let decimal = 3.14159;
let negative = -100;
let scientific = 1.5e10;
String
Strings are enclosed in single quotes:
let name = 'John Doe';
let message = 'Hello, World!';
let empty = '';
Template literals and regex patterns use backticks:
let name = 'Alice';
let greeting = `Hello, ${name}!`; // "Hello, Alice!"
let arr = 'test1test2'.match(`/t(e)(st(\d?))/g`)
Boolean
let isTrue = true;
let isFalse = false;
Null
Represents intentional absence of value:
Composite Types
Array
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, 'two', true, null];
let nested = [[1, 2], [3, 4]];
let empty = [];
Object
let person = {
name: 'John',
age: 30,
email: 'john@example.com'
};
let nested = {
user: {
name: 'Alice',
address: {
city: 'Boston'
}
}
};
Type Checking
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof null; // 'null'
typeof []; // 'array'
typeof {}; // 'object'
typeof fun() {}; // 'function'
// Built-in functions
isArray([1, 2, 3]); // true
isNumeric(42); // true
isNumeric('42'); // true
isNumeric('hello'); // false
Variables
Declaration
Variables do not need to be declared; in any case, the interpreter supports the use of the let keyword to assign variables:
x = 10;
let y = 20; // Also valid (converted to simple assignment)
Scope
Functions shares scope variables, outer variables can be mutated:
let x = 10;
fun test() {
let x = 20; /* Same variable*/
print(x) /* Outputs: 20 */
};
test();
print(x); // Outputs: 20
Assignment
let x = 10;
x = 20; /* Reassignment also accepts let x = 20 (does not behave like JavaScript)*/
let arr = [1, 2, 3];
arr[0] = 10; /* Array element assignment */
let obj = { name: 'John' };
obj.name = 'Jane'; /* Property assignment */
Compound Assignment
let x = 10;
x += 5; // x = x + 5 (15)
x -= 3; // x = x - 3 (12)
x *= 2; // x = x * 2 (24)
x /= 4; // x = x / 4 (6)
x %= 4; // x = x % 4 (2)
x ^= 3; // x = x ^ 3 (8)
x &= 7; // x = x & 7 (string concat)
x |= 2; // x = x | 2 (bitwise OR)
Operators
Arithmetic Operators
let a = 10, b = 3;
a + b; // 13 (addition)
a - b; // 7 (subtraction)
a * b; // 30 (multiplication)
a / b; // 3.333... (division)
a % b; // 1 (modulus/remainder)
a ^ b; // 1000 (exponentiation)
Comparison Operators
let x = 10, y = 20;
x == y; // false (equal)
x != y; // true (not equal)
x < y; // true (less than)
x > y; // false (greater than)
x <= y; // true (less than or equal)
x >= y; // false (greater than or equal)
Logical Operators
let a = true, b = false;
a && b; // false (AND)
a || b; // true (OR)
!a; // false (NOT)
String Concatenation
'Hello' + ' ' + 'World'; // 'Hello World'
'Value: ' + 42; // 'Value: 42'
'Count' & ': ' & 10; // 'Count: 10' (alternative)
Bitwise Operators
let x = 5; // Binary: 101
let y = 3; // Binary: 011
x << 1; // 10 (left shift)
x >> 1; // 2 (right shift)
Compound bitwise assignment:
x <<= 2; // x = x << 2
x >>= 1; // x = x >> 1
Ternary Operator
let age = 18;
let status = (age >= 18) ? 'adult' : 'minor';
print(status); // 'adult'
// Nested ternary
let score = 85;
let grade = (score >= 90) ? 'A' :
(score >= 80) ? 'B' :
(score >= 70) ? 'C' : 'F';
Spread/rest Operator
//rest argument
fun greetAll(greeting, ...names) {
result = greeting + ': ';
for (name of names) {
result = result + name + ', ';
};
return result.slice(0, -2);
};
msg = greetAll('Hello', 'Alice', 'Bob', 'Charlie');
return msg //=> Hello: Alice, Bob, Charlie
//spread operator on arrays
begin = [1]; middle = [2, 3, 4]; end = [5]; combined = [...begin, ...middle, ...end];
print(combined); //=> [ 1, 2, 3, 4, 5 ]
//spread operator on objects
obj1 = {a: 1, b: 2}; obj2 = {c: 3, ...obj1, d: 4};
return `${obj2.a}; ${obj2.b}; ${obj2.c}; ${obj2.d}` //=> 1; 2; 3; 4
Operator Precedence
From highest to lowest:
- Parentheses
( ) - Unary
!,-,typeof,... - Exponentiation
^ - Multiplication/Division
*,/,% - Addition/Subtraction
+,- - Bitwise Shift
<<,>> - Comparison
<,>,<=,>= - Equality
==,!= - Logical AND
&& - Logical OR
|| - Ternary
? : - Assignment
=,+=,-=, etc.
Control Flow
If Statement
let x = 10;
if (x > 5) {
print('Greater than 5');
};
if (x > 15) {
print('Greater than 15');
} else {
print('Not greater than 15');
};
// Multiple conditions
if (x > 20) {
print('Greater than 20');
} elseif (x > 10) {
print('Greater than 10');
} elseif (x > 5) {
print('Greater than 5');
} else {
print('5 or less');
};
Switch Statement
let day = 3;
switch (day) {
case 1 {
print('Monday');
}
case 2 {
print('Tuesday');
}
case 3 {
print('Wednesday');
}
default {
print('Other day');
};
};
Note: ASF switch statements don’t fall through - no break needed.
For Loop
Classic For Loop
// Standard C-style for loop
for (let i = 0, i < 5, i += 1) {
print(i);
};
For-In Loop (Iterate Indices/Keys)
// Array indices
let arr = [10, 20, 30];
for (let i in arr) {
print(i); /* 1, 2, 3 (indices, 1-based) */
};
// Object keys
let obj = { name: 'John', age: 30 };
for (let key in obj) {
print(key); /* 'name', 'age' */
};
// String indices
let str = 'ABC';
for (let i in str) {
print(i); // 1, 2, 3
};
For-Of Loop (Iterate Values)
// Array values
let arr = [10, 20, 30]
for (let val of arr) {
print(val); /* 10, 20, 30 */
};
// String characters
let str = 'ABC';
for (let char of str) {
print(char); /* 'A', 'B', 'C' */
};
// Object values
let obj = { name: 'John', age: 30 };
for (let val of obj) {
print(val); /* 'John', 30 */
};
While Loop
let i = 0;
while (i < 5) {
print(i);
i = i + 1;
}
// Infinite loop with break
let count = 0;
while (true) {
if (count >= 10) {
break;
};
count = count + 1;
}; print(count); //-->10
Break and Continue
// Break: exit loop
for (let i = 0, i < 10, i += 1) {
if (i == 5) {
break; // Exit loop when i is 5
};
print(i);
};
// Continue: skip to next iteration
for (let i = 0, i < 10, i += 1) {
if (i % 2 == 0) {
continue; // Skip even numbers
};
print(i); // Prints odd numbers only
};
Functions
Function Declaration
fun greet(name) {
print('Hello, ' + name + '!');
};
greet('Alice'); // Hello, Alice!
Function with Return Value
fun add(a, b) {
return a + b;
};
let result = add(5, 3); // 8
Function Expressions
let square = fun(x) {
return x * x;
};
print(square(5)); // 25
Anonymous Functions
let numbers = [1, 2, 3, 4, 5];
// Anonymous function in map
let squared = numbers.map(fun(x) {
return x * x;
}); //--> [ 1, 4, 9, 16, 25 ]
Closures
Functions can capture variables from their enclosing scope:
fun makeCounter() {
let count = 0;
return fun() {
count = count + 1;
return count;
};
};
let counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 3
Higher-Order Functions
Functions that accept or return other functions:
fun operate(a, b, operation) {
return operation(a, b)
};
let add = fun(x, y) { return x + y };
let multiply = fun(x, y) { return x * y };
print(operate(5, 3, add)); // 8
print(operate(5, 3, multiply)); // 15
Recursion
fun factorial(n) {
if (n <= 1) {
return 1;
};
return n * factorial(n - 1);
};
print(factorial(5)); // 120
// Fibonacci
fun fib(n) {
if (n <= 1) {
return n;
};
return fib(n - 1) + fib(n - 2);
};
print(fib(10)); // 55
Default Parameters (Workaround)
fun greet(name) {
if (typeof name == 'undefined') {
name = 'Guest';
};
print('Hello, ' + name + '!');
};
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
Variable Arguments (Workaround)
// Using array as parameter
fun sum(numbers) {
let total = 0;
for (let i = 1, i <= numbers.length, i += 1) {
total += numbers[i];
};
return total;
};
print(sum([1, 2, 3, 4])); // 10
Arrays
Creating Arrays
let empty = [];
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, 'two', true, null, [5, 6]];
Array Indexing
Important: ASF uses 1-based indexing by default (EXPERIMENTAL: configurable with option base).
let arr = [10, 20, 30, 40];
// 1-based indexing (default)
print(arr[1]); // 10 (first element)
print(arr[4]); // 40 (last element)
// Assignment
arr[2] = 25;
print(arr[2]); // 25
Array Properties
let arr = [1, 2, 3, 4, 5];
print(arr.length); // 5
Nested Arrays
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
print(matrix[1][1]); // 1
print(matrix[2][3]); // 6
Array Utilities
// Check if array
isArray([1, 2, 3]); // true
isArray('hello'); // false
// Flatten nested arrays
let nested = [1, [2, 3], [4, [5, 6]]];
let flat = flatten(nested); // [1, 2, 3, 4, 5, 6]
// Flatten with depth limit
let partial = flatten(nested, 1); // [1, 2, 3, 4, [5, 6]]
// Clone array (deep copy)
let original = [1, 2, [3, 4]];
let copy = clone(original);
Array Methods
Transforming Methods
map
Transform each element:
let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(fun(x) {
return x * 2;
});
print(doubled); // [2, 4, 6, 8, 10]
// With index
let indexed = numbers.map(fun(val, idx, arr) {
return val + idx;
});
filter
Select elements that match a condition:
let numbers = [1, 2, 3, 4, 5, 6];
let evens = numbers.filter(fun(x) {
return x % 2 == 0;
});
print(evens); // [2, 4, 6]
reduce
Reduce array to single value:
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce(fun(acc, val) {
return acc + val;
}, 1);
print(sum); // 16
// Without initial value (uses first element)
let product = numbers.reduce(fun(acc, val) {
return acc * val;
});
print(product); // 120
Searching Methods
find
Find first matching element:
let users = [
{ name: 'John', age: 25 },
{ name: 'Jane', age: 30 },
{ name: 'Bob', age: 35 }
];
let user = users.find(fun(u) {
return u.age > 28;
});
print(user.name); // 'Jane'
findIndex
Find index of first matching element:
let numbers = [10, 20, 30, 40, 50];
let idx = numbers.findIndex(fun(x) {
return x > 25;
});
print(idx); // 3 (30 is at index 3, 1-based)
findLast / findLastIndex
Find from end of array:
let numbers = [10, 20, 30, 20, 10];
let last = numbers.findLast(fun(x) {
return x == 20;
});
print(last); // 20 (last occurrence)
indexOf / lastIndexOf
let arr = [1, 2, 3, 2, 1];
print(arr.indexOf(2)); // 2 (first occurrence)
print(arr.lastIndexOf(2)); // 4 (last occurrence)
print(arr.indexOf(5)); // -1 (not found)
includes
let fruits = ['apple', 'banana', 'orange'];
print(fruits.includes('banana')); // true
print(fruits.includes('grape')); // false
Mutating Methods
push
Add elements to end:
let arr = [1, 2, 3];
arr.push(4);
arr.push(5, 6);
print(arr); // [1, 2, 3, 4, 5, 6]
pop
Remove last element:
let arr = [1, 2, 3, 4];
let last = arr.pop();
print(last); // 4
print(arr); // [1, 2, 3]
shift
Remove first element:
let arr = [1, 2, 3, 4];
let first = arr.shift();
print(first); // 1
print(arr); // [2, 3, 4]
unshift
Add elements to beginning:
let arr = [3, 4];
arr.unshift(1, 2);
print(arr); // [1, 2, 3, 4]
splice
Remove/insert elements:
let arr = [1, 2, 3, 4, 5];
// Remove 2 elements starting at index 2
let removed = arr.splice(2, 2);
print(removed); // [2, 3]
print(arr); // [1, 4, 5]
// Insert elements
arr = [1, 2, 5];
arr.splice(3, 0, 3, 4); // At index 3, remove 0, insert 3, 4
print(arr); // [1, 2, 3, 4, 5]
// Replace elements
arr = [1, 2, 3, 4, 5];
arr.splice(2, 2, 99); // Remove 2 elements, insert 99
print(arr); // [1, 99, 5]
reverse
Reverse array in place:
let arr = [1, 2, 3, 4, 5];
arr.reverse();
print(arr); // [5, 4, 3, 2, 1]
sort
Sort array in place:
let numbers = [3, 1, 4, 1, 5, 9];
numbers.sort();
print(numbers); // [1, 1, 3, 4, 5, 9]
// Custom comparator
let words = ['banana', 'apple', 'cherry'];
words.sort(fun(a, b) {
if (a < b) { return -1;};
if (a > b) { return 1;};
return 0;
});
print(words); // ['apple', 'banana', 'cherry']
Non-Mutating Methods
slice
Extract portion of array:
let arr = [1, 2, 3, 4, 5];
let sub = arr.slice(2, 4); // From index 2 to 4 (exclusive)
print(sub); // [2, 3]
// Negative indices (from end)
let last2 = arr.slice(-2);
print(last2); // [4, 5]
concat
Combine arrays:
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = arr1.concat(arr2, [5, 6]);
print(combined); // [1, 2, 3, 4, 5, 6]
toSorted / toReversed / toSpliced
Non-mutating versions (return new array):
let arr = [3, 1, 4, 1, 5];
let sorted = arr.toSorted();
print(sorted); // [1, 1, 3, 4, 5]
print(arr); // [3, 1, 4, 1, 5] (unchanged)
let reversed = arr.toReversed();
print(reversed); // [5, 1, 4, 1, 3]
print(arr); // [3, 1, 4, 1, 5] (unchanged)
with
Create copy with one element changed:
let arr = [1, 2, 3, 4];
let modified = arr.with(2, 99); // Change index 2 to 99
print(modified); // [1, 99, 3, 4]
print(arr); // [1, 2, 3, 4] (unchanged)
Iteration Methods
forEach
Execute function for each element:
let numbers = [1, 2, 3, 4, 5];
numbers.forEach(fun(val, idx) {
print('Index ' + idx + ': ' + val);
});
every
Test if all elements match condition:
let numbers = [2, 4, 6, 8];
let allEven = numbers.every(fun(x) {
return x % 2 == 0;
});
print(allEven); // true
some
Test if any element matches condition:
let numbers = [1, 3, 5, 8];
let hasEven = numbers.some(fun(x) {
return x % 2 == 0;
});
print(hasEven); // true
Utility Methods
unique
Remove duplicates:
let arr = [1, 2, 2, 3, 3, 3, 4];
let unique = arr.unique();
print(unique); // [1, 2, 3, 4]
at
Access with negative indices:
let arr = [1, 2, 3, 4, 5];
print(arr.at(1)); // 1 (first element)
print(arr.at(-1)); // 5 (last element)
print(arr.at(-2)); // 4 (second to last)
entries
Get [index, value] pairs:
let arr = ['a', 'b', 'c'];
let pairs = arr.entries();
// [[1, 'a'], [2, 'b'], [3, 'c']]
join / toString
Convert to string:
let arr = [1, 2, 3];
print(arr.join()); // '1, 2, 3'
print(arr.join('-')); // '1-2-3'
print(arr.toString()); // '1, 2, 3'
from
Create array from iterable:
let str = 'hello';
let chars = [].from(str);
print(chars); // ['h', 'e', 'l', 'l', 'o']
// With mapping function
let doubled = [].from([1, 2, 3], fun(x) {
return x * 2;
});
print(doubled); // [2, 4, 6]
of
Create array from arguments:
let arr = [].of(1, 2, 3, 4);
print(arr); // [1, 2, 3, 4]
copyWithin
Copy elements within array:
let arr = [1, 2, 3, 4, 5];
arr.copyWithin(1, 3); // Copy from index 3 to index 1
print(arr); // [1, 3, 4, 4, 5]
delete
Remove element at index:
let arr = [1, 2, 3, 4, 5];
arr.delete(3); // Remove element at index 3
print(arr); // [1, 2, 4, 5]
Objects
Creating Objects
let person = {
name: 'John Doe',
age: 30,
email: 'john@example.com'
};
Accessing Properties
// Dot notation
print(person.name); // 'John Doe'
// Bracket notation
print(person['age']); // 30
// Dynamic property access
let prop = 'email';
print(person[prop]); // 'john@example.com'
Setting Properties
person.age = 31;
person['city'] = 'Boston';
person.country = 'USA'; // Add new property
Nested Objects
let company = {
name: 'Tech Corp',
address: {
street: '123 Main St',
city: 'Boston',
zip: '02101'
},
employees: [
{ name: 'Alice', role: 'Developer' },
{ name: 'Bob', role: 'Designer' }
]
};
print(company.address.city); // 'Boston'
print(company.employees[1].name); // 'Alice' (1-based)
Methods in Objects
let calculator = {
add: fun(a, b) {
return a + b;
},
multiply: fun(a, b) {
return a * b;
}
};
print(calculator.add(5, 3)); // 8
print(calculator.multiply(4, 7)); // 28
Dynamic Property Names
let key = 'status';
let obj = {};
obj[key] = 'active';
print(obj.status); // 'active'
Object Member Methods
Property Enumeration
keys
Get array of all property names:
let person = { name: 'John', age: 30, city: 'Boston' };
let props = person.keys();
print(props); // ['name', 'age', 'city']
// Iterate over keys
person.keys().forEach(fun(key) {
print(key + ': ' + person[key]);
});
values
Get array of all property values:
let scores = { math: 85, english: 92, science: 78 };
let vals = scores.values();
print(vals); // [85, 92, 78]
// Calculate total
let total = scores.values().reduce(fun(sum, val) {
return sum + val;
}, 0);
print(total); // 255
entries
Get array of [key, value] pairs:
let config = { host: 'localhost', port: 8080, ssl: true };
let pairs = config.entries();
print(pairs); // [['host', 'localhost'], ['port', 8080], ['ssl', true]]
// Convert to different format
config.entries().forEach(fun(pair) {
print(pair[1] + '=' + pair[2]);
});
// Output:
// host=localhost
// port=8080
// ssl=true
Property Management
size / length
Get number of properties:
let obj = { a: 1, b: 2, c: 3 };
print(obj.size()); // 3
print(obj.length()); // 3 (alias)
let empty = {};
print(empty.size()); // 0
hasKey / has
Check if property exists:
let user = { name: 'Alice', email: 'alice@example.com' };
print(user.hasKey('name')); // true
print(user.has('email')); // true (alias)
print(user.hasKey('phone')); // false
// Safe property access
if (user.has('address')) {
print(user.address.city);
} else {
print('No address available');
};
isEmpty
Check if object has no properties:
let obj1 = {};
print(obj1.isEmpty()); // true
let obj2 = { x: 1 };
print(obj2.isEmpty()); // false
// Clear and check
obj2.clear();
print(obj2.isEmpty()); // true
get
Get property value with optional default:
let config = { timeout: 30, retry: 3 };
// Get existing property
print(config.get('timeout')); // 30
// Get with default for missing property
print(config.get('maxSize', 100)); // 100 (returns default)
// Without default returns empty
print(config.get('missing')); // '' (empty)
// Use in conditionals
let port = config.get('port', 8080);
print('Port: ' + port); // Port: 8080
set
Set property value:
let user = { name: 'John' };
// Add new property
user.set('age', 30);
print(user.age); // 30
// Update existing property
user.set('name', 'Jane');
print(user.name); // Jane
// Chain multiple sets
user.set('city', 'Boston').set('country', 'USA');
// Set with dynamic key
let key = 'status';
user.set(key, 'active');
print(user.status); // active
delete / remove
Remove property:
let person = { name: 'Alice', age: 25, temp: 'delete-me' };
// Delete property
person.delete('temp');
print(person.hasKey('temp')); // false
// Using alias
person.remove('age');
print(person); // { name: 'Alice' }
// Delete non-existent property (safe)
person.delete('notThere'); // No error
clear
Remove all properties:
let data = { a: 1, b: 2, c: 3, d: 4 };
print(data.size()); // 4
data.clear();
print(data.size()); // 0
print(data.isEmpty()); // true
// Object is now empty but still usable
data.set('x', 10);
print(data.x); // 10
Object Transformation
clone
Create deep copy of object:
let original = {
name: 'John',
scores: [85, 90, 78],
address: {
city: 'Boston',
zip: '02101'
}
};
let copy = original.clone();
// Modify copy
copy.name = 'Jane';
copy.scores[1] = 95;
copy.address.city = 'New York';
// Original unchanged
print(original.name); // John
print(original.scores[1]); // 85 (1-based indexing)
print(original.address.city); // Boston
// Copy modified
print(copy.name); // Jane
print(copy.scores[1]); // 95
print(copy.address.city); // New York
merge
Merge another object’s properties:
let defaults = {
timeout: 30,
retry: 3,
verbose: false
};
let userConfig = {
timeout: 60,
cache: true
};
// Merge userConfig into defaults (mutates defaults)
defaults.merge(userConfig);
print(defaults);
// {
// timeout: 60, // Overwritten
// retry: 3, // Preserved
// verbose: false, // Preserved
// cache: true // Added
// }
// Nested merge (overwrites nested objects)
let obj1 = { a: 1, nested: { x: 10, y: 20 } };
let obj2 = { b: 2, nested: { y: 30, z: 40 } };
obj1.merge(obj2);
print(obj1);
// {
// a: 1,
// b: 2,
// nested: { y: 30, z: 40 } // obj2.nested replaces obj1.nested
// }
// Safe merge pattern (preserve original)
let merged = original.clone().merge(updates);
Iteration Methods
forEach
Execute function for each property:
let scores = { math: 85, english: 92, science: 78 };
// With value only
scores.forEach(fun(val) {
print(val);
});
// Output: 85, 92, 78
// With value and key
scores.forEach(fun(val, key) {
print(key + ': ' + val);
});
// Output:
// math: 85
// english: 92
// science: 78
// Modify during iteration (affects original)
let data = { a: 1, b: 2, c: 3 };
data.forEach(fun(val, key) {
data[key] = val * 2;
});
print(data); // { a: 2, b: 4, c: 6 }
// Count values matching condition
let count = 0;
scores.forEach(fun(val) {
if (val > 80) {
count = count + 1;
}
});
print(count); // 2
map
Transform all values, return new object:
let prices = { apple: 1.50, banana: 0.75, orange: 2.00 };
// Double all prices
let doubled = prices.map(fun(val) {
return val * 2;
});
print(doubled); // { apple: 3, banana: 1.5, orange: 4 }
// With key parameter
let formatted = prices.map(fun(val, key) {
return key + ': $' + val;
});
print(formatted);
// { apple: 'apple: $1.5', banana: 'banana: $0.75', orange: 'orange: $2' }
// Transform nested objects
let users = {
user1: { name: 'John', age: 30 },
user2: { name: 'Jane', age: 25 }
};
let names = users.map(fun(user) {
return user.name;
});
print(names); // { user1: 'John', user2: 'Jane' }
// Original unchanged (non-mutating)
print(prices.apple); // 1.5
filter
Filter properties by condition, return new object:
let scores = { math: 85, english: 92, science: 78, history: 95 };
// Filter passing grades (>= 90)
let passing = scores.filter(fun(val) {
return val >= 90;
});
print(passing); // { english: 92, history: 95 }
// Filter by key
let user = {
name: 'John',
_id: 12345,
email: 'john@example.com',
_internal: true
};
let publicFields = user.filter(fun(val, key) {
return !key.startsWith('_');
});
print(publicFields); // { name: 'John', email: 'john@example.com' }
// Complex filtering
let products = {
item1: { name: 'Widget', price: 10, inStock: true },
item2: { name: 'Gadget', price: 25, inStock: false },
item3: { name: 'Tool', price: 15, inStock: true }
};
let available = products.filter(fun(item) {
return item.inStock && item.price < 20;
});
print(available); // { item1: {...}, item3: {...} }
some
Test if any property matches condition:
let scores = { math: 85, english: 92, science: 78 };
// Any score above 90?
let hasExcellent = scores.some(fun(val) {
return val > 90;
});
print(hasExcellent); // true
// Any failing grade?
let hasFailing = scores.some(fun(val) {
return val < 60;
});
print(hasFailing); // false
// With key
let config = {
debugMode: false,
logging: true,
verbose: false
};
let anyEnabled = config.some(fun(val, key) {
return val == true && key.startsWith('log');
});
print(anyEnabled); // true
// Validation example
let user = { name: '', email: 'valid@example.com' };
let hasEmptyField = user.some(fun(val) {
return val == '';
});
print(hasEmptyField); // true
every
Test if all properties match condition:
let scores = { math: 85, english: 92, science: 88 };
// All passing (>= 60)?
let allPassing = scores.every(fun(val) {
return val >= 60;
});
print(allPassing); // true
// All excellent (>= 90)?
let allExcellent = scores.every(fun(val) {
return val >= 90;
});
print(allExcellent); // false
// Validation example
let requiredFields = { name: 'John', email: 'john@example.com', age: 30 };
let allPresent = requiredFields.every(fun(val) {
return val != null && val != '';
});
print(allPresent); // true
// Type checking
let numbers = { a: 1, b: 2, c: 3 };
let allNumeric = numbers.every(fun(val) {
return typeof val == 'number';
});
print(allNumeric); // true
Method Chaining
Combine object methods for complex transformations:
let inventory = {
apple: { price: 1.50, quantity: 10 },
banana: { price: 0.75, quantity: 5 },
orange: { price: 2.00, quantity: 0 },
grape: { price: 3.50, quantity: 8 }
};
// Filter in-stock items, then get total value
let totalValue = inventory
.filter(fun(item) {
return item.quantity > 0;
})
.map(fun(item) {
return item.price * item.quantity;
})
.values()
.reduce(fun(sum, val) {
return sum + val;
}, 0);
print(totalValue); // 43.75
// Transform and validate
let users = {
user1: { name: 'John', age: 30, active: true },
user2: { name: 'Jane', age: 25, active: false },
user3: { name: 'Bob', age: 35, active: true }
};
let activeUserNames = users
.filter(fun(u) { return u.active; })
.map(fun(u) { return u.name; })
.values();
print(activeUserNames); // ['John', 'Bob']
Classes
Basic Class Definition
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
print('Hello, I am ' + this.name);
}
getAge() {
return this.age;
}
}
let person = new Person('Alice', 25);
person.greet(); // Hello, I am Alice
print(person.getAge()); // 25
print(person.name); // Alice
Class Fields
Declare instance fields with the field keyword:
class Rectangle {
field width = 0, height = 0;
constructor(w, h) {
this.width = w;
this.height = h;
}
getArea() {
return this.width * this.height;
}
}
let rect = new Rectangle(10, 5);
print(rect.getArea()); // 50
Field Syntax
// Single field
field x;
field x = 10;
// Multiple fields (comma-separated)
field x, y, z;
field x = 1, y = 2, z = 3;
// Mixed with/without initializers
field x = 1, y, z = 3;
// Multiple declarations
field width = 0, height = 0;
field color = 'black';
field name;
Field Initialization Order
- Parent class fields (if inheritance)
- Current class fields
- Constructor body
class Point {
field x = 0, y = 0;
constructor(x, y) {
// Fields already initialized to 0
this.x = x; // Now set to parameter value
this.y = y;
}
}
Static Methods
Methods that belong to the class, not instances:
class MathHelper {
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
static PI = 3.14159; // Note: static fields via method
}
// Call without creating instance
print(MathHelper.add(5, 3)); // 8
print(MathHelper.multiply(4, 7)); // 28
Inheritance
Classes can extend other classes:
class Animal {
field name, species;
constructor(name, species) {
this.name = name;
this.species = species;
}
speak() {
print(this.name + ' makes a sound');
}
}
class Dog extends Animal {
field breed;
constructor(name, breed) {
super(name, 'Dog'); // Call parent constructor
this.breed = breed;
}
speak() {
print(this.name + ' barks!');
}
getBreed() {
return this.breed;
}
}
let dog = new Dog('Rex', 'Labrador');
dog.speak(); // Rex barks!
print(dog.getBreed()); // Labrador
print(dog.species); // Dog
Super Keyword
Call parent constructor:
class Shape {
field x, y;
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Circle extends Shape {
field radius;
constructor(x, y, r) {
super(x, y); // Must call parent constructor
this.radius = r;
}
getArea() {
return 3.14159 * this.radius * this.radius;
}
}
This Binding
The this keyword refers to the current instance:
class Counter {
field count = 0;
increment() {
this.count = this.count + 1;
}
getValue() {
return this.count;
}
}
let c = new Counter();
c.increment();
c.increment();
print(c.getValue()); // 2
Complete Class Example
class BankAccount {
field balance = 0;
field accountNumber;
field owner;
constructor(owner, accountNum) {
this.owner = owner;
this.accountNumber = accountNum;
}
deposit(amount) {
if (amount > 0) {
this.balance = this.balance + amount;
return true;
}
return false;
}
withdraw(amount) {
if (amount > 0 && amount <= this.balance) {
this.balance = this.balance - amount;
return true;
}
return false;
}
getBalance() {
return this.balance;
}
getInfo() {
return this.owner + ' (' + this.accountNumber + '): $' + this.balance;
}
static create(owner) {
let accNum = 'ACC' + Math.floor(Math.random() * 10000);
return new BankAccount(owner, accNum);
}
}
let account = new BankAccount('John Doe', 'ACC12345');
account.deposit(1000);
account.withdraw(250);
print(account.getInfo()); // John Doe (ACC12345): $750
Module System
ASF v3.0.0 introduces a full ECMAScript-style module system using import and export statements to organize code across multiple files.
File Extension
ASF source files use the .vas extension (VBA Advanced Scripting):
project/
├── main.vas
├── math.vas
├── utils.vas
└── lib.vas
Module paths automatically append .vas if omitted. Example: './math' resolves to './math.vas'.
Imports
Named Imports
Import specific exports by name:
import { add, multiply, PI } from './math.vas';
result = add(5, 3);
area = PI * multiply(5, 5);
Default Import
Import the default export:
import Calculator from './calculator.vas';
calc = Calculator();
sum = calc.add(10, 5);
Namespace Import
Import all exports as a namespace object:
import * as utils from './utils.vas';
name = utils.formatName('John', 'Doe');
upperName = utils.uppercase(name);
Mixed Import
Import both default and named exports:
import mainFunc, { helper, VERSION } from './lib.vas';
print(mainFunc()); // Uses default export
print(helper()); // Uses named export
print(VERSION); // Uses named export
Import with Aliases
Rename imports to avoid conflicts:
import { add as sum, multiply as times } from './math.vas';
result = sum(2, 3);
product = times(4, 5);
Exports
Named Exports
Export multiple values by name:
// math.vas
fun add(a, b) {
return a + b;
};
fun multiply(a, b) {
return a * b;
};
PI = 3.14159;
export { add, multiply, PI };
Default Export
Export a single default value:
// calculator.vas
fun Calculator() {
return {
add: fun(a, b) { return a + b; },
subtract: fun(a, b) { return a - b; }
};
};
export default Calculator;
Function Export
Export a function declaration:
export fun processData(data) {
return data.map(fun(x) { return x * 2; });
};
Export with Aliases
Rename exports:
fun internalName() {
return 'Internal implementation';
};
export { internalName as publicName };
Module Features
- Caching: Each module executes once; subsequent imports return cached exports
- Circular Dependency Detection: Runtime error if a module re-enters during loading
- Path Resolution: Relative paths (
./,../) resolve againstcwd() - Isolation: Each module has its own scope; only exported values are accessible
Working Directory
Use cwd() and scwd() to manage module resolution:
// Set working directory before imports
scwd(wd);
// Relative imports now resolve from wd
import { add } from './math.vas';
import { helper } from '../lib/utils.vas';
// Get current directory
currentDir = cwd();
VBA Usage
From VBA, use the Execute method to run .vas files:
Sub RunModule()
Dim eng As New ASF
' Set working directory
eng.InjectVariable "wd", ThisWorkbook.Path
' Execute module file
Dim result As Variant
result = eng.Execute(ThisWorkbook.Path & "\main.vas")
Debug.Print result
End Sub
Or manually compile and run:
Sub RunModuleManual()
Dim eng As New ASF
Dim code As String
' Set working directory
eng.WorkingDir = ThisWorkbook.Path
' Read and execute
code = eng.ReadTextFile(ThisWorkbook.Path & "\main.vas")
Dim idx As Long
idx = eng.Compile(code)
eng.Run idx
Debug.Print eng.OUTPUT_
End Sub
Module Cache Management
Clear the module cache to force re-execution:
' From VBA
eng.ClearModuleCache
' Modules will execute fresh on next import
Example Project Structure
project/
├── main.vas ' Entry point
├── math.vas ' Math utilities
├── utils.vas ' String utilities
├── lib.vas ' Shared library
└── calculator.vas ' Calculator class
main.vas:
scwd(wd);
import { add, multiply } from './math.vas';
import * as utils from './utils.vas';
import Calculator from './calculator.vas';
calc = Calculator();
result = calc.add(add(5, 3), multiply(2, 4));
name = utils.formatName('ASF', 'Framework');
return `${name}: ${result}`;
math.vas:
fun add(a, b) { return a + b; };
fun multiply(a, b) { return a * b; };
PI = 3.14159;
export { add, multiply, PI };
utils.vas:
fun formatName(first, last) {
return first + ' ' + last;
};
fun uppercase(str) {
return str.toUpperCase();
};
export { formatName, uppercase };
calculator.vas:
fun Calculator() {
return {
add: fun(a, b) { return a + b; },
subtract: fun(a, b) { return a - b; },
multiply: fun(a, b) { return a * b; },
divide: fun(a, b) { return a / b; }
};
};
export default Calculator;
Error Handling
Module-related errors:
try {
import { missing } from './nonexistent.vas';
} catch (e) {
print('Module load failed: ' + e);
};
Common errors:
- Module file not found (#9012): File doesn’t exist at resolved path
- Circular dependency detected (#9010): Module re-enters during load
- Export not found (#9001): Requested export doesn’t exist in module
- Failed to load module (#9011): Source read or compilation error
Built-in Functions
Output
Output to debug/console:
print('Hello, World!');
print(42);
print([1, 2, 3]);
print({ name: 'John', age: 30 });
Type Checking
typeof
Return type as string:
// Basic types
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof null; // 'null'
typeof undefined; // 'undefined'
typeof [1, 2, 3]; // 'array'
typeof {}; // 'object'
typeof fun() {}; // 'function'
// VBA object types (when AppAccess = True)
typeof $1; // 'object: <Workbook>'
typeof $1.sheets; // 'object: <Sheets>'
typeof $1.sheets(1); // 'object: <Worksheet>'
typeof $1.sheets(1).range('A1'); // 'object: <Range>'
// VBA Collections and Dictionaries
// typeof <Collection instance> // 'object: <Collection>'
// typeof <Dictionary instance> // 'object: <Dictionary>'
isArray
Check if value is array:
isArray([1, 2, 3]); // true
isArray('hello'); // false
isArray(null); // false
isNumeric
Check if value is numeric:
isNumeric(42); // true
isNumeric('42'); // true
isNumeric('hello'); // false
isNumeric(true); // false
Array Utilities
range
Create array of numbers:
range(5); // [0, 1, 2, 3, 4]
range(1, 6); // [1, 2, 3, 4, 5]
range(0, 10, 2); // [0, 2, 4, 6, 8]
range(10, 0, -2); // [10, 8, 6, 4, 2]
flatten
Flatten nested arrays:
let nested = [1, [2, 3], [4, [5, 6]]];
flatten(nested); // [1, 2, 3, 4, 5, 6]
flatten(nested, 1); // [1, 2, 3, 4, [5, 6]]
clone
Deep copy arrays/objects:
let original = [1, 2, [3, 4]];
let copy = clone(original);
copy[3][1] = 99;
print(original[3][1]); // 4 (unchanged)
Iteration Helper
foreach
Iterate over array:
let nums = [1, 2, 3, 4, 5];
foreach(nums, fun(val, idx, arr) {
print('Index ' + idx + ': ' + val)
});
Iterate over object:
let scores = {math: 85, english: 92, science: 78};
foreach(scores, fun(val, key) {
s = s + val; c += 1
}); return('Average: ' + s/c); // Average: 85
Regular Expression Helper
regex
Create regex engine:
let re = regex(`hello`, true); // Case-insensitive
let re2 = regex(`\\d+`); // Matches digits
Module System Utilities
cwd
Get current working directory:
let currentPath = cwd();
print(currentPath); // Shows current working directory
scwd
Set current working directory for module resolution:
scwd(wd); // Set working directory
// Relative imports now resolve from this directory
import { add } from './math.vas';
Usage pattern with VBA:
Dim eng As New ASF
eng.InjectVariable "wd", ThisWorkbook.Path
result = eng.Execute(ThisWorkbook.Path & "\main.vas")
Inside the .vas file:
scwd(wd); // Use injected working directory
import { helper } from './utils.vas';
String Methods
Accessing Characters
charAt / charCodeAt
let str = 'Hello';
print(str.charAt(0)); // 'H' (0-based like JavaScript)
print(str.charCodeAt(0)); // 72 (ASCII code of 'H')
at
Access with negative indices:
let str = 'Hello';
print(str.at(0)); // 'H' (first char)
print(str.at(-1)); // 'o' (last char)
print(str.at(-2)); // 'l' (second to last)
String Properties
let str = 'Hello';
print(str.length); // 5
String Transformation
toLowerCase / toUpperCase
let str = 'Hello World';
print(str.toLowerCase()); // 'hello world'
print(str.toUpperCase()); // 'HELLO WORLD'
trim / trimStart / trimEnd
let str = ' hello ';
print(str.trim()); // 'hello'
print(str.trimStart()); // 'hello '
print(str.trimEnd()); // ' hello'
repeat
let str = 'abc';
print(str.repeat(3)); // 'abcabcabc'
padStart / padEnd
let str = '5';
print(str.padStart(3, '0')); // '005'
print(str.padEnd(3, '0')); // '500'
String Searching
indexOf / lastIndexOf
let str = 'hello world hello';
print(str.indexOf('hello')); // 0 (first occurrence)
print(str.lastIndexOf('hello')); // 12 (last occurrence)
print(str.indexOf('xyz')); // -1 (not found)
includes
let str = 'hello world';
print(str.includes('world')); // true
print(str.includes('xyz')); // false
startsWith / endsWith
let str = 'hello world';
print(str.startsWith('hello')); // true
print(str.endsWith('world')); // true
print(str.startsWith('world')); // false
slice
let str = 'hello world';
print(str.slice(0, 5)); // 'hello'
print(str.slice(6)); // 'world'
print(str.slice(-5)); // 'world' (last 5 chars)
substring
let str = 'hello world';
print(str.substring(0, 5)); // 'hello'
print(str.substring(6, 11)); // 'world'
String Manipulation
concat
let str1 = 'Hello';
let str2 = 'World';
print(str1.concat(' ', str2)); // 'Hello World'
split
let str = 'apple,banana,orange';
let fruits = str.split(',');
print(fruits); // ['apple', 'banana', 'orange']
let chars = 'hello'.split('');
print(chars); // ['h', 'e', 'l', 'l', 'o']
// With limit
let limited = 'a,b,c,d'.split(',', 2);
print(limited); // ['a', 'b']
replace / replaceAll
let str = 'hello world hello';
// Replace first occurrence
print(str.replace('hello', 'hi')); // 'hi world hello'
// Replace all occurrences
print(str.replaceAll('hello', 'hi')); // 'hi world hi'
// With function
let result = str.replace('hello', fun(match) {
return match.toUpperCase();
});
print(result); // 'HELLO world hello'
// With regex
let str2 = 'hello123world456';
let result2 = str2.replace(`/\\d+/`, 'X'); // Replace digits
print(result2); // 'helloXworld456'
Pattern Matching
match / matchAll
let str = 'The price is $10 and $20';
// Match first occurrence
let match = str.match('$10');
print(match); // ['$10']
// Match with regex (all digits)
let numbers = str.match(`/\\d+/g`);
print(numbers); // ['10', '20']
// matchAll returns array of all matches
let all = str.matchAll(`/\\d+/g`);
// [['10'], ['20']]
Conversion
toString / valueOf
let str = 'hello';
print(str.toString()); // 'hello'
print(str.valueOf()); // 'hello'
fromCharCode
print(''.fromCharCode(72)); // 'H'
Comparison
localeCompare
let a = 'apple';
let b = 'banana';
print(a.localeCompare(b)); // -1 (a < b)
print(b.localeCompare(a)); // 1 (b > a)
print(a.localeCompare(a)); // 0 (equal)
Template Literals
Basic Syntax
Use backticks (`) for template literals:
let name = 'Alice';
let greeting = `Hello, ${name}!`;
print(greeting); // Hello, Alice!
Expressions in Templates
Any expression can be embedded:
let a = 5, b = 10;
print(`${a} + ${b} = ${a + b}`); // 5 + 10 = 15
let items = [1, 2, 3];
print(`Array length: ${items.length}`); // Array length: 3
Multi-line (Simulated)
let message = `Line 1
Line 2
Line 3`;
// Note: Actual newline handling depends on VBA
Nested Templates
let user = { name: 'John', role: 'admin' };
let status = `User ${user.name} (${user.role == 'admin' ? 'Administrator' : 'User'})`;
print(status); // User John (Administrator)
Complex Expressions
let users = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 92 }
];
for (let i = 1, i <= users.length, i += 1) {
let user = users[i];
print(`${user.name}: ${user.score >= 90 ? 'A' : 'B'}`);
}
// Output:
// Alice: B
// Bob: A
Regular Expressions
Creating Regex
Constructor Notation
let re = regex(`pattern`);
re.init(`\\d+`); // Just pattern
re.init(`hello`, true); // Pattern + case-insensitive
re.init(`hello`, true, false, true); // Pattern + flags
Regex Flags
i- Case-insensitiveg- Global (find all matches)m- Multilines- Dot matches newline (dotAll)
Regex Methods
test
Test if pattern matches:
let re = regex(`\\d+`);
print(re.test('hello123')); // true
print(re.test('hello')); // false
exec
Execute regex and get first match:
let re = regex(`(\\d+)-(\\d+)`);
let result = re.exec('Phone: 555-1234');
print(result); // ['555-1234', '555', '1234']
// result[1] = full match, result[2+] = capture groups
execAll
Get all matches (global flag required):
let re = regex(`\\d+`);
let matches = re.execAll('Numbers: 10, 20, 30');
print(matches); // [['10'], ['20'], ['30']]
replace
Replace pattern matches:
let re = regex(`\\d+`);
let str = 'Price: $10 and $20';
let result = re.replace(str, 'XX');
print(result); // Price: $XX and $XX
split
Split string by pattern:
let re = regex(`[,;]`);
let str = 'apple,banana;orange';
let parts = re.split(str);
print(parts); // ['apple', 'banana', 'orange']
escape
Escape special regex characters, and the first character of the given string:
let escaped = regex().escape('a.b*c?');
print(escaped); // '\a\.b\*c\?'
Regex Properties
let re = regex(`hello`);
// Get/Set pattern
print(re.getPattern()); // 'hello'
re.setPattern(`world`);
print(re.getPattern()); // 'world'
// Get/Set flags
print(re.getIgnoreCase()); // true
re.setIgnoreCase(false);
print(re.getMultiline());
re.setMultiline(true);
print(re.getDotAll());
re.setDotAll(true);
String Methods with Regex
match
let str = 'The numbers are 10 and 20';
let nums = str.match('/\\d+/g');
print(nums); // ['10', '20']
replace
let str = 'hello123world456';
// Replace with string
let r1 = str.replace('/\\d+/', 'X');
print(r1); // 'helloXworld456' (first only)
// Replace all with global flag
let r2 = str.replace('/\\d+/g', 'X');
print(r2); // 'helloXworldX'
// Replace with function
let r3 = str.replace('/\\d+/g', fun(match) {
return '[' + match + ']'
});
print(r3); // 'hello[123]world[456]'
Common Patterns
// Email validation (simple)
let email = regex(`^[^@]+@[^@]+\\.[^@]+$`);
print(email.test('user@example.com')); // true
// Phone number
let phone = regex(`^\\d{3}-\\d{3}-\\d{4}$`);
print(phone.test('555-123-4567')); // true
// URL
let url = regex(`^https?:\\/\\/`);
print(url.test('https://example.com')); // true
// Hexadecimal color
let color = regex(`^#[0-9a-fA-F]{6}$`);
print(color.test('#FF5733')); // true
// Extract all words
let words = regex(`\\w+`);
let text = 'Hello, World! 123';
print(words.execAll(text)); // [['Hello'], ['World'], ['123']]
Error Handling
Try-Catch
try {
let x = 10 / 0; // May cause error in some contexts
print('Result: ' + x);
} catch {
print('An error occurred!');
}
Nested Try-Catch
try {
try {
// Inner operation
let result = riskyOperation();
} catch {
print('Inner error');
};
// Outer operation
anotherOperation();
} catch {
print('Outer error');
};
Error Recovery
fun safeDivide(a, b) {
try {
if (b == 0) {
return null;
}
return a / b;
} catch {
return null;
};
}
let result = safeDivide(10, 2);
if (result == null) {
print('Division failed');
} else {
print('Result: ' + result);
};
Validation Pattern
fun validateUser(user) {
try {
if (typeof(user.name) != 'string') {
return false;
}
if (typeof(user.age) != 'number') {
return false;
}
if (user.age < 0) {
return false;
}
return true;
} catch {
return false;
};
};
let user = { name: 'John', age: 30 };
if (validateUser(user)) {
print('User is valid');
} else {
print('Invalid user data');
};
VBA Integration
Evaluating with VBA-Expressions library
Use @(...) syntax to evaluate with VBA-Expressions:
// Call VBA-Expressions functions
let a = @({1;0;4});
let b = @({1;1;6});
let c = @({-3;0;-10});
print(@(MROUND(LUDECOMP(ARRAY(a;b;c));4)))
Injecting VBA Values
From VBA, inject values into ASF:
Dim engine As New ASF
engine.InjectVariable "userData", Array("John", 30, "john@example.com")
Dim code As String
code = "print(userData[1]);" ' Prints: John
Dim idx As Long
idx = engine.Compile(code)
engine.Run idx
Getting Results in VBA
Dim engine As New ASF
Dim code As String
code = "let x = 10; let y = 20; return(x + y);"
Dim idx As Long
idx = engine.Compile(code)
engine.Run idx
' Get output
Dim result As Variant
result = engine.OUTPUT_
Debug.Print result ' Prints: 30
Office Application Integration
Overview
ASF v3.1.0+ provides native Office object support, enabling seamless interaction with Excel, Word, PowerPoint, Outlook, and Access directly from ASF scripts. This integration includes full property chaining, method invocation, and bidirectional array marshaling.
AppAccess Property
Control Office object access with the AppAccess security property:
Dim engine As New ASF
' Enable Office object access (default: False)
engine.AppAccess = True
' Now scripts can access Office objects via $1, $2, etc.
pid = engine.Compile("return $1.name")
result = engine.Run(pid, ThisWorkbook)
Security Note: AppAccess is False by default. Enable only when scripts need to interact with Office applications.
Excel Integration
Accessing Workbooks and Sheets
Dim engine As New ASF
engine.AppAccess = True
' Access workbook properties
pid = engine.Compile("return $1.name")
wbName = engine.Run(pid, ThisWorkbook)
' Access sheets
pid = engine.Compile("return $1.sheets.count")
sheetCount = engine.Run(pid, ThisWorkbook)
' Access specific sheet
pid = engine.Compile("return $1.sheets(1).name")
sheetName = engine.Run(pid, ThisWorkbook)
Working with Ranges
// Read from range
let data = $1.sheets(1).range('A1:C10').value2;
// Write to range
$1.sheets(1).range('D1').value2 = 'Total';
// Property chaining
let cellValue = $1.sheets(1).range('A1').value2;
Word Integration
Accessing Document Objects
Dim engine As New ASF
engine.AppAccess = True
' Get paragraph text
pid = engine.Compile("return $1.paragraphs(1).range.text")
text = engine.Run(pid, ActiveDocument)
' Count paragraphs
pid = engine.Compile("return $1.paragraphs.count")
count = engine.Run(pid, ActiveDocument)
PowerPoint Integration
Accessing Presentation Objects
Dim engine As New ASF
engine.AppAccess = True
' Get slide count
pid = engine.Compile("return $1.slides.count")
slideCount = engine.Run(pid, ActivePresentation)
' Access shapes on a slide
pid = engine.Compile("return $1.slides(1).shapes.count")
shapeCount = engine.Run(pid, ActivePresentation)
Bidirectional Array Conversion
ASF v3.1.1+ automatically converts between ASF jagged arrays and VBA 2D arrays when interacting with Office objects:
Dim engine As New ASF
engine.AppAccess = True
' ASF jagged array → VBA 2D array (automatic)
Dim code As String
code = "let data = [['id', 'name'], [1, 'John'], [2, 'Jane']]; " & _
"$1.sheets(1).range('A1:B3').value2 = data;" ' Automatic conversion!
engine.Run engine.Compile(code), ThisWorkbook
' VBA 2D array → ASF jagged array (automatic)
code = "let range = $1.sheets(1).range('A1:B3').value2; " & _
"return range[0][0];" ' Automatic conversion!
result = engine.Run(engine.Compile(code), ThisWorkbook)
How It Works:
- When assigning ASF arrays to
Range.Value2, automatic conversion to VBA 2D arrays - When reading
Range.Value2, automatic conversion to ASF jagged arrays - Transparent to the script author - just works!
Enhanced typeof for Office Objects
The typeof operator provides detailed type information for VBA objects:
// VBA Collections
let coll = /* New Collection */;
typeof coll; // 'object: <Collection>'
// Scripting Dictionary
let dict = /* CreateObject("Scripting.Dictionary") */;
typeof dict; // 'object: <Dictionary>'
// Excel objects
typeof $1; // 'object: <Workbook>'
typeof $1.sheets; // 'object: <Sheets>'
typeof $1.sheets(1); // 'object: <Worksheet>'
typeof $1.sheets(1).range('A1'); // 'object: <Range>'
// Other Office applications
// Word: 'object: <Document>', 'object: <Paragraphs>'
// PowerPoint: 'object: <Presentation>', 'object: <Slides>'
Security Best Practices
- Disable AppAccess by default: Only enable when needed
- Validate input: Sanitize user input before passing to Office objects
- Limit scope: Pass specific objects (e.g., single worksheet) rather than entire workbook
- Error handling: Wrap Office interactions in try-catch blocks
' Good: Limited scope
engine.AppAccess = True
Set targetSheet = ThisWorkbook.Sheets("Data")
result = engine.Run(pid, targetSheet)
' Better: Disable after use
engine.AppAccess = True
result = engine.Run(pid, ThisWorkbook)
engine.AppAccess = False
ASF v3.1.2+ supports COM object prototype extension (monkey patching), allowing you to add custom methods to Office objects at runtime.
Overview
Prototype extension enables you to:
- Add custom methods to any Office COM object type
- Create reusable functionality across different Office applications
- Build fluent interfaces with method chaining
- Extend Office objects with domain-specific logic
Basic Syntax
Define prototype methods using the prototype.COM.ObjectType methodName() syntax:
// Syntax: prototype.COM.<ObjectType> <methodName>(<parameters>) { <body> }
prototype.COM.Range formatAsHeader() {
this.Font.Bold = true;
this.Font.Size = 14;
this.Interior.Color = 15592941; // Light blue
return this;
}
Simple Examples
Excel Range Enhancement
// Add a method to format currency
prototype.COM.Range formatCurrency() {
this.NumberFormat = "$#,##0.00";
this.Font.Bold = true;
this.Font.Color = 192; // Dark red
return this;
}
// Usage
$1.Range('B2:B10').formatCurrency();
Word Document Processing
// Add a method to count words in a paragraph
prototype.COM.Paragraph countWords() {
let text = this.Range.Text;
let words = text.split(' ').filter(fun(word) {
return word.trim().length > 0;
});
return words.length;
}
// Usage
let wordCount = $1.Paragraphs(1).countWords();
PowerPoint Slide Automation
// Add a method to apply consistent formatting
prototype.COM.Slide applyTemplate() {
this.Background.Fill.ForeColor.RGB = 16777215; // White
if (this.Shapes.Count > 0) {
this.Shapes(1).TextFrame.TextRange.Font.Name = 'Calibri';
this.Shapes(1).TextFrame.TextRange.Font.Size = 24;
}
return this;
}
// Usage
$1.Slides(1).applyTemplate();
Advanced Examples
ListRow to Dictionary Conversion
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) {
let columnName = headers.item(i).name;
let cellValue = values[1][i];
result.set(columnName, cellValue);
}
return result;
}
// Usage
let customer = $1.ListObjects('Customers').ListRows(1).asDictionary();
let name = customer.get('Name');
let email = customer.get('Email');
Recordset to JSON Converter
prototype.COM.Recordset toJSON() {
let results = [];
this.MoveFirst();
while (!this.EOF) {
let record = {};
for (let i = 0, i < this.Fields.Count, i += 1) {
let fieldName = this.Fields(i).Name;
let fieldValue = this.Fields(i).Value;
record.set(fieldName, fieldValue);
}
results.push(record);
this.MoveNext();
}
return results;
}
// Usage (Access)
let data = $1.OpenRecordset('SELECT * FROM Products').toJSON();
Method Chaining
Prototype methods can return this to enable fluent interfaces:
// Define chainable methods
prototype.COM.Range setBold() {
this.Font.Bold = true;
return this;
}
prototype.COM.Range setColor(color) {
this.Font.Color = color;
return this;
}
prototype.COM.Range center() {
this.HorizontalAlignment = -4108; // xlCenter
return this;
}
// Chain method calls
$1.Range('A1:C1')
.setBold()
.setColor(255) // Red
.center()
.formatCurrency();
Integration with Collection Methods
When OverrideCollMethods = True, Office collections gain JavaScript array methods that work seamlessly with prototype methods:
// Process all rows in a table
let processedData = $1.ListObjects('Sales').ListRows
.map(fun(row) {
return row.asDictionary();
})
.filter(fun(dict) {
return dict.get('Amount') > 1000;
})
.map(fun(dict) {
return {
customer: dict.get('Customer'),
amount: dict.get('Amount'),
formatted: '$' + dict.get('Amount').toString()
};
});
Working with this Context
Within prototype methods, this refers to the COM object instance:
prototype.COM.Worksheet findLastRow(column) {
// 'this' refers to the Worksheet object
let lastRow = this.Cells(this.Rows.Count, column).End(-4162).Row; // xlUp
return lastRow;
}
prototype.COM.Workbook saveBackup() {
// 'this' refers to the Workbook object
let backupName = this.Path + '\\' + this.Name + '.backup';
this.SaveCopyAs(backupName);
return this;
}
VBA Setup Requirements
Enable prototype functionality in your VBA code:
Sub UsePrototypeMethods()
Dim engine As New ASF
' Required settings
engine.AppAccess = True ' Enable Office object access
engine.OverrideCollMethods = True ' Enable collection method override
' Define prototype method
Dim prototypeCode As String
prototypeCode = "prototype.COM.Range highlight() {" & _
" this.Interior.Color = 65535;" & _
" this.Font.Bold = true;" & _
" return this;" & _
"};"
' Use prototype method
Dim usageCode As String
usageCode = "$1.Range('A1:A10').highlight();"
' Execute
Dim pid As Long
pid = engine.Compile(prototypeCode + usageCode)
engine.Run pid, ThisWorkbook.Sheets(1)
End Sub
Supported Object Types
Prototype methods work with any Office COM object:
Microsoft Excel
Application,Workbook,Workbooks,Worksheet,WorksheetsRange,ListObject,ListRow,ListColumn,Chart,PivotTable
Microsoft Word
Application,Document,Documents,SelectionParagraph,Paragraphs,Table,Tables,Range
Microsoft PowerPoint
Application,Presentation,PresentationsSlide,Slides,Shape,Shapes
Microsoft Access
Application,Form,Forms,Report,ReportsRecordset,TableDef,QueryDef
Microsoft Outlook
Application,MailItem,ContactItem,FolderAttachment,Recipient,Account
Error Handling
Handle prototype method errors using try-catch:
prototype.COM.Range safeFormat() {
try {
this.NumberFormat = "0.00%";
this.Font.Color = 255;
return true;
} catch (error) {
print('Formatting failed: ' + error);
return false;
}
}
// Usage with error handling
try {
let success = $1.Range('A1').safeFormat();
if (!success) {
print('Range formatting failed');
}
} catch (error) {
print('Prototype method error: ' + error);
}
Best Practices for Prototype Methods
Return this for Chainability
// Good - enables chaining
prototype.COM.Range formatHeader() {
this.Font.Bold = true;
this.Font.Size = 12;
return this; // Enable chaining
}
// Less useful - breaks chaining
prototype.COM.Range formatHeader() {
this.Font.Bold = true;
this.Font.Size = 12;
return true; // Returns boolean instead
}
Validate Input Parameters
prototype.COM.Range setBackgroundColor(colorValue) {
if (typeof colorValue != 'number') {
print('Error: Color must be a number');
return this;
}
this.Interior.Color = colorValue;
return this;
}
Use Descriptive Method Names
// Good - clear and descriptive
prototype.COM.Range formatAsCurrency() { }
prototype.COM.Range highlightNegativeValues() { }
prototype.COM.ListRow convertToDictionary() { }
// Poor - vague or confusing
prototype.COM.Range doStuff() { }
prototype.COM.Range format() { } // Too generic
prototype.COM.ListRow convert() { } // Unclear what it converts to
Performance Considerations
- Prototype method calls have ~15% overhead compared to native COM methods
- Collection override (
OverrideCollMethods = True) can impact memory usage - Consider caching frequently accessed properties within method implementations
- Use prototype methods for complex operations rather than simple property access
Best Practices
Code Organization
Use Functions for Reusability
// Good
fun calculateTotal(items) {
return items.reduce(fun(sum, item) {
return sum + item.price;
}, 0)
};
// Bad
let total = 0;
for (let i = 1, i <= items.length, i = i + 1) {
total = total + items[i].price;
};
Modular Design
// Separate concerns
fun validateInput(data) {
// Validation logic
}
fun processData(data) {
if (!validateInput(data)) {
return null;
}
// Processing logic
}
fun formatOutput(result) {
// Formatting logic
};
Naming Conventions
// Variables and functions: camelCase
let userName = 'John';
fun calculateTotal() { }
// Classes: PascalCase
class UserAccount { }
class BankTransaction { }
// Constants: UPPER_CASE (by convention)
let MAX_SIZE = 100;
let DEFAULT_TIMEOUT = 30;
// Boolean variables: is/has prefix
let isValid = true;
let hasPermission = false;
Performance Tips
Use Array Methods Instead of Loops
// Good - functional approach
let doubled = numbers.map(fun(x) { return x * 2; });
let evens = numbers.filter(fun(x) { return x % 2 == 0; });
// Slower - manual loops
let doubled = [];
for (let i = 1, i <= numbers.length, i = i + 1) {
doubled.push(numbers[i] * 2);
};
Cache Length in Loops
// Good
let len = arr.length;
for (let i = 1, i <= len, i = i + 1) {
// Process arr[i]
}
// Less efficient
for (let i = 1, i <= arr.length, i = i + 1) {
// arr.length evaluated each iteration
};
Use Local Variables
// Good
fun processItems(items) {
let total = 0;
let count = items.length;
// Process locally
return { total: total, count: count };
}
// Less efficient (global access)
let globalTotal = 0;
fun processItems(items) {
// Access global repeatedly
}
Error Handling
Always Validate Input
fun divide(a, b) {
if (typeof a != 'number' || typeof b != 'number') {
return null;
};
if (b == 0) {
return null;
};
return a / b;
};
Use Try-Catch for Risky Operations
fun parseUserData(jsonString) {
try {
// Risky operation
return JSON.parse(jsonString);
} catch {
return null;
};
};
Memory Management
Clear Large Arrays
let largeArray = range(1, 100000);
// Use array
processData(largeArray);
// Clear when done
largeArray = [];
Avoid Deep Nesting
// Good - flat structure
fun processUser(user) {
if (!user) return null;
if (!user.name) return null;
if (!user.email) return null;
return formatUser(user);
}
// Bad - deep nesting
fun processUser(user) {
if (user) {
if (user.name) {
if (user.email) {
return formatUser(user);
};
};
};
return null;
};
Appendix
Option Base (EXPERIMENTAL)
Control array indexing (0-based or 1-based):
// Set at program start
option base 0; // Use 0-based indexing
option base 1; // Use 1-based indexing (default)
let arr = [10, 20, 30];
print(arr[0]); // Depends on option base setting
Reserved Keywords
The following words are reserved and cannot be used as variable names:
asbreakcasecatchclassconstructorcontinuedefaultelseelseifexportextendsfalsefieldforfromfunifimportletnewnullprintreturnstaticsuperswitchtruetrytypeofundefinedwhile
Limitations
- No
async/awaitsupport - No
Promiseor callback patterns - No arrow functions (
=>) - No
constdeclaration (uselet) - No
var(uselet) - Limited regex features compared to JavaScript
Future Enhancements
Potential future additions:
- Enhanced error handling with error objects
- More ES6+ features
- Performance optimizations
Contributing
For bug reports, feature requests, or contributions, please contact the ASF development team.
License
Copyright 2026 W. García
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
End of Documentation
| Version 1.0.1 | Last Updated:February 2026 |