Skip to content

Commit

Permalink
changes
Browse files Browse the repository at this point in the history
  • Loading branch information
pmalacho-mit committed May 24, 2024
1 parent 4e950d0 commit 52d424f
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 123 deletions.
3 changes: 0 additions & 3 deletions extensions/src/common/extension/decorators/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export const blockBundleEvent = tryCreateBundleTimeEvent<BlockFunctionMetadata>(
export const getAccessorPrefix = "__getter__";
export const setAccessorPrefix = "__setter__";

export const reporter = makeDecorator("reporter");
export const command = makeDecorator("command");

/**
* This a decorator function that should be associated with methods of your Extension class, all in order to turn your class methods
* into Blocks that can be executed in the Block Programming Environment.
Expand Down
137 changes: 38 additions & 99 deletions extensions/src/common/extension/decorators/newBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,29 @@
import { getImplementationName } from "../mixins/base/scratchInfo/index";
import { BlockMetadata, Argument, ScratchArgument, ToArguments } from "$common/types";
import { blockBundleEvent } from "$common/extension/decorators/blocks";
import { BlockMetadata, Argument } from "$common/types";
import { block } from "$common/extension/decorators/blocks";
import { BlockType } from "$common/types/enums";
import { ExtensionInstance } from "..";
import type BlockUtilityWithID from "$scratch-vm/engine/block-utility";
import { isFunction, isString } from "$common/utils";
import { extractArgs } from "../mixins/base/scratchInfo/args";
import { TypedMethodDecorator } from ".";

// This should be defined elsewhere
type ScratchBlockType = typeof BlockType[keyof typeof BlockType];

const process = (type: ScratchBlockType, strings: TemplateStringsArray, ...args: any[]) => {
if (args.length === 0) return { type, text: strings[0], };
const text = (...placeholders: any[]) => strings.map((str, i) => `${str}${placeholders[i] ?? ""}`).join("");
if (args.length === 1) return { type, text, arg: args[0] };
return { type, text, args };
}


const extractTaggedTemplateLiteral = (funcString) => {
// Define a regular expression pattern to match the tagged template literal
const pattern = /`([^`]*)`/;
// Match the tagged template literal in the string
const match = funcString.match(pattern);
// Return the tagged template literal component
return match ? match[1] : null;
};

export function makeDecorator<T>(type: T): TemplateEngine<T>["execute"] {

export function makeDecorator<T extends ScratchBlockType>(type: T): TemplateEngine<T>["execute"] {
// function takes T and returns a function of TemplateEngine type
// TemplateEngine returns based on the ScratchType of the block
return function decoratorFn<
const Return
>(builderOrStrings, ...args) {
return function decoratorFn(builderOrStrings, ...args) {
return function (target, context) {
// Defining block characteristics
const opcode = target.name;
const internalFuncName = getImplementationName(opcode);
let argList: any[] = args;
const blockType = (type === "reporter") ? BlockType.Reporter : BlockType.Command;

let textFunction;
if (typeof builderOrStrings == "function") {
// Get the tagged template string
const taggedTemplateLiteral = extractTaggedTemplateLiteral(builderOrStrings.toString());
const tagFunction = (strings1, ...values) => {
// Set the arguments
argList = values;
// Set the text function
textFunction = (...values: any[]) => {
let result = '';
strings1.forEach((str, index) => {
result += str;
if (index < values.length) {
result += values[index];
}
});
return result;
};
return "";

};
// Initiate the function?
tagFunction`For some reason, I need this line for the line below...`;
// Call the function and set arguments and text function
const taggedTemplate = eval("tagFunction`" + taggedTemplateLiteral + "`");

} else {
// Set the text function
textFunction = (...args: any[]) => {
const strings = Array.isArray(builderOrStrings) ? builderOrStrings : [builderOrStrings];
let result = '';
strings.forEach((str, index) => {
result += str;
if (index < args.length) {
result += args[index];
}
});
return result;
};
}

// Push the block
//type Fn = (this: This extends ExtensionInstance, value: any, util: BlockUtilityWithID) => void;
type Fn = (...args) => Return;
const blockInfo = { type: blockType, text: textFunction, args: argList };
context.addInitializer(function () { this.pushBlock(opcode, blockInfo as BlockMetadata<Fn>, target) });

// bundle events
const isProbableAtBundleTime = !isFunction(blockInfo);
if (isProbableAtBundleTime) {
const { type } = blockInfo;
blockBundleEvent?.fire({
methodName: opcode,
args: extractArgs(blockInfo as BlockMetadata<Fn>).map(a => isString(a) ? a : a.type),
// is 'any' an issue? Likely!
returns: type === "command" ? "void" : "any",
scratchType: blockInfo.type
});
}

return (function () { return this[internalFuncName].call(this, ...arguments) });
const input: any = typeof builderOrStrings == "function"
? (instance) => builderOrStrings(instance, process.bind(null, type))
: process(type, builderOrStrings, ...args);
return block(input)(target, context);
}
}
}
Expand All @@ -107,12 +36,14 @@ namespace Utility {
namespace Argument {
type TRemoveUtil<T extends any[]> = T extends [...infer R extends any[], BlockUtilityWithID] ? R : T;
// Maya note: thought ToArguments was the equivalent, but TypeScript does not like it....
// Parker note: ^interesting! Also, if we keep this implementation, we'll need to handle InlineImages
export type MapToScratch<T extends any[], Internal extends TRemoveUtil<T> = TRemoveUtil<T>> = {
[k in keyof Internal]: Argument<Internal[k]>
}
}

interface TemplateEngine<TBlockType> {
// TODO: Restrict return based on Scratch type
interface TemplateEngine<TBlockType extends ScratchBlockType> {
/**
*
*/
Expand All @@ -121,20 +52,28 @@ interface TemplateEngine<TBlockType> {
const Args extends any[],
const Return,
>
(
strings: TemplateStringsArray, ...args: Argument.MapToScratch<Args>
): TypedMethodDecorator<This, Args, Return, Utility.Method<This, Args, Return>>;
(
strings: TemplateStringsArray, ...args: Argument.MapToScratch<Args>
): TypedMethodDecorator<This, Args, Return, Utility.Method<This, Args, Return>>;

/**
*
*/
execute<
const This extends ExtensionInstance,
const Args extends any[],
const Return,
>
(
builder: (instance: This, tag: Utility.TaggedTemplate<Argument.MapToScratch<Args>, TypedMethodDecorator<This, Args, Return, Utility.Method<This, Args, Return>>>)
=> TypedMethodDecorator<This, Args, Return, Utility.Method<This, Args, Return>>
);
const This extends ExtensionInstance,
Args extends any[],
Return,
>
(
builder: (
instance: This,
tag: Utility.TaggedTemplate<Argument.MapToScratch<Args>, BlockMetadata<(...args: Args) => Return>>
) => BlockMetadata<(...args: Args) => Return>
): TypedMethodDecorator<This, Args, Return, Utility.Method<This, Args, Return>>;
}


export const scratch = {
reporter: makeDecorator("reporter"),
command: makeDecorator("command"),
}
42 changes: 21 additions & 21 deletions extensions/src/simple_example/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArgumentType, BlockType, BlockUtilityWithID, Environment, ExtensionMenuDisplayDetails, Language, Menu, SaveDataHandler, block, buttonBlock, extension, tryCastToArgumentType, untilTimePassed, reporter, command } from "$common";
import { ArgumentType, BlockType, BlockUtilityWithID, Environment, ExtensionMenuDisplayDetails, Language, Menu, SaveDataHandler, block, buttonBlock, extension, tryCastToArgumentType, untilTimePassed, scratch } from "$common";
import jibo from "./jibo.png";
import five from "./five.png";

Expand Down Expand Up @@ -55,30 +55,30 @@ export default class SimpleTypescript extends extension(details, "ui", "customSa
console.log(value);
}

@reporter`Add ${{type: "string", defaultValue: "yee"}} to ${{type: "string", defaultValue: "haw"}}: strings simple`
simpleReporterString(x: string, y: string) {
return x + y;
}
@(scratch.reporter`Add ${{ type: "string", defaultValue: "yee" }} to ${{ type: "string", defaultValue: "haw" }}: strings simple`)
simpleReporterString(x: string, y: string) {
return x + y;
}
@reporter`Add ${{type: "number", defaultValue: 3}} to ${"number"}: number simple`
simpleReporterNumber(x: number, y: number) {
return x + y;
}
@(scratch.reporter`Add ${{ type: "number", defaultValue: 3 }} to ${"number"}: number simple`)
simpleReporterNumber(x: number, y: number) {
return x + y;
}
@reporter((instance, $) => $`Add ${{type: "string", defaultValue: "oo"}} to ${{type: "string", defaultValue: "wee"}}: strings complex`)
reporterWithCallbackString(x: string, y: string) {
return x + y;
}
@(scratch.reporter((instance, $) => $`Add ${{ type: "string", defaultValue: "oo" }} to ${{ type: "string", defaultValue: "wee" }}: strings complex`))
reporterWithCallbackString(x: string, y: string) {
return x + y;
}
@reporter((self, $) => $`Add ${{type: "number", defaultValue: 3}} to ${"number"}: number complex`)
reporterWithCallbackNumber(x: number, y: number) {
return x + y;
}
@(scratch.reporter((self, $) => $`Add ${{ type: "number", defaultValue: 3 }} to ${"number"}: number complex`))
reporterWithCallbackNumber(x: number, y: number) {
return x + y;
}
@command`Hello ${{type: "string", defaultValue: "there"}}`
simpleCommand(text: string) {
console.log(text);
}
@(scratch.command`Hello ${{ type: "string", defaultValue: "there" }}`)
simpleCommand(text: string) {
console.log(text);
}
@block({
type: "command",
Expand Down

0 comments on commit 52d424f

Please sign in to comment.