Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transform usage example #14

Open
jnordberg opened this issue Aug 2, 2021 · 5 comments
Open

Transform usage example #14

jnordberg opened this issue Aug 2, 2021 · 5 comments
Labels
question Further information is requested

Comments

@jnordberg
Copy link

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.

@yjhmelody
Copy link

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

@dcodeIO
Copy link
Member

dcodeIO commented Aug 2, 2021

More transform that may serve as examples:

@MaxGraey MaxGraey added the question Further information is requested label Aug 2, 2021
@jnordberg
Copy link
Author

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

@MaxGraey
Copy link
Member

MaxGraey commented Aug 3, 2021

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

@yjhmelody
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants