libast-transform provides utilities for transforming and wrapping Abstract Syntax Tree (AST) nodes during Oak compilation and bundling.
transform := import('ast-transform')
{
wrapBlock: wrapBlock
wrapModule: wrapModule
formatIdentForWeb: formatIdentForWeb
formatIdentForOak: formatIdentForOak
} := import('ast-transform')
Creates a block AST node containing multiple expressions.
Parameters:
nodes- List of AST nodes
Returns: Block AST node
{ wrapBlock: wrapBlock } := import('ast-transform')
// Combine multiple AST nodes into one block
block := wrapBlock([node1, node2, node3])
// Equivalent to: { node1; node2; node3 }
Transforms an AST block into a module wrapper function for bundling.
Parameters:
block- Block AST node
Returns: Function AST node that exports module contents
{ wrapModule: wrapModule } := import('ast-transform')
moduleFunc := wrapModule(blockNode)
// Creates: fn { /* block contents */ }
Formats an identifier for JavaScript output, avoiding reserved words.
Parameters:
name- Original identifier namekey- Optional unique suffix
Returns: Safe JavaScript identifier
{ formatIdentForWeb: formatIdentForWeb } := import('ast-transform')
formatIdentForWeb('_') // => '__oak_empty_ident'
formatIdentForWeb('class') // => '__oak_js_class'
formatIdentForWeb('function') // => '__oak_js_function'
formatIdentForWeb('myVar') // => 'myVar'
formatIdentForWeb('_', '1') // => '__oak_empty_ident1'
Formats an identifier for Oak output, handling Oak-specific reserved words.
Parameters:
name- Original identifier name
Returns: Safe Oak identifier
{ formatIdentForOak: formatIdentForOak } := import('ast-transform')
formatIdentForOak('import') // => '__oak_module_import'
formatIdentForOak('myVar') // => 'myVar'
Protected by formatIdentForWeb:
await, break, case, catch, class, const, continue
debugger, default, delete, do, else, enum, export
extends, false, finally, for, function, if, in
instanceof, new, null, return, super, switch, this
throw, true, try, typeof, var, void, while, with
yield
// Underscore (_) gets unique suffix to avoid shadowing
formatIdentForWeb('_') // => '__oak_empty_ident'
formatIdentForWeb('_', '0') // => '__oak_empty_ident0'
// JS keywords get prefixed
formatIdentForWeb('class') // => '__oak_js_class'
formatIdentForWeb('return') // => '__oak_js_return'
// Normal names unchanged
formatIdentForWeb('myVar') // => 'myVar'
{
type: :block
tok: { pos: [0, 1, 1], type: :leftBrace, val: ? }
exprs: [expr1, expr2, expr3] // List of expressions
decls: ['var1', 'var2'] // Declared variables
}
{
type: :function
tok: { pos: [0, 1, 1], type: :fnKeyword, val: ? }
name: '' // Empty for anonymous
args: [] // Parameters
restArg: ? // Rest parameter or ?
body: blockNode // Function body
decls: ['localVar'] // Local declarations
}
{ wrapBlock: wrapBlock } := import('ast-transform')
statements := [
{ type: :assignment, /* ... */ }
{ type: :fnCall, /* ... */ }
{ type: :identifier, /* ... */ }
]
combinedBlock := wrapBlock(statements)
{ wrapBlock: wrapBlock, wrapModule: wrapModule } := import('ast-transform')
// Wrap module exports in function
exports := wrapBlock([
exportNode1
exportNode2
])
moduleFunc := wrapModule(exports)
// Result is callable module initializer
{ formatIdentForWeb: formatIdentForWeb } := import('ast-transform')
// Generate safe variable names for JS output
varNames := ['class', 'return', 'myVar', '_']
safeNames := varNames |> map(fn(name, i) {
formatIdentForWeb(name, string(i))
})
// => ['__oak_js_class', '__oak_js_return', 'myVar', '__oak_empty_ident0']
{ wrapBlock: wrapBlock, formatIdentForWeb: formatIdentForWeb } := import('ast-transform')
fn processModule(moduleAST, moduleName) {
// Rename identifiers to avoid conflicts
processedAST := walkNode(moduleAST, fn(node) if node.type = :identifier {
true -> {
node.val := formatIdentForWeb(node.val, moduleName)
node
}
_ -> node
})
// Wrap in module function
wrapModule(processedAST)
}
{
wrapBlock: wrapBlock
wrapModule: wrapModule
formatIdentForWeb: formatIdentForWeb
} := import('ast-transform')
fn bundleModules(modules) {
// Transform each module
moduleWrappers := modules |> map(fn(mod) {
// Wrap module contents
moduleFunc := wrapModule(mod.ast)
// Safe module name
safeName := formatIdentForWeb('mod_' + mod.name)
{
name: safeName
func: moduleFunc
}
})
// Combine all modules into one block
wrapBlock(moduleWrappers |> map(fn(m) m.func))
}
Token objects in AST nodes:
{
pos: [offset, line, column] // Source position
type: :tokenType // Token type atom
val: value // Token value or ?
}
emptyToken := { pos: [0, 1, 1], type: :leftBrace, val: ? }
{ wrapBlock: wrapBlock } := import('ast-transform')
// Create node from scratch
syntheticNode := {
type: :identifier
tok: { pos: [0, 1, 1], type: :identifier, val: 'x' }
val: 'x'
}
block := wrapBlock([syntheticNode])
// build.oak uses ast-transform
buildAST := parseSource(code)
wrappedModule := wrapModule(buildAST)
jsCode := codegen(wrappedModule)
// bundle-ast.oak uses ast-transform
modules := collectModules()
bundled := wrapBlock(modules |> map(wrapModule))
// transpile.oak uses formatIdent functions
transpileNode := fn(node) {
if node.type = :identifier -> {
node.val := formatIdentForWeb(node.val)
}
node
}
- All functions return new AST nodes (immutable)
- Token positions use synthetic values ([0, 1, 1])
- Block wrapping preserves declaration information
- Module wrapping creates anonymous functions
- Identifier formatting is deterministic
- Synthetic tokens have no real source position
- No validation of reserved words beyond predefined list
- Module wrapping doesn't handle circular dependencies
- No automatic import/export transformation
- formatIdent functions are target-specific
- Bundling: Combine multiple modules
- Code generation: Create synthetic AST nodes
- Transpilation: Safe identifier renaming
- Optimization: Restructure code blocks
- Module systems: Wrap exports in functions
- ast-analyze.md - Semantic AST analysis
- bundle-ast.md - AST bundling
- transpile.md - AST transformation middleware
- build.md - Build system