|
2 | 2 |
|
3 | 3 | All notable changes for ASF. This file combines the release notes from the project's releases. |
4 | 4 |
|
| 5 | +## [v3.0.0] - 2026-02-03 |
| 6 | +https://github.com/ECP-Solutions/ASF/releases/tag/v3.0.0 |
| 7 | + |
| 8 | +## Summary |
| 9 | +ASF v3.0.0 is a major-version release that introduces a full ECMAScript-style module system (`import`/`export`) and adds working-directory builtins (`cwd`, `scwd`) together with a single-call `Execute` entry point on the `ASF` UI. Modules are cached on first load, circular dependencies are detected at load time, and relative paths are resolved against the current working directory. Many of the new features are still in beta, awaiting community support and feedback, or under continuous development. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Breaking Changes |
| 14 | + |
| 15 | +- **File extension adopted: `.vas`.** |
| 16 | + `.vas` stands for **V**BA **A**dvanced **S**cripting. |
| 17 | + `ReadModuleSource` resolves `.vas` automatically; bare module names without an extension are tried first as-is, then with `.vas` appended. |
| 18 | + Existing `.asf` source files must be renamed to `.vas` before use with the module loader. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Highlights |
| 23 | + |
| 24 | +- **Added** |
| 25 | + - `.vas` file extension — the official source-file extension for all VBA Advanced Scripting files: |
| 26 | + |
| 27 | + - Module system — `import` / `export` statements with ECMAScript semantics: |
| 28 | + ```javascript |
| 29 | + // ── Named exports (math.vas) ────────────────────── |
| 30 | + fun add(a, b) { |
| 31 | + return a + b; |
| 32 | + }; |
| 33 | + |
| 34 | + fun multiply(a, b) { |
| 35 | + return a * b; |
| 36 | + }; |
| 37 | + |
| 38 | + PI = 3.14159; |
| 39 | + |
| 40 | + export { add, multiply, PI }; |
| 41 | + |
| 42 | + // ── Named imports (main_math.vas) ───────────────── |
| 43 | + scwd(wd); |
| 44 | + import { add, multiply, PI } from './math.vas'; |
| 45 | + |
| 46 | + result = add(5, 3); |
| 47 | + area = PI * multiply(5, 5); |
| 48 | + return `5 + 3 = ${result}, Circle area: ${area}`; |
| 49 | + // => "5 + 3 = 8, Circle area: 78.53975" |
| 50 | + ``` |
| 51 | + |
| 52 | + ```javascript |
| 53 | + // ── Default export (calculator.vas) ─────────────── |
| 54 | + fun Calculator() { |
| 55 | + return { |
| 56 | + add: fun(a, b) { return a + b; }, |
| 57 | + subtract: fun(a, b) { return a - b; } |
| 58 | + }; |
| 59 | + }; |
| 60 | + |
| 61 | + export default Calculator; |
| 62 | + |
| 63 | + // ── Default import (main_calculator.vas) ────────── |
| 64 | + scwd(wd); |
| 65 | + import calc from './calculator.vas'; |
| 66 | + |
| 67 | + calculator = calc(); |
| 68 | + return(calculator.add(10, 5)); |
| 69 | + // => "15" |
| 70 | + ``` |
| 71 | + |
| 72 | + ```javascript |
| 73 | + // ── Namespace import (utils.vas) ────────────────── |
| 74 | + fun formatName(first, last) { |
| 75 | + return first + ' ' + last; |
| 76 | + }; |
| 77 | + |
| 78 | + fun uppercase(str) { |
| 79 | + return str.toUpperCase(); |
| 80 | + }; |
| 81 | + |
| 82 | + export { formatName, uppercase }; |
| 83 | + |
| 84 | + // ── Namespace usage (main_utils.vas) ────────────── |
| 85 | + scwd(wd); |
| 86 | + import * as utils from './utils.vas'; |
| 87 | + |
| 88 | + name = utils.formatName('John', 'Doe'); |
| 89 | + return utils.uppercase(name); |
| 90 | + // => "JOHN DOE" |
| 91 | + ``` |
| 92 | + |
| 93 | + ```javascript |
| 94 | + // ── Mixed: default + named (lib.vas) ────────────── |
| 95 | + fun helper() { |
| 96 | + return 'Helper function'; |
| 97 | + }; |
| 98 | + |
| 99 | + fun main() { |
| 100 | + return 'Main function'; |
| 101 | + }; |
| 102 | + |
| 103 | + VERSION = '1.0.0'; |
| 104 | + |
| 105 | + export default main; |
| 106 | + export { helper, VERSION }; |
| 107 | + |
| 108 | + // ── Mixed imports (app.vas) ──────────────────────── |
| 109 | + scwd(wd); |
| 110 | + import mainFunc, { helper, VERSION } from './lib.vas'; |
| 111 | + |
| 112 | + return `${mainFunc()} | ${helper()} | Version: ${VERSION}`; |
| 113 | + // => "Main function | Helper function | Version: 1.0.0" |
| 114 | + ``` |
| 115 | + |
| 116 | + - Aliasing with `as` in both import and export specifier lists: |
| 117 | + ```javascript |
| 118 | + import { add as sum } from './math.vas'; |
| 119 | + export { localName as publicName }; |
| 120 | + ``` |
| 121 | + |
| 122 | + - Working-directory builtins (`cwd` / `scwd`): |
| 123 | + ```javascript |
| 124 | + // scwd(path) — set current working directory |
| 125 | + // cwd() — return current working directory |
| 126 | + scwd(wd); // set to injected path |
| 127 | + currentPath = cwd(); // read it back |
| 128 | + // currentPath === wd |
| 129 | + ``` |
| 130 | + |
| 131 | + - `Execute(filePath)` method on the `ASF` façade — single call to read, compile, and run a `.vas` file and return its result: |
| 132 | + ```vba |
| 133 | + Dim eng As New ASF |
| 134 | + eng.InjectVariable "wd", ThisWorkbook.path |
| 135 | + result = eng.Execute(ThisWorkbook.path & "\main_math.vas") |
| 136 | + ' result === "5 + 3 = 8, Circle area: 78.53975" |
| 137 | + ``` |
| 138 | + |
| 139 | +- **Internal core changes**: |
| 140 | + - **Parser** (`ASF_Parser.cls`): |
| 141 | + - Four identifiers are now intercepted during tokenization and emitted as `["KEYWORD", …]` tokens instead of `["IDENT", …]`: `import`, `export`, `default`, `as` |
| 142 | + - Matching is case-sensitive; only the lower-case forms are recognised as keywords |
| 143 | + |
| 144 | + - **Compiler** (`ASF_Compiler.cls`): |
| 145 | + - New instance properties `CurrentModulePath` and `IsModuleMode` set by the VM loader before each module compilation |
| 146 | + - New `ParseImportStatement` method — parses all five supported import forms and produces an `Import` AST node |
| 147 | + - New `ParseExportStatement` method — parses named-brace, default-expression, and declaration export forms and produces an `Export` AST node |
| 148 | + - `CompileProgram` main loop now checks for `KEYWORD / import` and `KEYWORD / export` tokens before falling through to the existing statement collector; matched statements are added directly to `stmtsAST` via `GoTo Compiler_MainLoop` |
| 149 | + |
| 150 | + - **VM** (`ASF_VM.cls`): |
| 151 | + - New builtins `cwd` and `scwd` in the Call-case builtin dispatcher; both read/write `GLOBALS_.CURRENT_MODULE_PATH` |
| 152 | + - New `ExecModImport` — resolves module path, calls `LoadModule`, then binds default, namespace, or named imports into the caller's scope; named-export values are resolved through `EvalExprNode` (Variable fallback to `gFuncTable`) so that top-level `fun` declarations export correctly |
| 153 | + - New `ExecModExport` — writes named or default export values into `gModuleExports` for the currently executing module path |
| 154 | + - New `LoadModule` — orchestrates the full module lifecycle: circular-dependency check against `gLoadingModules`, source read, compilation via a fresh `ASF_Compiler` instance, scope creation, statement execution, pending-export post-processing, and default-export extraction; caches the result in `gModuleRegistry` |
| 155 | + - New `ResolveModulePath` — for paths beginning with `./` or `../`, prepends the current working directory (normalised to forward-slash); otherwise returns the path unchanged |
| 156 | + - New `ReadModuleSource` — delegates to `ResolveModulePath`, appends `.vas` if the file does not exist without it, raises error 9012 if still missing, then delegates to `ReadTextFile` |
| 157 | + |
| 158 | + - **Globals** (`ASF_Globals.cls`): |
| 159 | + - `gModuleRegistry` (`ASF_Map`) — caches loaded module objects keyed by resolved path |
| 160 | + - `gModuleExports` (`ASF_Map`) — maps each module path to its live exports map during execution |
| 161 | + - `gLoadingModules` (`Collection`) — stack of paths currently being loaded; used for circular-dependency detection |
| 162 | + - `CURRENT_MODULE_PATH` (`String`) — the working directory used by `ResolveModulePath` and read/written by `cwd`/`scwd` |
| 163 | + - All four members are initialised in `ASF_InitGlobals` |
| 164 | + |
| 165 | + - **ASF UI** (`ASF.cls`): |
| 166 | + - New `Execute(filePath As String)` method — reads, compiles, and runs a `.vas` file in a single call; returns the program output |
| 167 | + - New `WorkingDir` property (get/set) — direct access to `GLOBALS_.CURRENT_MODULE_PATH` |
| 168 | + - New `ClearModuleCache` method — resets `gModuleRegistry`, `gModuleExports`, `gLoadingModules`, and `CURRENT_MODULE_PATH` |
| 169 | + - New `ReadTextFile(filePath)` method — exposes the VM's binary-stream file reader |
| 170 | + |
| 171 | +- **Technical Details**: |
| 172 | + - **Import AST Node**: |
| 173 | + ``` |
| 174 | + Import { |
| 175 | + type: "Import" |
| 176 | + source: String // raw string-literal value from 'from' clause |
| 177 | + defaultImport: String // present only for default-import forms |
| 178 | + namespaceImport: String // present only for * as X forms |
| 179 | + namedImports: Collection // present only when { … } specifiers exist; |
| 180 | + } // each item is an ImportSpecifier |
| 181 | + ``` |
| 182 | + |
| 183 | + - **ImportSpecifier AST Node**: |
| 184 | + ``` |
| 185 | + ImportSpecifier { |
| 186 | + type: "ImportSpecifier" |
| 187 | + imported: String // name as it appears in the exporting module |
| 188 | + local: String // name bound in the importing scope (differs when 'as' used) |
| 189 | + } |
| 190 | + ``` |
| 191 | + |
| 192 | + - **Export AST Node** (three shapes): |
| 193 | + ``` |
| 194 | + // default form |
| 195 | + Export { |
| 196 | + type: "Export" |
| 197 | + isDefault: True |
| 198 | + expression: ExprNode // parsed expression after 'default' |
| 199 | + } |
| 200 | + |
| 201 | + // named-brace form |
| 202 | + Export { |
| 203 | + type: "Export" |
| 204 | + isDefault: False |
| 205 | + namedExports: Collection // each item is an ExportSpecifier |
| 206 | + } |
| 207 | + |
| 208 | + // declaration form (export fun …) |
| 209 | + Export { |
| 210 | + type: "Export" |
| 211 | + isDefault: False |
| 212 | + declarationType: "function" |
| 213 | + declarationName: String // function name; main loop re-parses the declaration |
| 214 | + } |
| 215 | + ``` |
| 216 | + |
| 217 | + - **ExportSpecifier AST Node**: |
| 218 | + ``` |
| 219 | + ExportSpecifier { |
| 220 | + type: "ExportSpecifier" |
| 221 | + local: String // name in the module's own scope |
| 222 | + exported: String // name exposed to importers (differs when 'as' used) |
| 223 | + } |
| 224 | + ``` |
| 225 | + |
| 226 | + - **Module Object** (stored in `gModuleRegistry`): |
| 227 | + ``` |
| 228 | + Module (ASF_Map) { |
| 229 | + path: String // resolved absolute path used as cache key |
| 230 | + loaded: Boolean // True after full execution completes |
| 231 | + exports: ASF_Map // live named-export bindings (name → value) |
| 232 | + defaultExport: Variant // present only when module contains 'export default' |
| 233 | + } |
| 234 | + ``` |
| 235 | + |
| 236 | + - **New Keywords** (tokenised as `["KEYWORD", value]`): |
| 237 | + ``` |
| 238 | + "import" "export" "default" "as" |
| 239 | + ``` |
| 240 | + |
| 241 | + - **Error Messages**: |
| 242 | + - `"Unexpected end after import"` — import statement truncated (Compiler, #8001) |
| 243 | + - `"Expected 'as' after * in import"` — namespace import missing `as` (Compiler, #8002) |
| 244 | + - `"Expected identifier after 'as'"` — `as` not followed by a name (Compiler, #8003 / #8004 / #8011) |
| 245 | + - `"Invalid import syntax"` — unrecognised token after `import` (Compiler, #8005) |
| 246 | + - `"Expected 'from' in import statement"` — missing `from` clause (Compiler, #8006) |
| 247 | + - `"Expected string literal for module path"` — non-string after `from` (Compiler, #8007) |
| 248 | + - `"Unexpected end after export"` — export statement truncated (Compiler, #8010) |
| 249 | + - `"Expected function name after 'fun'"` — `export fun` not followed by name (Compiler, #8012) |
| 250 | + - `"Invalid export syntax"` — unrecognised token after `export` (Compiler, #8013) |
| 251 | + - `"Export 'X' not found in module 'Y'"` — named import references missing key (VM, #9001) |
| 252 | + - `"Circular dependency detected: X"` — module re-entered while still loading (VM, #9010) |
| 253 | + - `"Failed to load module: X"` — source read raised an error (VM, #9011) |
| 254 | + - `"Module file not found: X"` — path does not exist after `.vas` fallback (VM, #9012) |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +**Full Changelog**: https://github.com/ECP-Solutions/ASF/compare/v2.0.8...v3.0.0 |
| 259 | + |
5 | 260 | ## [v2.0.8] - 2026-02-01 |
6 | 261 | https://github.com/ECP-Solutions/ASF/releases/tag/v2.0.8 |
7 | 262 |
|
|
0 commit comments