ASF Language Documentation

Version 1.0.1 Complete Language Reference

Table of Contents

  1. Introduction
  2. Getting Started
  3. Basic Syntax
  4. Data Types
  5. Variables
  6. Operators
  7. Control Flow
  8. Functions
  9. Arrays
  10. Objects
  11. Object Member Methods
  12. Classes
  13. Module System
  14. Built-in Functions
  15. String Methods
  16. Array Methods
  17. Template Literals
  18. Regular Expressions
  19. Error Handling
  20. VBA Integration
  21. Office Application Integration
  22. COM Object Prototype Extension
  23. 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

  1. Import the ASF class modules into your VBA project:
    • ASF.cls
    • ASF_Compiler.cls
    • ASF_Globals.cls
    • ASF_Map.cls
    • ASF_Parser.cls
    • ASF_ScopeStack.cls
    • ASF_VM.cls
    • ASF_RegexEngine.cls
    • UDFunctions.cls
    • VBAcallBack.cls
    • VBAexpressions.cls
    • VBAexpressionsScope.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

Comments

// Single-line comment

/* Multi-line
   comment */

# Python-style comment (also supported)

Statements

Statements are terminated by semicolons (;):

let x = 10;
print(x);

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:

let nothing = null;

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: '[email protected]'
};

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:

  1. Parentheses ( )
  2. Unary !, -, typeof, ...
  3. Exponentiation ^
  4. Multiplication/Division *, /, %
  5. Addition/Subtraction +, -
  6. Bitwise Shift <<, >>
  7. Comparison <, >, <=, >=
  8. Equality ==, !=
  9. Logical AND &&
  10. Logical OR ||
  11. Ternary ? :
  12. 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: '[email protected]'
};

Accessing Properties

// Dot notation
print(person.name);  // 'John Doe'

// Bracket notation
print(person['age']);  // 30

// Dynamic property access
let prop = 'email';
print(person[prop]);  // '[email protected]'

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: '[email protected]' };

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: '[email protected]',
    _internal: true
};

let publicFields = user.filter(fun(val, key) {
    return !key.startsWith('_');
});
print(publicFields);  // { name: 'John', email: '[email protected]' }

// 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: '[email protected]' };
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: '[email protected]', 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

  1. Parent class fields (if inheritance)
  2. Current class fields
  3. 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 against cwd()
  • 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

print

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

String Extraction

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-insensitive
  • g - Global (find all matches)
  • m - Multiline
  • s - 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('[email protected]'));  // 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, "[email protected]")

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

  1. Disable AppAccess by default: Only enable when needed
  2. Validate input: Sanitize user input before passing to Office objects
  3. Limit scope: Pass specific objects (e.g., single worksheet) rather than entire workbook
  4. 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

COM Object Prototype Extension

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, Worksheets
  • Range, ListObject, ListRow, ListColumn, Chart, PivotTable

Microsoft Word

  • Application, Document, Documents, Selection
  • Paragraph, Paragraphs, Table, Tables, Range

Microsoft PowerPoint

  • Application, Presentation, Presentations
  • Slide, Slides, Shape, Shapes

Microsoft Access

  • Application, Form, Forms, Report, Reports
  • Recordset, TableDef, QueryDef

Microsoft Outlook

  • Application, MailItem, ContactItem, Folder
  • Attachment, 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:

  • as
  • break
  • case
  • catch
  • class
  • constructor
  • continue
  • default
  • else
  • elseif
  • export
  • extends
  • false
  • field
  • for
  • from
  • fun
  • if
  • import
  • let
  • new
  • null
  • print
  • return
  • static
  • super
  • switch
  • true
  • try
  • typeof
  • undefined
  • while

Limitations

  • No async/await support
  • No Promise or callback patterns
  • No arrow functions (=>)
  • No const declaration (use let)
  • No var (use let)
  • 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