examples icon indicating copy to clipboard operation
examples copied to clipboard

Transform usage example

Open jnordberg opened this issue 2 years ago • 5 comments

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 avatar Aug 02 '21 00:08 jnordberg

@jnordberg See https://github.com/willemneal/visitor-as, it can help you.

yjhmelody avatar Aug 02 '21 01:08 yjhmelody

More transform that may serve as examples:

dcodeIO avatar Aug 02 '21 02:08 dcodeIO

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

jnordberg avatar Aug 03 '21 06:08 jnordberg

Perhaps this is an interesting idea for implementing Partial Evaluation (which can be "online", "offline" and most flexibly "hybrid")

MaxGraey avatar Aug 03 '21 06:08 MaxGraey

@dcodeIO Hi, Maybe Need a new example to show transform in typescript.

yjhmelody avatar Dec 09 '22 12:12 yjhmelody