examples
examples copied to clipboard
Transform usage example
It would be awesome to have a usage example for transforms that showed how to transform some code. I want to try implementing a custom decorator as a preprocessor macro (as hinted by the docs you can do) but I'm not sure where to start.
@jnordberg See https://github.com/willemneal/visitor-as, it can help you.
Thanks for those references! I'm experimenting with implementing something akin to C++'s constexpr
as a decorator and was able to do it pretty easily, very cool to be able to extend the language like this :)
Attaching my transform that does this for posterity, it's super unsafe easy to break but works as a proof of concept, I think it could be made safe by actually compiling the code into another wasm module that's executed by the transform and only allowing a safe set of instructions to be used in the body of functions marked with @staticEval
.
Example usage:
// @ts-ignore: decorator
@staticEval
function hash(value: string): i32 {
let l = value.length, rv = 0
for (let i = 0; i < l; i++) {
rv = (rv << 5) - rv + value.charCodeAt(i)
}
return rv
}
function getString() {
return 'hello runtime!'
}
const hash1 = hash('hello') // i32.const 99162322
const hash2 = hash(getString()) // call $assembly/index/hash
Transform:
import {
Parser,
NodeKind,
FunctionDeclaration,
IdentifierExpression,
CallExpression,
Expression,
IntegerLiteralExpression,
LiteralExpression,
LiteralKind,
FloatLiteralExpression,
StringLiteralExpression,
} from 'assemblyscript'
import { SimpleParser, TransformVisitor } from 'visitor-as'
class StaticEvalTransform extends TransformVisitor {
staticFunctions: { [name: string]: Function } = {}
visitCallExpression(node: CallExpression): Expression {
if (node.expression instanceof IdentifierExpression) {
const fn = this.staticFunctions[node.expression.text]
// someone is calling a staticEval function
if (fn) {
// only allow constant as arguments
let args = node.args
.filter((arg) => arg.kind == NodeKind.LITERAL)
.map((value) => {
const arg = value as LiteralExpression
switch (arg.literalKind) {
case LiteralKind.INTEGER:
// yes this will explode :)
return parseInt(
(arg as IntegerLiteralExpression).value.toString()
)
case LiteralKind.FLOAT:
return parseFloat(
(arg as FloatLiteralExpression).value.toString()
)
case LiteralKind.STRING:
return (arg as StringLiteralExpression).value
}
})
.filter((arg) => arg !== undefined)
if (args.length === node.args.length) {
// call fn with args and inline the result
let result = fn(...args)
let res = SimpleParser.parseExpression(
JSON.stringify(result)
)
res.range = node.range
return res
}
}
}
return super.visitCallExpression(node)
}
afterParse(parser: Parser): void {
this.staticFunctions = {}
const sources = this.program.sources.filter((node) => !node.isLibrary)
// find all top-level staticEval functions
for (const source of sources) {
for (const statement of source.statements) {
if (statement instanceof FunctionDeclaration) {
let staticEval = statement.decorators?.some(
(decorator) =>
decorator.name.kind == NodeKind.IDENTIFIER &&
(<IdentifierExpression>decorator.name).text === 'staticEval'
)
if (!staticEval) continue
let params = statement.signature.parameters.map(
(param) => param.name.text
)
this.staticFunctions[statement.name.text] = Function(
...params,
statement.body.range.toString()
)
}
}
}
// visit all statements
this.visit(sources)
}
}
export = StaticEvalTransform
Perhaps this is an interesting idea for implementing Partial Evaluation (which can be "online", "offline" and most flexibly "hybrid")
@dcodeIO Hi, Maybe Need a new example to show transform in typescript.