Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/compiler/irgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
case 'operator_gtorequal':
return {
kind: 'op.greaterorequal',
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
case 'operator_join':
return {
kind: 'op.join',
Expand Down Expand Up @@ -515,6 +521,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
case 'operator_ltorequal':
return {
kind: 'op.lessorequal',
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
case 'operator_mathop': {
const value = this.descendInputOfBlock(block, 'NUM');
const operator = block.fields.OPERATOR.value.toLowerCase();
Expand Down Expand Up @@ -612,6 +624,12 @@ class ScriptTreeGenerator {
kind: 'op.not',
operand: this.descendInputOfBlock(block, 'OPERAND')
};
case 'operator_notequal':
return {
kind: 'op.notequal',
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
case 'operator_or':
return {
kind: 'op.or',
Expand Down
59 changes: 59 additions & 0 deletions src/compiler/jsgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,24 @@ class JSGenerator {
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`compareGreaterThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN);
}
case 'op.greaterorequal': { // Adapted from op.greater optimizations
const left = this.descendInput(node.left);
const right = this.descendInput(node.right);
// When the left operand is a number and the right operand is a number or NaN, we can use >=
if (left.isAlwaysNumber() && right.isAlwaysNumberOrNaN()) {
return new TypedInput(`(${left.asNumber()} >= ${right.asNumberOrNaN()})`, TYPE_BOOLEAN);
}
// When the left operand is a number or NaN and the right operand is a number, we can negate <
if (left.isAlwaysNumberOrNaN() && right.isAlwaysNumber()) {
return new TypedInput(`!(${left.asNumberOrNaN()} < ${right.asNumber()})`, TYPE_BOOLEAN);
}
// When either operand is known to never be a number, avoid all number parsing.
if (left.isNeverNumber() || right.isNeverNumber()) {
return new TypedInput(`(${left.asString()}.toLowerCase() >= ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN);
}
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`(!compareLessThan(${left.asUnknown()}, ${right.asUnknown()}))`, TYPE_BOOLEAN);
}
case 'op.join':
return new TypedInput(`(${this.descendInput(node.left).asString()} + ${this.descendInput(node.right).asString()})`, TYPE_STRING);
case "op.expandjoin": {
Expand Down Expand Up @@ -840,6 +858,24 @@ class JSGenerator {
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`compareLessThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN);
}
case 'op.lessorequal': { // Adapted from op.less optimizations
const left = this.descendInput(node.left);
const right = this.descendInput(node.right);
// When the left operand is a number or NaN and the right operand is a number, we can use <=
if (left.isAlwaysNumberOrNaN() && right.isAlwaysNumber()) {
return new TypedInput(`(${left.asNumberOrNaN()} <= ${right.asNumber()})`, TYPE_BOOLEAN);
}
// When the left operand is a number and the right operand is a number or NaN, we can negate >
if (left.isAlwaysNumber() && right.isAlwaysNumberOrNaN()) {
return new TypedInput(`!(${left.asNumber()} > ${right.asNumberOrNaN()})`, TYPE_BOOLEAN);
}
// When either operand is known to never be a number, avoid all number parsing.
if (left.isNeverNumber() || right.isNeverNumber()) {
return new TypedInput(`(${left.asString()}.toLowerCase() <= ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN);
}
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`(!compareGreaterThan(${left.asUnknown()}, ${right.asUnknown()}))`, TYPE_BOOLEAN);
}
case 'op.letterOf':
return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.ln':
Expand All @@ -863,6 +899,29 @@ class JSGenerator {
return new TypedInput(`(${this.descendInput(node.left).asNumber()} * ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN);
case 'op.not':
return new TypedInput(`!${this.descendInput(node.operand).asBoolean()}`, TYPE_BOOLEAN);
case 'op.notequal': { /// Adapted from op.equals optimizations
const left = this.descendInput(node.left);
const right = this.descendInput(node.right);
// When both operands are known to never be numbers, only use string comparison to avoid all number parsing.
if (left.isNeverNumber() || right.isNeverNumber()) {
return new TypedInput(`(${left.asString()}.toLowerCase() !== ${right.asString()}.toLowerCase())`, TYPE_BOOLEAN);
}
const leftAlwaysNumber = left.isAlwaysNumber();
const rightAlwaysNumber = right.isAlwaysNumber();
// When both operands are known to be numbers, we can use !==
if (leftAlwaysNumber && rightAlwaysNumber) {
return new TypedInput(`(${left.asNumber()} !== ${right.asNumber()})`, TYPE_BOOLEAN);
}
// In certain conditions, we can use !== when one of the operands is known to be a safe number.
if (leftAlwaysNumber && left instanceof ConstantInput && isSafeConstantForEqualsOptimization(left)) {
return new TypedInput(`(${left.asNumber()} !== ${right.asNumber()})`, TYPE_BOOLEAN);
}
if (rightAlwaysNumber && right instanceof ConstantInput && isSafeConstantForEqualsOptimization(right)) {
return new TypedInput(`(${left.asNumber()} !== ${right.asNumber()})`, TYPE_BOOLEAN);
}
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`(!compareEqual(${left.asUnknown()}, ${right.asUnknown()}))`, TYPE_BOOLEAN);
}
case 'op.or':
return new TypedInput(`(${this.descendInput(node.left).asBoolean()} || ${this.descendInput(node.right).asBoolean()})`, TYPE_BOOLEAN);
case 'op.random':
Expand Down
39 changes: 35 additions & 4 deletions test/fixtures/execute/tw-generate-comparison-matrix-inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,42 @@ const OPERATORS = [
opcode: 'operator_gt',
symbol: '&gt;',
execute: (a, b) => Cast.compare(a, b) > 0
}
},
{
opcode: 'operator_ltorequal',
symbol: '&lt;=',
execute: (a, b) => Cast.compare(a, b) <= 0
},
{
opcode: 'operator_notequal',
symbol: '≠',
execute: (a, b) => Cast.compare(a, b) !== 0
},
{
opcode: 'operator_gtorequal',
symbol: '&gt;=',
execute: (a, b) => Cast.compare(a, b) >= 0
},
];

const NEXT = '{{NEXT}}';
const MSG_DURATION = '2';

let result = `
<xml>
<block type="event_whenflagclicked">
<next>
<block type="looks_say">
<block type="looks_sayforsecs">
<value name="MESSAGE">
<shadow type="text">
<field name="TEXT">plan 0</field>
</shadow>
</value>
<value name="SECS">
<shadow type="math_number">
<field name="NUM">${MSG_DURATION}</field>
</shadow>
</value>
${NEXT}
</block>
</next>
Expand Down Expand Up @@ -106,12 +127,17 @@ for (const i of VALUES) {
</block>
</value>
<statement name="SUBSTACK">
<block type="looks_say">
<block type="looks_sayforsecs">
<value name="MESSAGE">
<shadow type="text">
<field name="TEXT">fail ${n}: ${i} should be ${operator.symbol} ${j}</field>
</shadow>
</value>
<value name="SECS">
<shadow type="math_number">
<field name="NUM">${MSG_DURATION}</field>
</shadow>
</value>
</block>
</statement>
${NEXT}
Expand All @@ -124,12 +150,17 @@ for (const i of VALUES) {

result = result.replace(NEXT, `
<next>
<block type="looks_say">
<block type="looks_sayforsecs">
<value name="MESSAGE">
<shadow type="text">
<field name="TEXT">end</field>
</shadow>
</value>
<value name="SECS">
<shadow type="math_number">
<field name="NUM">${MSG_DURATION}</field>
</shadow>
</value>
</block>
</next>
`);
Expand Down
Binary file not shown.