How to create reusable formula blocks in MakeCode extensions?

Hi @richard ! I had a question about custom blocks in extensions.

I’m working on a MathX extension, and I’d like users to be able to define reusable formulas in Blocks, something conceptually like:

define formula "square" with input x
    x × x

Then later evaluate it with something like:

evaluate formula "square" with 5

I tried using callback handlers (handlerStatement, draggableParameters, etc.), but I ran into the issue that the callback only provides the evaluated value and not the expression itself, so I can’t store the formula for later evaluation.

Is there a supported way for an extension to create a block like this, or would it require custom Blockly/PXT changes? If it isn’t currently possible, is there a recommended approach for implementing something similar in a MakeCode extension?

Thanks!

Code:

// CUSTOM FUNCTIONS 


    type FormulaFn = (x: number) => number

    let formulas: { [key: string]: FormulaFn } = {}

    let currentFormula = ""

    let currentInput = 0

    /**

    * Define a formula

    */

    //% blockId=mathx_define_formula

    //% block="define formula $name with input $x"

    //% handlerStatement=1

    //% draggableParameters="reporter"

    //% group="Create"

    //% subcategory="Custom Functions"

    //% name.shadow="text"

    //% x.defl="x"

    export function defineFormula(name: string, handler: (x: number) => void): void {

        currentFormula = name

        // Run once so "set result" executes while defining.

        handler(0)

        currentFormula = ""

    }

    /**

     * Formula input

     */

    //% blockId=mathx_formula_input

    //% block="input"

    //% group="Create"

    //% subcategory="Custom Functions"

    export function input(): number {

        return currentInput

    }

    /**

    * Set formula result

    */

    //% blockId=mathx_set_formula_result

    //% block="set result to $value"

    //% group="Create"

    //% subcategory="Custom Functions"

    //% value.shadow="math_number"

    export function setFormulaResult(value: number): void {

        if (!currentFormula)

            return

        formulas[currentFormula] = (x: number) => {

            currentInput = x

            return value

        }

    }

    /**

    * Evaluate formula

    */

    //% blockId=mathx_eval_formula

    //% block="evaluate formula $name with $x"

    //% group="Run"

    //% subcategory="Custom Functions"

    //% name.shadow="text"

    //% x.shadow="math_number"

    export function evalFormula(name: string, x: number): number {

        let fn = formulas[name]

        if (!fn)

            return 0

        return fn(x)

    }

Github: (I haven’t pushed the changes yet for it)

sure, i’ve done this thing in a few of my extensions (most notably arcade-tile-scanner and arcade-rpg)

there is no way to define callbacks with return values in blocks, but you can fake it by building up an equation using functions you create. here’s an example of what I mean:

the blocks code is in custom.ts

the downside to this approach is that you basically have to remake all of the math blocks and people need to know to use your versions instead of the ones in the math category.

2 Likes

That makes sense. I hadn’t considered that I’d need to recreate most of the math blocks. I think I’ll experiment with it and see whether the usability is worth the extra blocks.