
2026/02/18 19:29
**VBAにおけるモンキーパッチング** - **モンキーパッチング**とは、実行時に既存のコードを変更・拡張する手法で、主に関数やプロシージャ、オブジェクトを新しい実装に置き換えることです。 - VBA(Visual Basic for Applications)では以下のように制限があります。 * ビルトイン関数の本体は直接変更できません。 * 回避策としては次のような方法が用いられます。 - **クラスモジュール**を使って元機能をラップし、改変したメソッドだけを公開する。 - **Application.OnTime**やイベントハンドラを利用して呼び出しを傍受・変更する。 - **APIコール**でメモリ上のコードを書き換える(非常に危険)。 - よくある使用例 * 既存プロシージャにログ記録やデバッグラッパーを追加する。 * 元ソースを変更せずにエラーハンドリングロジックを差し替える。 * サードパーティ製アドインに独自の振る舞いを付与する。 --- ### 実践的なヒント 1. **ラッパークラスを作成** ```vba ' クラスモジュール: MySheetWrapper Private pSheet As Worksheet Public Sub Init(ByVal ws As Worksheet) Set pSheet = ws End Sub Public Function GetCell(rng As String) As Variant Debug.Print "セルにアクセス中:" & rng GetCell = pSheet.Range(rng).Value End Function ``` 2. **イベントハンドラを利用** ```vba Private Sub Worksheet_Change(ByVal Target As Range) ' 変更を傍受し、動作を修正する処理を書き込む End Sub ``` 3. **直接メモリパッチは避ける** – Excelがクラッシュしたりサポート外になる恐れがあります。 > **ポイント** > VBA の設計は本格的なモンキーパッチングを推奨していません。安全に拡張するには、**構成(composition)**や**イベントフック**を利用する方法が望ましいです。
RSS: https://news.ycombinator.com/rss
要約▶
本文
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.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 // 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 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: ' typeof $1.sheets; // 'object: ' typeof $1.sheets(1); // 'object: ' typeof $1.sheets(1).range('A1'); // 'object: '
// VBA Collections and Dictionaries // typeof // 'object: ' // typeof // 'object: ' 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-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('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: '
// Scripting Dictionary let dict = /* CreateObject("Scripting.Dictionary") */; typeof dict; // 'object: '
// Excel objects typeof $1; // 'object: ' typeof $1.sheets; // 'object: ' typeof $1.sheets(1); // 'object: ' typeof $1.sheets(1).range('A1'); // 'object: '
// Other Office applications // Word: 'object: ', 'object: ' // PowerPoint: 'object: ', 'object: ' 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. () { } 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