Skip to content

Commit

Permalink
Strictify toLeopard
Browse files Browse the repository at this point in the history
  • Loading branch information
adroitwhiz committed Mar 8, 2023
1 parent 33a0628 commit 3f8a884
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 37 deletions.
4 changes: 2 additions & 2 deletions src/BlockInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export interface PointTowardsTarget extends Base {

export interface RotationStyle extends Base {
type: "rotationStyle";
value: string;
value: "left-right" | "don't rotate" | "all around";
}

// Deprecated field for "align scene" block.
Expand All @@ -113,7 +113,7 @@ export interface ScrollAlignment extends Base {

export interface PenColorParam extends Base {
type: "penColorParam";
value: string;
value: "color" | "saturation" | "brightness" | "transparency";
}

export interface MusicDrum extends Base {
Expand Down
11 changes: 5 additions & 6 deletions src/Project.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Sprite, Stage } from "./Target";

import fromSb3, { fromSb3JSON } from "./io/sb3/fromSb3";
import toSb3, { ToSb3Options } from "./io/sb3/toSb3";
import toSb3 from "./io/sb3/toSb3";
import toLeopard from "./io/leopard/toLeopard";
import toScratchblocks, { ToScratchblocksOptions } from "./io/scratchblocks/toScratchblocks";
import toScratchblocks from "./io/scratchblocks/toScratchblocks";

export type TextToSpeechLanguage =
| "ar"
Expand Down Expand Up @@ -34,10 +34,9 @@ export default class Project {
public static fromSb3 = fromSb3;
public static fromSb3JSON = fromSb3JSON;

public toSb3: (options?: Partial<ToSb3Options>) => ReturnType<typeof toSb3> = toSb3.bind(null, this);
public toLeopard: typeof toLeopard = toLeopard.bind(this);
public toScratchblocks: (options?: Partial<ToScratchblocksOptions>) => ReturnType<typeof toScratchblocks> =
toScratchblocks.bind(null, this);
public toSb3 = toSb3.bind(null, this);
public toLeopard = toLeopard.bind(null, this);
public toScratchblocks = toScratchblocks.bind(null, this);

public stage: Stage = new Stage();
public sprites: Sprite[] = [];
Expand Down
75 changes: 47 additions & 28 deletions src/io/leopard/toLeopard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function uniqueNameGenerator(reservedNames: string[] | Set<string> = []) {
if (numResult === null) {
return uniqueName(name + "2");
}
return uniqueName(name.slice(0, numResult.index) + (parseInt(numResult[0], 10) + 1));
return uniqueName(name.slice(0, numResult.index) + String(parseInt(numResult[0], 10) + 1));
}
}

Expand Down Expand Up @@ -175,7 +175,7 @@ function camelCase(name: string, upper = false): string {
return result;
}

interface ToLeopardOptions {
export interface ToLeopardOptions {
leopardJSURL: string;
leopardCSSURL: string;
getTargetURL: (info: { name: string; from: "index" | "target" }) => string;
Expand All @@ -184,11 +184,10 @@ interface ToLeopardOptions {
autoplay: boolean;
}
export default function toLeopard(
options: Partial<ToLeopardOptions> = {},
project: Project,
inOptions: Partial<ToLeopardOptions> = {},
prettierConfig: prettier.Options = {}
): { [fileName: string]: string } {
const project: Project = this;

const defaultOptions: ToLeopardOptions = {
leopardJSURL: "https://unpkg.com/leopard@^1/dist/index.esm.js",
leopardCSSURL: "https://unpkg.com/leopard@^1/dist/index.min.css",
Expand All @@ -211,7 +210,7 @@ export default function toLeopard(
indexURL: "./index.js",
autoplay: true
};
options = { ...defaultOptions, ...options };
const options = { ...defaultOptions, ...inOptions };

// Sprite identifier must not conflict with module-level/global identifiers,
// imports and any others that are referenced in generated code.
Expand Down Expand Up @@ -360,7 +359,7 @@ export default function toLeopard(
for (const script of target.scripts) {
script.setName(uniqueScriptName(camelCase(script.name)));

const argNameMap = {};
const argNameMap: Record<string, string> = {};
customBlockArgNameMap.set(script, argNameMap);

// Parameter names aren't defined on a namespace at all, so must not conflict
Expand Down Expand Up @@ -395,7 +394,7 @@ export default function toLeopard(
}

function staticBlockInputToLiteral(
value: string | number | boolean | object,
value: string | number | boolean | object | null,
desiredInputShape?: InputShape
): string {
// Short-circuit for string inputs. These must never return number syntax.
Expand Down Expand Up @@ -424,7 +423,7 @@ export default function toLeopard(
return null;
}

const triggerInitStr = (name: string, options?: Partial<Record<string, string>>): string => {
const triggerInitStr = (name: string, options?: Record<string, string>): string => {
let optionsStr = "";
if (options) {
const optionValues = [];
Expand Down Expand Up @@ -488,7 +487,7 @@ export default function toLeopard(
function blockToJSWithContext(block: Block, target: Target, script?: Script): string {
return blockToJS(block);

function increase(leftSide: string, input: BlockInput.Any, allowIncrementDecrement: boolean) {
function increase(leftSide: string, input: BlockInput.Any, allowIncrementDecrement: boolean): string {
const n = parseNumber(input);
if (n === null) {
return `${leftSide} += (${inputToJS(input, InputShape.Number)});`;
Expand All @@ -500,7 +499,7 @@ export default function toLeopard(
return `${leftSide}--;`;
} else if (n >= 0) {
return `${leftSide} += ${JSON.stringify(n)};`;
} else if (n < 0) {
} else {
return `${leftSide} -= ${JSON.stringify(-n)};`;
}
}
Expand All @@ -517,7 +516,7 @@ export default function toLeopard(
return `${leftSide}++`;
} else if (n > 0) {
return `${leftSide} -= ${JSON.stringify(n)}`;
} else if (n <= 0) {
} else {
return `${leftSide} += ${JSON.stringify(-n)}`;
}
}
Expand Down Expand Up @@ -550,9 +549,9 @@ export default function toLeopard(

switch (input.type) {
case "block":
return blockToJS(input.value as Block, desiredInputShape);
return blockToJS(input.value, desiredInputShape);
case "blocks":
return input.value.map((block) => blockToJS(block as Block)).join(";\n");
return input.value?.map((block) => blockToJS(block)).join(";\n") ?? "";
default: {
return staticBlockInputToLiteral(input.value, desiredInputShape);
}
Expand All @@ -565,9 +564,10 @@ export default function toLeopard(

// If the block contains a variable or list dropdown,
// get the code to grab that variable now for convenience
let selectedVarSource: string = null;
let selectedWatcherSource: string = null;
let varInputId: string = null;
// TODO: set these to null and restructure control flow to avoid null checks
let selectedVarSource = "";
let selectedWatcherSource = "";
let varInputId: string | null = null;
if ("VARIABLE" in block.inputs) {
varInputId = (block.inputs.VARIABLE.value as { id: string }).id;
} else if ("LIST" in block.inputs) {
Expand All @@ -586,8 +586,8 @@ export default function toLeopard(

const stage = "this" + (target.isStage ? "" : ".stage");

let satisfiesInputShape: InputShape = null;
let blockSource: string = null;
let satisfiesInputShape: InputShape;
let blockSource: string;

switch (block.opcode) {
case OpCode.motion_movesteps:
Expand Down Expand Up @@ -1192,7 +1192,7 @@ export default function toLeopard(
break;

case OpCode.sensing_of: {
let propName: string;
let propName: string | null;
switch (block.inputs.PROPERTY.value) {
case "x position":
propName = "x";
Expand Down Expand Up @@ -1221,15 +1221,24 @@ export default function toLeopard(
satisfiesInputShape = InputShape.Number;
break;
case "volume":
propName = null;
propName = "audioEffects.volume";
satisfiesInputShape = InputShape.Number;
break;
default: {
let varOwner: Target = project.stage;
if (block.inputs.OBJECT.value !== "_stage_") {
varOwner = project.sprites.find((sprite) => sprite.name === targetNameMap[block.inputs.OBJECT.value]);
const sprite = project.sprites.find(
(sprite) => sprite.name === targetNameMap[block.inputs.OBJECT.value]
);
if (sprite) {
varOwner = sprite;
}
}
// "of" block gets variables by name, not ID, using lookupVariableByNameAndType in scratch-vm.
const variable = varOwner.variables.find((variable) => variable.name === block.inputs.PROPERTY.value);
if (!variable) {
throw new Error(`Variable ${block.inputs.PROPERTY.value} not found on ${varOwner.name}`);
}
const newName = variableNameMap[variable.id];
propName = `vars.${newName}`;
satisfiesInputShape = InputShape.Any;
Expand Down Expand Up @@ -1693,12 +1702,14 @@ export default function toLeopard(
satisfiesInputShape = InputShape.Stack;

// Get name of custom block script with given PROCCODE:
// TODO: what if it doesn't exist?
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const procName = target.scripts.find(
(script) =>
script.hat !== null &&
script.hat.opcode === OpCode.procedures_definition &&
script.hat.inputs.PROCCODE.value === block.inputs.PROCCODE.value
).name;
)!.name;

// TODO: Boolean inputs should provide appropriate desiredInputShape instead of "any"
const procArgs = `${block.inputs.INPUTS.value.map((input) => inputToJS(input, InputShape.Any)).join(", ")}`;
Expand All @@ -1713,21 +1724,29 @@ export default function toLeopard(
}

case OpCode.argument_reporter_string_number:
case OpCode.argument_reporter_boolean:
case OpCode.argument_reporter_boolean: {
// Argument reporters dragged outside their script return 0
if (!script) {
satisfiesInputShape = InputShape.Number;
blockSource = `0`;
break;
}
const argNames = customBlockArgNameMap.get(script);
// Procedure definition no longer exists. Return 0.
if (!argNames) {
satisfiesInputShape = InputShape.Number;
blockSource = `0`;
break;
}

if (block.opcode === OpCode.argument_reporter_boolean) {
satisfiesInputShape = InputShape.Boolean;
} else {
satisfiesInputShape = InputShape.Any;
}
blockSource = customBlockArgNameMap.get(script)[block.inputs.VALUE.value];
blockSource = argNames[block.inputs.VALUE.value];
break;
}

case OpCode.pen_clear:
satisfiesInputShape = InputShape.Stack;
Expand Down Expand Up @@ -1835,15 +1854,15 @@ export default function toLeopard(
}
}

const getPathsToRelativeOrAbsolute = (destination) => {
const fakeOrigin = "http://" + Math.random() + ".com";
const getPathsToRelativeOrAbsolute = (destination: string) => {
const fakeOrigin = `http://${Math.random()}.com`;
const isExternal = new URL(destination, fakeOrigin).origin !== fakeOrigin;
const isAbsolute = isExternal || destination.startsWith("/");

if (isAbsolute) {
return () => destination;
} else {
return ({ from }) => {
return ({ from }: { from: "index" | "target" }) => {
switch (from) {
case "index":
return "./" + destination;
Expand Down
2 changes: 1 addition & 1 deletion src/io/sb3/toSb3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface ToSb3Options {
warn: (message: string) => void;
}

interface ToSb3Output {
export interface ToSb3Output {
json: string;
}

Expand Down

0 comments on commit 3f8a884

Please sign in to comment.