diff --git a/archive/style.css b/archive/style.css
index 4fceb7a..8cf0c0d 100644
--- a/archive/style.css
+++ b/archive/style.css
@@ -430,6 +430,7 @@ main {
display: flex; /* Add this */
flex-direction: row; /* Add this */
flex-basis: 25%;
+ user-select: all;
}
.output {
diff --git a/index.html b/index.html
index 18ecaf2..72e9810 100644
--- a/index.html
+++ b/index.html
@@ -93,7 +93,7 @@
Dr. Michael Stewart
const reportButton = document.querySelector('.report-button');
reportButton.addEventListener('click', function () {
- window.open("https://docs.google.com/forms/d/e/1FAIpQLSeo2mUrrhNZejPch9UcQQDHWk5e6ql_xFfFSdS6oiaNA-Tk8Q/viewform?embedded=true", '_blank');
+ window.open("https://docs.google.com/forms/d/e/1FAIpQLSeo2mUrrhNZejPch9UcQQDHWk5e6ql_xFfFSdS6oiaNA-Tk8Q/viewform", '_blank');
})
praxlyButton.addEventListener('click', function () {
diff --git a/public/themes.css b/public/themes.css
index 0031732..4c6cfe1 100644
--- a/public/themes.css
+++ b/public/themes.css
@@ -531,6 +531,7 @@ body:not(.embed) #bottom-part {
flex-direction: row-reverse;
/* Add this */
flex-basis: 25%;
+ user-select: text;
}
.output {
@@ -554,6 +555,15 @@ body:not(.embed) #bottom-part {
color: red;
}
+.stderr a {
+ color: skyblue;
+}
+
+.stderr a:active,
+.stderr a:hover {
+ color: white;
+}
+
/* not sure where any of this goes */
.menuButtons {
diff --git a/src/ast.js b/src/ast.js
index 178298b..19b96f1 100644
--- a/src/ast.js
+++ b/src/ast.js
@@ -42,16 +42,17 @@ async function stepOver(environment, json) {
* this is meant to halt the execution wherever it is at for return statements.
*/
class ReturnException extends Error {
- constructor(errorData) {
- super(`attempting to return. this should return ${errorData}`);
+ constructor(returnValue) {
+ super(`attempting to return. this should return ${returnValue}`);
this.name = this.constructor.name;
- this.errorData = errorData;
+ this.returnValue = returnValue;
}
}
function checkArity(node, expectedArity) {
- if (node.parameters.length !== expectedArity) {
- throw new PraxlyError(`Function ${node.name} expects ${expectedArity} parameter${expectedArity === 1 ? '' : 's'}, not ${node.parameters.length}.`, node.line);
+ const actualArity = node.args.length;
+ if (actualArity !== expectedArity) {
+ throw new PraxlyError(`Function ${node.name} expects ${expectedArity} parameter${expectedArity === 1 ? '' : 's'}, not ${actualArity}.`, node.line);
}
}
@@ -64,7 +65,7 @@ function checkArity(node, expectedArity) {
export function createExecutable(tree) {
if (typeof tree === 'undefined' || typeof tree.type === 'undefined') {
if (errorOutput.length === 0) {
- defaultError("invalid program.");
+ defaultError("invalid program (abstract syntax tree is undefined)");
}
return new Praxly_invalid(tree);
}
@@ -146,32 +147,54 @@ export function createExecutable(tree) {
return new Praxly_random(tree);
} else if (tree.name === 'randomInt') {
checkArity(tree, 1);
- return new Praxly_random_int(createExecutable(tree.parameters[0]), tree);
+ return new Praxly_random_int(createExecutable(tree.args[0]), tree);
} else if (tree.name === 'randomSeed') {
checkArity(tree, 1);
- return new Praxly_random_seed(createExecutable(tree.parameters[0]), tree);
+ return new Praxly_random_seed(createExecutable(tree.args[0]), tree);
} else if (tree.name === 'int') {
checkArity(tree, 1);
- return new Praxly_int_conversion(createExecutable(tree.parameters[0]), tree);
+ return new Praxly_int_conversion(createExecutable(tree.args[0]), tree);
} else if (tree.name === 'float') {
checkArity(tree, 1);
- return new Praxly_float_conversion(createExecutable(tree.parameters[0]), tree);
+ return new Praxly_float_conversion(createExecutable(tree.args[0]), tree);
} else if (tree.name === 'min') {
- return new Praxly_min(createExecutable(tree.parameters[0]), createExecutable(tree.parameters[1]), tree);
+ checkArity(tree, 2);
+ return new Praxly_min(createExecutable(tree.args[0]), createExecutable(tree.args[1]), tree);
} else if (tree.name === 'max') {
- return new Praxly_max(createExecutable(tree.parameters[0]), createExecutable(tree.parameters[1]), tree);
+ checkArity(tree, 2);
+ return new Praxly_max(createExecutable(tree.args[0]), createExecutable(tree.args[1]), tree);
} else if (tree.name === 'abs') {
- return new Praxly_abs(createExecutable(tree.parameters[0]), tree);
+ checkArity(tree, 1);
+ return new Praxly_abs(createExecutable(tree.args[0]), tree);
} else if (tree.name === 'log') {
- return new Praxly_log(createExecutable(tree.parameters[0]), tree);
+ checkArity(tree, 1);
+ return new Praxly_log(createExecutable(tree.args[0]), tree);
} else if (tree.name == 'sqrt') {
- return new Praxly_sqrt(createExecutable(tree.parameters[0]), tree);
+ checkArity(tree, 1);
+ return new Praxly_sqrt(createExecutable(tree.args[0]), tree);
} else {
- throw new Error("unknown builtin function: " + tree.name);
+ throw Error(`unknown builtin function ${tree.name} (line ${tree.line})`);
}
}
case NODETYPES.SPECIAL_STRING_FUNCCALL:
+ switch (tree.right.name) {
+ case StringFuncs.LENGTH:
+ case StringFuncs.TOLOWERCSE:
+ case StringFuncs.TOUPPERCASE:
+ checkArity(tree.right, 0);
+ break;
+ case StringFuncs.CHARAT:
+ case StringFuncs.CONTAINS:
+ case StringFuncs.INDEXOF:
+ checkArity(tree.right, 1);
+ break;
+ case StringFuncs.SUBSTRING:
+ checkArity(tree.right, 2);
+ break;
+ default:
+ throw Error(`unknown string method ${tree.right.name} (line ${tree.line})`);
+ }
var args = [];
tree.right.args.forEach((arg) => {
args.push(createExecutable(arg));
@@ -193,7 +216,7 @@ export function createExecutable(tree) {
case NODETYPES.IF:
try {
- return new Praxly_if(createExecutable(tree.condition), createExecutable(tree.statement), tree);
+ return new Praxly_if(createExecutable(tree.condition), createExecutable(tree.codeblock), tree);
}
catch (error) {
return new Praxly_statement(null);
@@ -201,18 +224,18 @@ export function createExecutable(tree) {
case NODETYPES.IF_ELSE:
try {
- return new Praxly_if_else(createExecutable(tree.condition), createExecutable(tree.statement), createExecutable(tree.alternative), tree);
+ return new Praxly_if_else(createExecutable(tree.condition), createExecutable(tree.codeblock), createExecutable(tree.alternative), tree);
}
catch (error) {
return new Praxly_statement(null);
}
case NODETYPES.ASSIGNMENT:
+ case NODETYPES.ARRAY_REFERENCE_ASSIGNMENT:
try {
- return new Praxly_assignment(tree, createExecutable(tree.location), createExecutable(tree.value), tree);
+ return new Praxly_assignment(createExecutable(tree.location), createExecutable(tree.value), tree);
}
catch (error) {
- // console.error('assignment error: ', error);
return null;
}
@@ -226,10 +249,9 @@ export function createExecutable(tree) {
case NODETYPES.ARRAY_ASSIGNMENT:
try {
- return new Praxly_array_assignment(tree, createExecutable(tree.location), createExecutable(tree.value));
+ return new Praxly_array_assignment(createExecutable(tree.location), createExecutable(tree.value), tree);
}
catch (error) {
- // console.error('array assignment error: ', error);
return null;
}
@@ -242,7 +264,7 @@ export function createExecutable(tree) {
return new Praxly_Location(tree, index);
}
catch (error) {
- return;
+ return null;
}
case NODETYPES.FOR:
@@ -250,44 +272,40 @@ export function createExecutable(tree) {
var initialization = createExecutable(tree.initialization);
var condition = createExecutable(tree.condition);
var incrementation = createExecutable(tree.increment);
- var statement = createExecutable(tree.statement);
- return new Praxly_for(initialization, condition, incrementation, statement, tree);
+ var codeblock = createExecutable(tree.codeblock);
+ return new Praxly_for(initialization, condition, incrementation, codeblock, tree);
}
catch (error) {
- // console.error('for statement error: ', error);
return new Praxly_statement(null);
}
case NODETYPES.WHILE:
try {
var condition = createExecutable(tree.condition);
- var statement = createExecutable(tree.statement);
- return new Praxly_while(condition, statement, tree);
+ var codeblock = createExecutable(tree.codeblock);
+ return new Praxly_while(condition, codeblock, tree);
}
catch (error) {
- // console.error('while statement error: ', error);
return new Praxly_statement(null);
}
case NODETYPES.DO_WHILE:
try {
var condition = createExecutable(tree.condition);
- var statement = createExecutable(tree.statement);
- return new Praxly_do_while(condition, statement, tree);
+ var codeblock = createExecutable(tree.codeblock);
+ return new Praxly_do_while(condition, codeblock, tree);
}
catch (error) {
- // console.error('do while statement error: ', error);
return new Praxly_statement(null);
}
case NODETYPES.REPEAT_UNTIL:
try {
var condition = createExecutable(tree.condition);
- var statement = createExecutable(tree.statement);
- return new Praxly_repeat_until(condition, statement, tree);
+ var codeblock = createExecutable(tree.codeblock);
+ return new Praxly_repeat_until(condition, codeblock, tree);
}
catch (error) {
- // console.error('repeat until statement error: ', error);
return new Praxly_statement(null);
}
@@ -304,8 +322,8 @@ export function createExecutable(tree) {
return new Praxly_single_line_comment(tree.value, tree);
case NODETYPES.FUNCDECL:
- var contents = createExecutable(tree.contents);
- return new Praxly_function_declaration(tree.returnType, tree.name, tree.params, contents, tree);
+ var codeblock = createExecutable(tree.codeblock);
+ return new Praxly_function_declaration(tree.returnType, tree.name, tree.params, codeblock, tree);
case NODETYPES.FUNCCALL:
var args = [];
@@ -327,9 +345,6 @@ export function createExecutable(tree) {
case NODETYPES.ARRAY_REFERENCE:
return new Praxly_array_reference(tree.name, createExecutable(tree.index), tree);
- case NODETYPES.ARRAY_REFERENCE_ASSIGNMENT:
- return new Praxly_array_reference_assignment(tree.name, createExecutable(tree.index), createExecutable(tree.value), tree);
-
case 'INVALID':
return new Praxly_invalid(tree);
@@ -337,23 +352,7 @@ export function createExecutable(tree) {
return new Praxly_emptyLine(tree);
default:
- console.error("Unhandled node type: " + tree.type);
- return new Praxly_invalid(tree);
- }
-}
-
-class Praxly_array_reference_assignment {
-
- constructor(name, index, value, node) {
- this.json = node;
- this.name = name;
- this.value = value;
- this.index = index;
- }
-
- async evaluate(environment) {
- var index = await this.index.evaluate(environment);
- environment.variableList[this.name].elements[index.value] = await this.value.evaluate(environment);
+ throw new PraxlyError("unhandled node type " + tree.type, tree.line);
}
}
@@ -517,13 +516,14 @@ class Praxly_array_literal {
}
}
-export function valueToString(child, json) {
+export function valueToString(child, quotes, line) {
if (child === "Exit_Success") {
- throw new PraxlyError("no value returned from void procedure", json.line);
+ throw new PraxlyError("no value returned from void procedure", line);
}
var result;
if (child.jsonType === 'Praxly_array') {
- let values = child.elements.map(valueToString);
+ // always show quote marks for arrays
+ let values = child.elements.map((element) => valueToString(element, true, line));
result = '{' + values.join(", ") + '}';
} else {
result = child.value.toString();
@@ -532,6 +532,14 @@ export function valueToString(child, json) {
result += '.0';
}
}
+ if (quotes) {
+ if (child.realType === TYPES.CHAR) {
+ result = "'" + result + "'";
+ }
+ if (child.realType === TYPES.STRING) {
+ result = '"' + result + '"';
+ }
+ }
}
return result;
}
@@ -545,7 +553,7 @@ class Praxly_print {
async evaluate(environment) {
var child = await (this.expression.evaluate(environment));
- var result = valueToString(child, this.json);
+ var result = valueToString(child, false, this.json.line);
let suffix;
if (this.json.comment) {
@@ -1060,10 +1068,10 @@ class Praxly_less_than_equal {
class Praxly_if {
- constructor(condition, code, node) {
+ constructor(condition, codeblock, node) {
this.json = node;
this.condition = condition;
- this.code = code;
+ this.codeblock = codeblock;
}
async evaluate(environment) {
@@ -1072,7 +1080,7 @@ class Praxly_if {
throw new PraxlyError("Invalid condition (must be boolean)", this.json.line);
}
if (cond.value) {
- await this.code.evaluate(environment);
+ await this.codeblock.evaluate(environment);
}
return 'success';
}
@@ -1080,10 +1088,10 @@ class Praxly_if {
class Praxly_if_else {
- constructor(condition, code, alternative, node) {
+ constructor(condition, codeblock, alternative, node) {
this.json = node;
this.condition = condition;
- this.code = code;
+ this.codeblock = codeblock;
this.alternative = alternative;
}
@@ -1093,7 +1101,7 @@ class Praxly_if_else {
throw new PraxlyError("Invalid condition (must be boolean)", this.json.line);
}
if (cond.value) {
- await this.code.evaluate(environment);
+ await this.codeblock.evaluate(environment);
} else {
await this.alternative.evaluate(environment);
}
@@ -1140,6 +1148,9 @@ class Praxly_codeBlock {
// for each statement in the block
for (let i = 0; i < this.praxly_blocks.length; i++) {
const element = this.praxly_blocks[i];
+ if (element.json === undefined) {
+ throw new PraxlyError("Incomplete code (undefined)", 0); // no line number
+ }
// skip elements that have no effect
if (element.json.type == NODETYPES.NEWLINE || element.json.type === NODETYPES.COMMENT || element.json.type === NODETYPES.SINGLE_LINE_COMMENT) {
continue;
@@ -1171,7 +1182,7 @@ function accessLocation(environment, json) {
}
// converts the evaluated value to the variable's type when assigned.
-function typeCoercion(varType, praxlyObj) {
+function typeCoercion(varType, praxlyObj, line) {
if (varType === praxlyObj.realType) {
return praxlyObj;
}
@@ -1199,37 +1210,41 @@ function typeCoercion(varType, praxlyObj) {
newValue = String(praxlyObj.value);
return new Praxly_String(newValue, praxlyObj.json);
default:
- console.error("Unhandled var type: " + varType);
- return praxlyObj;
+ throw new PraxlyError("unhandled var type: " + varType, line);
}
}
class Praxly_assignment {
- constructor(json, location, expression, node) {
+ constructor(location, expression, node) {
this.json = node;
this.location = location;
this.value = expression;
}
async evaluate(environment) {
- // if it is a reassignment, the variable must be in the list and have a matching type.
- let valueEvaluated = await this.value.evaluate(environment);
+ // the variable must be in the environment and have a matching type
var storage = accessLocation(environment, this.location);
if (!storage) {
throw new PraxlyError(`Variable ${this.location.name} does not exist in this scope.`, this.json.line);
}
+ if (this.location.isArray) {
+ var index = await this.location.index.evaluate(environment);
+ var length = storage[this.location.name].elements.length;
+ if (index.value >= length) {
+ throw new PraxlyError(`Array index ${index.value} out of bounds for length ${length}`, this.json.line);
+ }
+ }
+ let valueEvaluated = await this.value.evaluate(environment);
let currentStoredVariableEvaluated = await this.location.evaluate(environment);
if (!can_assign(currentStoredVariableEvaluated.realType, valueEvaluated.realType, this.json.line)) {
throw new PraxlyError(`Error: variable reassignment does not match declared type: \n\t Expected: `
+ `${currentStoredVariableEvaluated.realType}, \n\t Actual: ${valueEvaluated.realType}`, this.json.line);
}
- // console.warn(storage);
- valueEvaluated = typeCoercion(currentStoredVariableEvaluated.realType, valueEvaluated);
+ valueEvaluated = typeCoercion(currentStoredVariableEvaluated.realType, valueEvaluated, this.json.line);
if (this.location.isArray) {
- var index = await this.location.index.evaluate(environment);
storage[this.location.name].elements[index.value] = valueEvaluated;
} else {
storage[this.location.name] = valueEvaluated;
@@ -1277,8 +1292,7 @@ class Praxly_vardecl {
valueEvaluated = new Praxly_String("");
break;
default:
- console.error("Unhandled var type: " + this.json.varType);
- break;
+ throw new PraxlyError("unhandled var type: " + this.json.varType, this.json.line);
}
}
if (environment.variableList.hasOwnProperty(this.name)) {
@@ -1288,7 +1302,7 @@ class Praxly_vardecl {
if (!can_assign(this.json.varType, valueEvaluated.realType, this.json.line)) {
throw new PraxlyError(`incompatible types: ${valueEvaluated.realType} cannot be converted to ${this.json.varType}`, this.json.line);
}
- valueEvaluated = typeCoercion(this.json.varType, valueEvaluated);
+ valueEvaluated = typeCoercion(this.json.varType, valueEvaluated, this.json.line);
environment.variableList[this.name] = valueEvaluated;
return;
@@ -1297,7 +1311,7 @@ class Praxly_vardecl {
class Praxly_array_assignment {
- constructor(json, location, expression) {
+ constructor(location, expression, json) {
this.json = json;
this.location = location;
this.value = expression;
@@ -1311,28 +1325,13 @@ class Praxly_array_assignment {
if (!can_assign(this.json.varType, valueEvaluated.elements[k].realType, this.json.line)) {
throw new PraxlyError(`at least one element in the array did not match declared type:\n\texpected type: ${this.json.varType} \n\texpression type: ${valueEvaluated.realType}`, this.json.line);
}
- valueEvaluated.elements[k] = typeCoercion(this.json.varType, valueEvaluated.elements[k]);
+ valueEvaluated.elements[k] = typeCoercion(this.json.varType, valueEvaluated.elements[k], this.json.line);
}
+ // store in current environment, because the array is being declared and initialized
environment.variableList[this.name] = valueEvaluated;
}
}
-class Praxly_variable {
-
- constructor(json, name, node) {
- this.json = node;
- this.name = name;
- }
-
- async evaluate(environment) {
- if (!environment.variableList.hasOwnProperty(this.name)) {
- throw new PraxlyError(`the variable \'${this.name}\' is not recognized by the program. \n\tPerhaps you forgot to initialize it?`, this.json.line);
- // return new Praxly_invalid(this.json);
- }
- return environment.variableList[this.name];
- }
-}
-
class Praxly_Location {
constructor(json, index) {
@@ -1349,12 +1348,12 @@ class Praxly_Location {
}
if (this.isArray) {
- var index = await this.index.evaluate(environment).value;
- if (index >= storage[this.name].elements.length) {
- throw new PraxlyError(`index ${index} out of bounds for array named ${this.name}`, this.json.line);
+ var index = await this.index.evaluate(environment);
+ var length = storage[this.name].elements.length;
+ if (index.value >= length) {
+ throw new PraxlyError(`Array index ${index.value} out of bounds for length ${length}`, this.json.line);
}
- var ind = await this.index.evaluate(environment);
- return await storage[this.name].elements[ind.value].evaluate(environment);
+ return await storage[this.name].elements[index.value].evaluate(environment);
} else {
return await storage[this.name].evaluate(environment);
}
@@ -1560,21 +1559,21 @@ class Praxly_negate {
class Praxly_invalid {
constructor() {
- this.value = 'error';
+ this.value = 'INVALID';
}
async evaluate(environment) {
- console.info(`invalid tree. Problem detected here:`);
+ throw new PraxlyError("attempting to evaluate invalid tree node");
}
}
class Praxly_function_declaration {
- constructor(returnType, name, params, contents, node) {
+ constructor(returnType, name, params, codeblock, node) {
this.returnType = returnType;
this.name = name;
this.params = params;
- this.codeblock = contents;
+ this.codeblock = codeblock;
this.json = node;
}
@@ -1582,7 +1581,7 @@ class Praxly_function_declaration {
environment.functionList[this.name] = {
returnType: this.returnType,
params: this.params,
- contents: this.codeblock,
+ codeblock: this.codeblock,
}
}
}
@@ -1608,11 +1607,11 @@ class Praxly_function_call {
//this one was tricky
async evaluate(environment) {
var func = findFunction(this.name, environment, this.json);
- var functionParams = func.params;
- var functionContents = func.contents;
+ var functionArgs = func.params;
+ var functionBody = func.codeblock;
var returnType = func.returnType;
- if (functionParams.length !== this.args.length) {
- throw new PraxlyError(`incorrect amount of arguments passed, expected ${functionParams.length}, was ${this.args.length}`, this.json.line);
+ if (functionArgs.length !== this.args.length) {
+ throw new PraxlyError(`incorrect amount of arguments passed, expected ${functionArgs.length}, was ${this.args.length}`, this.json.line);
}
//NEW: parameter list is now a linkedList. expect some errors till I fix it.
@@ -1624,8 +1623,8 @@ class Praxly_function_call {
global: environment.global,
};
for (let i = 0; i < this.args.length; i++) {
- let parameterName = functionParams[i][1];
- let parameterType = functionParams[i][0];
+ let parameterName = functionArgs[i][1];
+ let parameterType = functionArgs[i][0];
let argument = await this.args[i].evaluate(environment);
if (can_assign(parameterType, argument.realType, this.json.line)) {
@@ -1638,11 +1637,11 @@ class Praxly_function_call {
// call the user's function
let result = null;
try {
- result = await functionContents.evaluate(newScope);
+ result = await functionBody.evaluate(newScope);
}
catch (error) {
if (error instanceof ReturnException) {
- result = error.errorData;
+ result = error.returnValue;
}
else if (error instanceof RangeError) {
// most likely infinite recursion
@@ -1693,12 +1692,12 @@ class Praxly_String_funccall {
case StringFuncs.CONTAINS:
var char = await this.args[0].evaluate(environment);
this.typecheckhelper(char, [TYPES.STRING, TYPES.CHAR]);
- result = str.includes(char.value)
+ result = str.value.includes(char.value);
return new Praxly_boolean(result);
case StringFuncs.INDEXOF:
- var index = await this.args[0].evaluate(environment);
- this.typecheckhelper(char, [TYPES.CHAR]);
- result = str.value.indexOf(index.value);
+ var substr = await this.args[0].evaluate(environment);
+ this.typecheckhelper(substr, [TYPES.STRING, TYPES.CHAR]);
+ result = str.value.indexOf(substr.value);
return new Praxly_int(result);
case StringFuncs.LENGTH:
return new Praxly_int(str.value.length);
@@ -1714,7 +1713,7 @@ class Praxly_String_funccall {
result = str.value.substring(startIndex.value, endIndex.value);
return new Praxly_String(result);
default:
- throw new PraxlyError(`unrecognized function name ${this.name} for strings.`, this.json.line);
+ throw new PraxlyError("unhandled string method " + this.name, this.json.line);
}
}
@@ -1763,7 +1762,7 @@ function can_assign(varType, expressionType, line) {
} else if (varType === TYPES.CHAR) {
return expressionType === TYPES.CHAR;
} else {
- return false; // Invalid varType
+ throw Error(`unknown variable type ${varType} (line ${line})`);
}
}
@@ -1916,7 +1915,7 @@ function binop_typecheck(operation, type1, type2, json) {
return can_compare(operation, type1, type2, json);
default:
- throw new PraxlyError(`typecheck called when it shouldn't have been`, json.line);// Invalid operation
+ throw new PraxlyError("unhandled operation " + operation, json.line);
}
}
@@ -1937,9 +1936,8 @@ function litNode_new(type, value, json) {
case TYPES.SHORT:
return new Praxly_short(value);
case TYPES.INVALID:
- // console.error("Invalid literal: ", json);
return new Praxly_invalid();
default:
- throw new PraxlyError("Unknown literal type", json.line);
+ throw new PraxlyError("unhandled literal type " + type, json.line);
}
}
diff --git a/src/blocks2tree.js b/src/blocks2tree.js
index 71dec56..4d0f453 100644
--- a/src/blocks2tree.js
+++ b/src/blocks2tree.js
@@ -1,5 +1,5 @@
import Blockly from 'blockly';
-import { NODETYPES, TYPES } from './common';
+import { NODETYPES, StringFuncs, TYPES } from './common';
function containsOnlyNumbers(str) {
return /^-?\d+$/.test(str);
@@ -13,7 +13,6 @@ function isValidIdentifier(str) {
export const blocks2tree = (workspace, generator) => {
var topBlocks = workspace.getTopBlocks();
- // console.error(topBlocks);
if (topBlocks.length === 0) {
return {
type: NODETYPES.PROGRAM,
@@ -32,7 +31,7 @@ export const blocks2tree = (workspace, generator) => {
}
function customizeMaybe(block, node) {
- if (block.data) {
+ if (block?.data) {
const data = JSON.parse(block.data);
if (data.isParenthesized) {
node = {
@@ -48,21 +47,24 @@ function customizeMaybe(block, node) {
export const makeGenerator = () => {
const praxlyGenerator = [];
- praxlyGenerator['codeBlockJsonBuilder'] = (headBlock) => {
- // console.log('this is the head block');
- // console.log(headBlock);
+ praxlyGenerator[undefined] = (block) => {
+ return null; // incomplete block
+ }
+ praxlyGenerator['codeBlockJsonBuilder'] = (headBlock) => {
var codeblock = {
type: NODETYPES.CODEBLOCK,
blockID: "blocks[]",
}
var statements = [];
let currentBlock = headBlock;
- while (currentBlock.getNextBlock() != null) {
+ if (currentBlock) {
+ while (currentBlock.getNextBlock() != null) {
+ statements.push(praxlyGenerator[currentBlock.type](currentBlock));
+ currentBlock = currentBlock.getNextBlock();
+ }
statements.push(praxlyGenerator[currentBlock.type](currentBlock));
- currentBlock = currentBlock.getNextBlock();
}
- statements.push(praxlyGenerator[currentBlock.type](currentBlock));
codeblock.statements = statements;
return customizeMaybe(headBlock, codeblock);
}
@@ -85,7 +87,7 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
blockID: block.id,
type: 'PRINT',
- value: praxlyGenerator[expression.type](expression),
+ value: praxlyGenerator[expression?.type](expression),
comment: block.getCommentText(),
})
}
@@ -95,7 +97,7 @@ export const makeGenerator = () => {
name: 'random',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [],
+ args: [],
});
}
@@ -105,7 +107,7 @@ export const makeGenerator = () => {
name: 'randomInt',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -117,7 +119,7 @@ export const makeGenerator = () => {
name: 'randomSeed',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -129,7 +131,7 @@ export const makeGenerator = () => {
name: 'int',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -141,7 +143,7 @@ export const makeGenerator = () => {
name: 'float',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -154,7 +156,7 @@ export const makeGenerator = () => {
name: 'min',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
praxlyGenerator[expression2.type](expression2),
],
@@ -168,7 +170,7 @@ export const makeGenerator = () => {
name: 'max',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
praxlyGenerator[expression2.type](expression2),
],
@@ -181,7 +183,7 @@ export const makeGenerator = () => {
name: 'abs',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -193,7 +195,7 @@ export const makeGenerator = () => {
name: 'log',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -205,7 +207,7 @@ export const makeGenerator = () => {
name: 'sqrt',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [
+ args: [
praxlyGenerator[expression.type](expression),
],
});
@@ -217,7 +219,7 @@ export const makeGenerator = () => {
name: 'input',
blockID: block.id,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
- parameters: [],
+ args: [],
});
}
@@ -226,7 +228,7 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
blockID: block.id,
type: NODETYPES.STATEMENT,
- value: praxlyGenerator[expression.type](expression),
+ value: praxlyGenerator[expression?.type](expression),
})
}
@@ -241,6 +243,10 @@ export const makeGenerator = () => {
})
}
+ // NOTE: praxly_literal_block and praxly_variable_block work the same way.
+ // The only differences are the color of the outline and the error message
+ // when invalid. Ideally this code would not be duplicated.
+
praxlyGenerator['praxly_literal_block'] = (block) => {
const input = block.getFieldValue('LITERAL');
const node = {
@@ -366,8 +372,8 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.IF,
blockID: block.id,
- condition: praxlyGenerator[condition.type](condition),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements)
+ condition: praxlyGenerator[condition?.type](condition),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements)
})
}
@@ -378,8 +384,8 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.IF_ELSE,
blockID: block.id,
- condition: praxlyGenerator[condition.type](condition),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements),
+ condition: praxlyGenerator[condition?.type](condition),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements),
alternative: praxlyGenerator['codeBlockJsonBuilder'](alternative),
})
}
@@ -399,11 +405,12 @@ export const makeGenerator = () => {
})
}
+ // Note: declaration and assignment (as a statement)
praxlyGenerator['praxly_assignment_block'] = (block) => {
var varType = block.getFieldValue('VARTYPE');
var variableName = block.getFieldValue('VARIABLENAME');
var expression = block.getInputTargetBlock('EXPRESSION');
- var value = praxlyGenerator[expression.type](expression);
+ var value = praxlyGenerator[expression?.type](expression);
return customizeMaybe(block, {
type: NODETYPES.VARDECL,
name: variableName,
@@ -419,7 +426,6 @@ export const makeGenerator = () => {
praxlyGenerator['praxly_array_assignment_block'] = (block) => {
var varType = block.getFieldValue('VARTYPE');
- // console.log(`field input is ${varType}`);
var variableName = block.getFieldValue('VARIABLENAME');
var args = block.getInputTargetBlock('EXPRESSION');
var argschildren = args.getChildren(true);
@@ -447,8 +453,6 @@ export const makeGenerator = () => {
}
praxlyGenerator['praxly_reassignment_block'] = (block) => {
- var varType = block.getFieldValue('VARTYPE');
- // console.log(`field input is ${varType}`);
var variableName = block.getFieldValue('VARIABLENAME');
var expression = block.getInputTargetBlock('EXPRESSION');
var value = praxlyGenerator[expression.type](expression);
@@ -457,7 +461,10 @@ export const makeGenerator = () => {
name: variableName,
value: value,
blockID: block.id,
- varType: 'reassignment'
+ location: {
+ name: variableName,
+ type: NODETYPES.LOCATION,
+ },
})
}
@@ -465,20 +472,26 @@ export const makeGenerator = () => {
var variableName = block.getFieldValue('VARIABLENAME');
var expression = block.getInputTargetBlock('EXPRESSION');
var indexInput = block.getInputTargetBlock('INDEX');
- var value = praxlyGenerator[expression.type](expression);
- var index = praxlyGenerator[indexInput.type](indexInput);
+ var value = praxlyGenerator[expression?.type](expression);
+ var index = praxlyGenerator[indexInput?.type](indexInput);
return customizeMaybe(block, {
type: NODETYPES.ARRAY_REFERENCE_ASSIGNMENT,
name: variableName,
index: index,
value: value,
blockID: block.id,
+ location: {
+ name: variableName,
+ type: NODETYPES.LOCATION,
+ isArray: true,
+ index: index,
+ },
})
}
+ // declaration and assignment (likely in a for loop)
praxlyGenerator['praxly_assignment_expression_block'] = (block) => {
var varType = block.getFieldValue('VARTYPE');
- // console.log(`field input is ${varType}`);
var variableName = block.getFieldValue('VARIABLENAME');
var expression = block.getInputTargetBlock('EXPRESSION');
var value = praxlyGenerator[expression.type](expression);
@@ -496,8 +509,6 @@ export const makeGenerator = () => {
}
praxlyGenerator['praxly_reassignment_expression_block'] = (block) => {
- var varType = block.getFieldValue('VARTYPE');
- // console.log(`field input is ${varType}`);
var location = block.getInputTargetBlock('LOCATION');
var expression = block.getInputTargetBlock('EXPRESSION');
var loc = praxlyGenerator[location.type](location);
@@ -508,7 +519,6 @@ export const makeGenerator = () => {
location: loc,
value: value,
blockID: block.id,
- varType: varType,
})
}
@@ -518,8 +528,8 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.WHILE,
blockID: block.id,
- condition: praxlyGenerator[condition.type](condition),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements)
+ condition: praxlyGenerator[condition?.type](condition),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements)
});
}
@@ -529,8 +539,8 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.DO_WHILE,
blockID: block.id,
- condition: praxlyGenerator[condition.type](condition),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements)
+ condition: praxlyGenerator[condition?.type](condition),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements)
});
}
@@ -540,8 +550,8 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.REPEAT_UNTIL,
blockID: block.id,
- condition: praxlyGenerator[condition.type](condition),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements)
+ condition: praxlyGenerator[condition?.type](condition),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements)
});
}
@@ -550,7 +560,7 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
blockID: block.id,
type: NODETYPES.NOT,
- value: praxlyGenerator[expression.type](expression),
+ value: praxlyGenerator[expression?.type](expression),
})
}
@@ -571,16 +581,15 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
type: NODETYPES.FOR,
blockID: block.id,
- initialization: praxlyGenerator[initialization.type](initialization),
- statement: praxlyGenerator['codeBlockJsonBuilder'](statements),
- increment: praxlyGenerator[reassignment.type](reassignment),
- condition: praxlyGenerator[condition.type](condition),
+ initialization: praxlyGenerator[initialization?.type](initialization),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements),
+ increment: praxlyGenerator[reassignment?.type](reassignment),
+ condition: praxlyGenerator[condition?.type](condition),
});
}
praxlyGenerator['praxly_procedure_block'] = (block) => {
var returnType = block.getFieldValue('RETURNTYPE');
- // console.log(`field input is ${varType}`);
var args = block.getInputTargetBlock('PARAMS');
var argschildren = args.getChildren(true);
var argsList = [];
@@ -591,14 +600,14 @@ export const makeGenerator = () => {
argsList.push(param);
});
var procedureName = block.getFieldValue('PROCEDURE_NAME');
- const statements = block.getInputTargetBlock("CONTENTS");
+ const statements = block.getInputTargetBlock("CODEBLOCK");
block.setFieldValue(procedureName, 'END_PROCEDURE_NAME');
return customizeMaybe(block, {
type: NODETYPES.FUNCDECL,
name: procedureName,
params: argsList,
returnType: returnType,
- contents: praxlyGenerator['codeBlockJsonBuilder'](statements),
+ codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements),
blockID: block.id,
})
}
@@ -624,29 +633,143 @@ export const makeGenerator = () => {
return customizeMaybe(block, {
blockID: block.id,
type: NODETYPES.RETURN,
- value: praxlyGenerator[expression.type](expression),
+ value: praxlyGenerator[expression?.type](expression),
})
}
- praxlyGenerator['praxly_StringFunc_block'] = (block) => {
+ // praxlyGenerator['praxly_StringFunc_block'] = (block) => {
+ // const expression = block.getInputTargetBlock('EXPRESSION');
+ // var procedureName = block.getFieldValue('FUNCTYPE');
+ // var args = block.getInputTargetBlock('PARAMS');
+ // var argschildren = args.getChildren(true);
+ // var argsList = [];
+ // argschildren.forEach(element => {
+ // argsList.push(praxlyGenerator[element.type](element));
+ // });
+ // return customizeMaybe(block, {
+ // blockID: block.id,
+ // type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ // left: praxlyGenerator[expression.type](expression),
+ // right: {
+ // name: procedureName,
+ // args: argsList,
+ // type: NODETYPES.FUNCCALL
+ // }
+ // });
+ // }
+
+ praxlyGenerator['praxly_charAt_block'] = (block) => {
+ const procedureName = StringFuncs.CHARAT;
const expression = block.getInputTargetBlock('EXPRESSION');
- var procedureName = block.getFieldValue('FUNCTYPE');
- var args = block.getInputTargetBlock('PARAMS');
- var argschildren = args.getChildren(true);
- var argsList = [];
- argschildren.forEach(element => {
- argsList.push(praxlyGenerator[element.type](element));
+ const index = block.getInputTargetBlock('INDEX');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name : procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: [praxlyGenerator[index.type](index)],
+ type: NODETYPES.FUNCCALL
+ }
});
+ }
+
+ praxlyGenerator['praxly_contains_block'] = (block) => {
+ const procedureName = StringFuncs.CONTAINS;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ const param = block.getInputTargetBlock('PARAM');
return customizeMaybe(block, {
blockID: block.id,
+ name: procedureName,
type: NODETYPES.SPECIAL_STRING_FUNCCALL,
left: praxlyGenerator[expression.type](expression),
right: {
name: procedureName,
- args: argsList,
+ args: [praxlyGenerator[param.type](param)],
type: NODETYPES.FUNCCALL
}
- })
+ });
+ }
+
+ praxlyGenerator['praxly_indexOf_block'] = (block) => {
+ const procedureName = StringFuncs.INDEXOF;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ const param = block.getInputTargetBlock('PARAM');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name: procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: [praxlyGenerator[param.type](param)],
+ type: NODETYPES.FUNCCALL
+ }
+ });
+ }
+
+ praxlyGenerator['praxly_length_block'] = (block) => {
+ const procedureName = StringFuncs.LENGTH;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name: procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: []
+ }
+ });
+ }
+
+ praxlyGenerator['praxly_substring_block'] = (block) => {
+ const procedureName = StringFuncs.SUBSTRING;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ const param1 = block.getInputTargetBlock('PARAM1');
+ const param2 = block.getInputTargetBlock('PARAM2');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name: procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: [praxlyGenerator[param1.type](param1), praxlyGenerator[param2.type](param2)],
+ type: NODETYPES.FUNCCALL
+ }
+ });
+ }
+
+ praxlyGenerator['praxly_toLowerCase_block'] = (block) => {
+ const procedureName = StringFuncs.TOLOWERCSE;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name: procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: []
+ }
+ });
+ }
+
+ praxlyGenerator['praxly_toUpperCase_block'] = (block) => {
+ const procedureName = StringFuncs.TOUPPERCASE;
+ const expression = block.getInputTargetBlock('EXPRESSION');
+ return customizeMaybe(block, {
+ blockID: block.id,
+ name: procedureName,
+ type: NODETYPES.SPECIAL_STRING_FUNCCALL,
+ left: praxlyGenerator[expression.type](expression),
+ right: {
+ name: procedureName,
+ args: []
+ }
+ });
}
return praxlyGenerator;
diff --git a/src/common.js b/src/common.js
index 6d90a72..6d90257 100644
--- a/src/common.js
+++ b/src/common.js
@@ -44,7 +44,6 @@ export const OP = {
DIVISION: "DIVISION",
MODULUS: "MODULUS",
EXPONENTIATION: "EXPONENTIATION",
- ASSIGNMENT: "ASSIGNMENT",
EQUALITY: "EQUALITY",
INEQUALITY: "INEQUALITY",
GREATER_THAN: "GREATER THAN",
@@ -94,7 +93,7 @@ export const MAX_LOOP = 100; // prevents accidental infinite loops
// this is the special Error type that is thrown when there is in error in the IDE.
export class PraxlyError extends Error {
constructor(message, line) {
- super(`runtime error occurred on line ${line}:\n\t${message}`);
+ super("runtime error occurred on line " + line + ":
" + message);
textError("runtime", message, line);
}
}
@@ -110,14 +109,17 @@ export function textError(type, message, line) {
if (errorOutput) {
errorOutput += "
";
}
- errorOutput += `${type} error occurred on line ${line}:\n\t${message}`;
+ errorOutput += type + " error occurred on line " + line + ":
" + message;
}
export function defaultError(message) {
if (errorOutput) {
errorOutput += "
";
}
- errorOutput += `${message}
We have not written an error message for this issue yet.`;
+ errorOutput += message
+ + '
Will you please submit a '
+ + 'bug report'
+ + ' for this error?';
}
export function clearErrors() {
@@ -188,10 +190,9 @@ export function consoleInput() {
* @returns the marker id associated with the marker. This should not be needed.
*/
export function highlightAstNode(environment, node) {
- // console.log(`attempting to highlight index [${node.startIndex[0]},${ node.startIndex[1]}] to [${ node.endIndex[0]}, ${ node.endIndex[1] - 1}]`)
var session = textEditor.session;
- // var errorRange = indextoAceRange(line - 1);
+ // var errorRange = indexToAceRange(line - 1);
var Range = ace.require('ace/range').Range;
if (DEV_LOG) {
console.log(`attempting to highlight: `, node.startIndex[0], node.startIndex[1], node.endIndex[0], node.endIndex[1]);
@@ -283,5 +284,5 @@ export function comingSoon() {
}
-//this will let information that I deemed important to be logged to the console.
+// this will let information deemed important to be logged to the console
export const DEV_LOG = false;
diff --git a/src/debugger.js b/src/debugger.js
index 1838672..3c8fdc7 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -64,7 +64,6 @@ export async function generateVariableTable(environment, level) {
}
let parent = environment.parent;
const variableList = environment.variableList;
- // console.error(variableList);
for (const key in variableList) {
if (Object.hasOwnProperty.call(variableList, key)) {
const value = variableList[key];
@@ -76,10 +75,7 @@ export async function generateVariableTable(environment, level) {
typeCell.textContent = value.realType;
let valueEvaluated = await value.evaluate(environment);
- valueCell.textContent = valueToString(valueEvaluated);
- if (value.realType === "String") {
- valueCell.textContent = '"' + valueCell.textContent + '"';
- }
+ valueCell.textContent = valueToString(valueEvaluated, true, value.json?.line);
const locationCell = document.createElement("td");
locationCell.textContent = environment.name;
diff --git a/src/examples.js b/src/examples.js
index 56bbd03..e5c3a8c 100644
--- a/src/examples.js
+++ b/src/examples.js
@@ -92,7 +92,7 @@ mystery(6)
int countCharacter(String str, char targetChar)
int count ← 0
for (int i ← 0; i < str.length(); i ← i + 1)
- if (str.charAt(i) == targetChar)
+ if (str.charAt(i) == targetChar)
count ← count + 1
end if
end for
@@ -150,8 +150,8 @@ int[] myArray ← {5, 2, 9, 1, 5, 6}
void bubbleSort()
int n ← 6
for (int i ← 0; i < n - 1; i ← i + 1)
- for (int j ← 0; j < n - i - 1; j ← j + 1)
- if (myArray[j] > myArray[j + 1])
+ for (int j ← 0; j < n - i - 1; j ← j + 1)
+ if (myArray[j] > myArray[j + 1])
// Swap elements if they are in the wrong order
int temp ← myArray[j]
myArray[j] ← myArray[j + 1]
@@ -165,7 +165,7 @@ end bubbleSort
void printArray()
int n ← 6
for (int i ← 0; i < n; i ← i + 1)
- print myArray[i]
+ print myArray[i]
end for
end printArray
@@ -186,10 +186,10 @@ int[] myArray ← {5, 2, 9, 1, 5, 6}
void selectionSort()
int n ← 6
for (int i ← 0; i < n - 1; i ← i + 1)
- // Find the minimum element in the unsorted part of the array
+ // Find the minimum element in the unsorted part of the array
int minIndex ← i
for (int j ← i + 1; j < n; j ← j + 1)
- if (myArray[j] < myArray[minIndex])
+ if (myArray[j] < myArray[minIndex])
minIndex ← j
end if
end for
@@ -205,7 +205,7 @@ end selectionSort
void printArray()
int n ← 6
for (int i ← 0; i < n; i ← i + 1)
- print myArray[i]
+ print myArray[i]
end for
end printArray
diff --git a/src/main.js b/src/main.js
index b21e5be..f5a60cb 100644
--- a/src/main.js
+++ b/src/main.js
@@ -226,7 +226,6 @@ function registerListeners() {
// Prevent the default save action (e.g., opening the save dialog, reloading the page)
event.preventDefault();
runTasks(false);
- // console.log(trees);
}
});
@@ -493,9 +492,9 @@ async function runTasks(startDebug) {
// special case: abort running (not an error)
clear();
} else if (!errorOutput) {
- // error not previously handled (by PraxlyError)
- defaultError(error);
+ // error not previously handled by PraxlyError
console.error(error);
+ defaultError(error);
}
}
@@ -515,8 +514,9 @@ async function runTasks(startDebug) {
textEditor.focus();
}
+
export function turnCodeToBlocks() {
- // I need to make the listeners only be one at a time to prevent an infinite loop.
+ // only one listener at a time to prevent infinite loop
workspace.removeChangeListener(onBlocklyChange);
if (getDebugMode()) {
stopButton.click();
@@ -526,9 +526,10 @@ export function turnCodeToBlocks() {
clearErrors();
mainTree = text2tree();
if (DEV_LOG) {
- console.log(mainTree);
+ console.log("text2tree", mainTree);
}
+ // update block side to match
workspace.clear();
tree2blocks(workspace, mainTree);
workspace.render();
@@ -543,12 +544,21 @@ function onBlocklyChange(event) {
}
function turnBlocksToCode() {
+ // only one listener at a time to prevent infinite loop
textEditor.removeEventListener("input", turnCodeToBlocks);
+ if (getDebugMode()) {
+ stopButton.click();
+ }
+
+ // this is where block compiling begins
+ clearErrors();
mainTree = blocks2tree(workspace, praxlyGenerator);
if (DEV_LOG) {
- console.log(mainTree);
+ console.log("blocks2tree", mainTree);
}
- const text = tree2text(mainTree, 0, 0);
+
+ // update text side to match
+ const text = tree2text(mainTree, 0);
textEditor.setValue(text, -1);
};
diff --git a/src/newBlocks.js b/src/newBlocks.js
index e38f8e6..93fa601 100644
--- a/src/newBlocks.js
+++ b/src/newBlocks.js
@@ -145,39 +145,6 @@ export function definePraxlyBlocks(workspace) {
"tooltip": "Blank line in the code",
"helpUrl": ""
},
- { // common 6
- "type": "praxly_StringFunc_block",
- "message0": "%1.%2(%3)",
- "args0": [
- {
- "type": "input_value",
- "name": "EXPRESSION"
- },
- {
- "type": "field_dropdown",
- "name": "FUNCTYPE",
- "options": [
- ["charAt", StringFuncs.CHARAT],
- ["contains", StringFuncs.CONTAINS],
- ['indexOf', StringFuncs.INDEXOF],
- ["length", StringFuncs.LENGTH],
- ["substring", StringFuncs.SUBSTRING],
- ["toLowerCase", StringFuncs.TOLOWERCSE],
- ["toUpperCase", StringFuncs.TOUPPERCASE],
- ]
- },
- {
- "type": "input_value",
- "name": "PARAMS",
- "text": "params"
- },
- ],
- "inputsInline": true,
- "output": null,
- "style": 'other_blocks',
- "tooltip": "String methods:\ncharAt(i) - Returns the character at index i\ncontains(s) - Returns true if s is a substring\nindexOf(s) - Returns the first index of substring s, or -1 if not found\nlength() - Returns the length of the string\nsubstring(i, j) - Extracts characters from index i up to but not including index j\ntoLowerCase() - Converts the string to all lowercase\ntoUpperCase() - Converts the string to all uppercase",
- "helpUrl": ""
- },
{ // variables 1
"type": "praxly_vardecl_block",
"message0": "%1%2%3",
@@ -198,7 +165,7 @@ export function definePraxlyBlocks(workspace) {
{
"type": "field_input",
"name": "VARIABLENAME",
- "text": "VariableName"
+ "text": "variableName"
},
{
"type": "input_dummy"
@@ -231,7 +198,7 @@ export function definePraxlyBlocks(workspace) {
{
"type": "field_input",
"name": "VARIABLENAME",
- "text": "VariableName"
+ "text": "variableName"
},
{
"type": "input_value",
@@ -255,7 +222,7 @@ export function definePraxlyBlocks(workspace) {
{
"type": "field_input",
"name": "VARIABLENAME",
- "text": "VariableName"
+ "text": "variableName"
},
{
"type": "input_value",
@@ -387,7 +354,7 @@ export function definePraxlyBlocks(workspace) {
}
],
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "A literal value in the code",
"helpUrl": ""
},
@@ -441,7 +408,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Arithmetic operators:\n+ addition (and string concatenation)\n- subtraction\n* multiplication\n/ division (integer and floating-point)\n% remainder\n^ exponent",
"helpUrl": "",
},
@@ -458,7 +425,7 @@ export function definePraxlyBlocks(workspace) {
}
],
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Negates a value",
"helpUrl": ""
},
@@ -467,7 +434,7 @@ export function definePraxlyBlocks(workspace) {
"message0": "random ( )",
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Generates a random double greater than or equal to 0 and less than 1",
"helpUrl": ""
},
@@ -482,7 +449,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Generates a random integer greater than or equal to 0 and less than x",
"helpUrl": ""
},
@@ -497,7 +464,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Sets the seed of the random number generator",
"helpUrl": ""
},
@@ -512,7 +479,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Converts a String into an int",
"helpURL": ""
},
@@ -527,7 +494,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Converts a String into a float",
"helpURL": ""
},
@@ -546,7 +513,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Returns the lower value",
"helpURL": ""
},
@@ -565,7 +532,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Returns the higher value",
"helpURL": ""
},
@@ -580,7 +547,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Returns the absolute value",
"helpURL": ""
},
@@ -595,7 +562,7 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Calculates the natural logarithm",
"helpURL": ""
},
@@ -610,10 +577,173 @@ export function definePraxlyBlocks(workspace) {
],
"inputsInline": true,
"output": null,
- "style": 'expression_blocks',
+ "style": 'math_blocks',
"tooltip": "Calculates the square root",
"helpURL": ""
},
+ // { // text 1
+ // "type": "praxly_StringFunc_block",
+ // "message0": "%1.%2(%3)",
+ // "args0": [
+ // {
+ // "type": "input_value",
+ // "name": "EXPRESSION"
+ // },
+ // {
+ // "type": "field_dropdown",
+ // "name": "FUNCTYPE",
+ // "options": [
+ // ["charAt", StringFuncs.CHARAT],
+ // ["contains", StringFuncs.CONTAINS],
+ // ['indexOf', StringFuncs.INDEXOF],
+ // ["length", StringFuncs.LENGTH],
+ // ["substring", StringFuncs.SUBSTRING],
+ // ["toLowerCase", StringFuncs.TOLOWERCSE],
+ // ["toUpperCase", StringFuncs.TOUPPERCASE],
+ // ]
+ // },
+ // {
+ // "type": "input_value",
+ // "name": "PARAMS",
+ // "text": "params"
+ // },
+ // ],
+ // "inputsInline": true,
+ // "output": null,
+ // "style": 'other_blocks',
+ // "tooltip": "String methods:\ncharAt(i) - Returns the character at index i\ncontains(s) - Returns true if s is a substring\nindexOf(s) - Returns the first index of substring s, or -1 if not found\nlength() - Returns the length of the string\nsubstring(i, j) - Extracts characters from index i up to but not including index j\ntoLowerCase() - Converts the string to all lowercase\ntoUpperCase() - Converts the string to all uppercase",
+ // "helpUrl": ""
+ // },
+ { // text 1
+ "type": "praxly_charAt_block",
+ "message0": "%1.charAt (%2)",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ },
+ {
+ "type": "input_value",
+ "name": "INDEX",
+ "text": "params"
+ },
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Returns the character at the index",
+ "helpUrl": ""
+ },
+ { // text 2
+ "type": "praxly_contains_block",
+ "message0": "%1.contains (%2)",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ },
+ {
+ "type": "input_value",
+ "name": "PARAM",
+ "text": "params"
+ },
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Returns true if string is a substring",
+ "helpUrl": ""
+ },
+ { // text 3
+ "type": "praxly_indexOf_block",
+ "message0": "%1.indexOf (%2)",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ },
+ {
+ "type": "input_value",
+ "name": "PARAM",
+ "text": "params"
+ },
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Returns the first index of substring value, or -1 if not found",
+ "helpUrl": ""
+ },
+ { // text 4
+ "type": "praxly_length_block",
+ "message0": "%1.length ( )",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ },
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Returns the length of the string",
+ "helpUrl": ""
+ },
+ { // text 5
+ "type": "praxly_substring_block",
+ "message0": "%1.substring (%2 , %3)",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ },
+ {
+ "type": "input_value",
+ "name": "PARAM1",
+ "text": "params"
+ },
+ {
+ "type": "input_value",
+ "name": "PARAM2",
+ "text": "params"
+ }
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Extracts characters from the start index up to but not including the end index",
+ "helpUrl": ""
+ },
+ { // text 6
+ "type": "praxly_toLowerCase_block",
+ "message0": "%1.toLowerCase ( )",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ }
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Converts the string to all lowercase",
+ "helpUrl": ""
+ },
+ { // text 7
+ "type": "praxly_toUpperCase_block",
+ "message0": "%1.toUpperCase ( )",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "EXPRESSION"
+ }
+ ],
+ "inputsInline": true,
+ "output": null,
+ "style": 'text_blocks',
+ "tooltip": "Converts the string to all uppercase",
+ "helpUrl": ""
+ },
{ // logic 1
"type": "praxly_true_block",
"message0": "true",
@@ -999,7 +1129,7 @@ export function definePraxlyBlocks(workspace) {
},
{
"type": "input_statement",
- "name": "CONTENTS"
+ "name": "CODEBLOCK"
},
{
"type": "field_input",
diff --git a/src/text2tree.js b/src/text2tree.js
index 5e7714d..84c6a20 100644
--- a/src/text2tree.js
+++ b/src/text2tree.js
@@ -1,4 +1,4 @@
-import { MAX_LOOP, NODETYPES, OP, TYPES, indexToAceRange, textEditor, textError } from './common';
+import { MAX_LOOP, NODETYPES, OP, textEditor, textError } from './common';
/**
* this will take all of the text currently in the editor and generate the corresponding Intermediate Representation .
@@ -10,8 +10,6 @@ export function text2tree() {
let lexer = new Lexer(code);
let ir;
let tokens = lexer.lex();
- // console.info('here are the tokens:');
- // console.debug(tokens);
let parser = new Parser(tokens);
ir = parser?.parse();
return ir;
@@ -33,8 +31,9 @@ class Token {
class Lexer {
constructor(source) {
+ // insert a final newline at the end of the program if missing
if (source?.length > 0 && source[source?.length - 1] !== '\n') {
- source += "\n";
+ source += '\n';
}
this.source = source;
this.tokens = [];
@@ -43,7 +42,7 @@ class Lexer {
this.length = this.source?.length;
this.token_so_far = "";
this.multi_Char_symbols = ['>', '<', '=', '!', '-'];
- this.symbols = [",", ";", "(", ")", "{", "}", "[", "]", ".", "+", "/", "*", "%", "^", "≠", , "←", "⟵", "≥", "≤"];
+ this.symbols = [",", ";", "(", ")", "{", "}", "[", "]", ".", "+", "/", "*", "%", "^", "≠", "←", "⟵", "≥", "≤"];
this.builtins = ['input', 'random', 'randomInt', 'randomSeed', 'int', 'float', 'min', 'max', 'abs', 'log', 'sqrt'];
this.keywords = ["if", "else", "end", "print", "for", "while", 'and', 'or', 'do', 'repeat',
'until', 'not', 'return', 'null'];
@@ -124,13 +123,9 @@ class Lexer {
emit_token(type = null) {
var endIndex = this.i - this.index_before_this_line;
- // console.error(endIndex);
this.tokens.push(new Token(type ?? this.token_so_far, this.token_so_far, this.currentLine, this.startToken, [this.currentLine, endIndex]));
this.token_so_far = '';
-
- // console.error([this.currentLine, endIndex]);
this.startToken = [this.currentLine, endIndex];
- // console.error(this.startToken);
}
/**
@@ -140,6 +135,7 @@ class Lexer {
lex() {
while (this.i < this.length) {
+ // end-of-line comment
if (this.has_short_comment()) {
this.skip(2);
while (this.hasNot('\n')) {
@@ -151,55 +147,64 @@ class Lexer {
continue;
}
+ // missing code (/* comment */)
if (this.has_long_comment()) {
this.skip(2);
- while (this.hasNot('*') && this.hasNot_ahead('/')) {
+ while (this.hasNot('*') && this.hasNot_ahead('/') && this.hasNot('\n')) {
this.capture();
}
- if (this.i === this.length) {
+ if (this.hasNot('\n')) {
+ this.skip(2);
+ } else {
textError('lexing', `looks like you didn\'t close your comment. Remember comments
- start with a \'/*\' and end with a \'*/\'.`, commentStart, this.currentLine);
+ start with a \'/*\' and end with a \'*/\'.`, this.currentLine + 1);
}
- this.skip(2);
this.emit_token(NODETYPES.COMMENT);
continue;
}
+ // character literal (single quotes)
if (this.has('\'')) {
this.skip();
- if (this.has_letter && this.has_ahead('\'')) {
+ while (this.i < this.length && !this.has("\'") && !this.has("\n")) {
this.capture();
- this.skip();
+ }
+ if (!this.has("\'")) {
+ textError('lexing', 'looks like you didn\'t close your quotes on your char. \n \tRemember chars start and end with a single quote mark (\').', this.currentLine + 1);
this.emit_token(NODETYPES.CHAR);
continue;
}
- textError('lexing', 'looks like you didn\'t close your quotes on your char. \n \tRemember chars start and end with a single quote mark (\').', this.currentLine);
+ this.skip(); // close single quote
+ const length = this.token_so_far.length;
+ if (this.token_so_far.length != 1) {
+ textError('lexing', 'incorrect character length: ' + length, this.currentLine + 1);
+ }
+ this.emit_token(NODETYPES.CHAR);
+ continue;
}
+ // string literal (double quotes)
if (this.has("\"")) {
- var stringStart = this.currentLine;
this.skip();
while (this.i < this.length && !this.has("\"") && !this.has("\n")) {
this.capture();
}
- if (this.has("\"")) {
- this.skip();
+ if (!this.has("\"")) {
+ textError('lexing', 'looks like you didn\'t close your quotes on your String. \n \tRemember Strings start and end with a double quote mark (\").', this.currentLine + 1);
this.emit_token(NODETYPES.STRING);
continue;
}
- textError('lexing', 'looks like you didn\'t close your quotes on your String. \n \tRemember Strings start and end with a double quote mark (\").', stringStart);
- this.i -= 1;
- this.token_so_far = "";
+ this.skip(); // close double quote
this.emit_token(NODETYPES.STRING);
continue;
}
+ // operators and punctuation
if (this.has_valid_symbol()) {
this.capture();
this.emit_token();
continue;
}
-
if (this.has_multi_char_symbol()) {
while (this.has_multi_char_symbol()) {
this.capture();
@@ -208,6 +213,7 @@ class Lexer {
continue;
}
+ // integers and floating-points
if (this.has_digit()) {
while (this.i < this.length && this.has_digit()) {
this.capture();
@@ -223,10 +229,14 @@ class Lexer {
this.emit_token(NODETYPES.INT);
continue;
}
+
+ // ignore spaces
if (this.has(' ')) {
this.skip();
continue;
}
+
+ // newline (increment line number)
if (this.has("\n")) {
this.capture();
this.emit_token("\n");
@@ -235,13 +245,18 @@ class Lexer {
this.startToken = [this.currentLine, this.i - this.index_before_this_line];
continue;
}
+
+ // get the next word
if (!this.has_letter()) {
- textError('lexing', `unrecognized character \"${this.source[this.i]}\"`, this.i, this.i + 1);
- break;
+ textError('lexing', `unrecognized character \"${this.source[this.i]}\"`, this.currentLine + 1);
+ this.skip();
+ continue;
}
while (this.i < this.length && (this.has_letter() || this.has_digit())) {
this.capture();
}
+
+ // built-in types and functions
if (this.has_type()) {
this.emit_token('Type');
continue;
@@ -250,59 +265,84 @@ class Lexer {
this.emit_token(NODETYPES.BUILTIN_FUNCTION_CALL);
continue;
}
+
+ // end keyword (if, for, while, proc)
if (this.token_so_far === 'end') {
- while (this.hasNot('\n')) {
+ if (this.has(' ')) {
+ this.capture();
+ } else {
+ textError('lexing', 'missing space after end keyword', this.currentLine + 1);
+ }
+ while (this.has(' ')) { // ignore extra spaces
+ this.skip();
+ }
+ while (this.has_letter()) {
this.capture();
}
this.emit_token();
continue;
}
+
+ // true/false literal
if (this.has_boolean()) {
this.emit_token(NODETYPES.BOOLEAN);
continue;
}
+
+ // other keywords
if (this.has_keyword()) {
this.emit_token();
continue;
}
+
+ // variable/procedure names
this.emit_token('Location');
}
+
+ // That's all folks!
this.emit_token("EOF");
return this.tokens;
}
}
+
class Parser {
constructor(tokens) {
- this.statements = [];
this.tokens = tokens;
this.i = 0;
this.length = tokens?.length;
- this.eof = false;
- this.keywords = ["if", "else", "then", "done"];
- this.statementKeywords = ['if', 'print', 'for', 'while'];
- this.specialStringFunctionKEywords = ["charAt", "contains", "indexOf", "length", "substring", "toLowerCase", "toUpperCase"];
- }
-
- hasNot(type) {
- return this.i < this.length && this.tokens[this.i].token_type !== type;
}
getCurrentToken() {
return this.tokens[this.i];
}
+ getCurrentLine() {
+ return this.tokens[this.i].line;
+ }
+
+ getCurrentValue() {
+ return this.tokens[this.i].value;
+ }
+
+ getPrevTokenType() {
+ return this.tokens[this.i - 1].token_type;
+ }
+
has(type) {
return this.i < this.length && this.tokens[this.i].token_type === type;
}
+ hasNot(type) {
+ return this.i < this.length && this.tokens[this.i].token_type !== type;
+ }
+
hasAny() {
var types = Array.prototype.slice.call(arguments);
return this.i < this.length && types.includes(this.tokens[this.i].token_type);
}
-
hasNotAny() {
var types = Array.prototype.slice.call(arguments);
return this.i < this.length && !types.includes(this.tokens[this.i].token_type);
@@ -320,18 +360,11 @@ class Parser {
return this.i < this.length && this.tokens[this.i].token_type === 'Type';
}
- has_keyword() {
- return this.keywords.includes(this.tokens[this.i].token_type);
- }
- has_statementKeyword() {
- return this.statementKeywords.includes(this.tokens[this.i].token_type);
- }
-
match_and_discard_next_token(type) {
if (this.tokens[this.i].token_type === type) {
this.advance();
} else {
- textError('parsing', `did not detect desired token at this location. \nexpected: \'${type}\'\n but was: ${this.tokens[this.i].token_type}`, this.tokens[this.i].line);
+ textError('parsing', `did not detect desired token at this location. \nexpected: \'${type}\'\n but was: ${this.tokens[this.i].token_type}`, this.getCurrentLine());
}
}
@@ -347,8 +380,6 @@ class Parser {
return this.parse_program();
}
-
-
/**
* This function creates new nodes for the AST for any binary operation
* @param {*} operation the operation symbol
@@ -414,7 +445,7 @@ class Parser {
type = OP.OR;
break;
default:
- // handle unknown type
+ textError('parsing', 'invalid operator ' + operation, line);
break;
}
return {
@@ -423,13 +454,11 @@ class Parser {
left: l,
right: r,
type: type,
- startIndex: l.startIndex,
- endIndex: r.endIndex,
+ startIndex: l?.startIndex,
+ endIndex: r?.endIndex,
}
}
-
-
/**
* Creates a new node for literal values in the AST.
* @param {*} token
@@ -457,7 +486,6 @@ class Parser {
unaryOPNode_new(operation, expression, line, startIndex) {
var type;
switch (operation) {
- case '!':
case 'not':
type = OP.NOT;
break;
@@ -471,7 +499,7 @@ class Parser {
value: expression,
type: type,
startIndex: startIndex,
- endIndex: expression.endIndex,
+ endIndex: expression?.endIndex,
}
}
@@ -482,92 +510,103 @@ class Parser {
*/
parse_expression(precedence = 9) {
let operation = this.getCurrentToken().token_type;
- let line = this.tokens[this.i].line;
+ let line = this.getCurrentLine();
let startIndex = this.getCurrentToken().startIndex;
- let endIndex = this.getCurrentToken().endIndex;
switch (precedence) {
+
+ // or logical operator
case 9:
var l = this.parse_expression(precedence - 1);
while (this.has("or")) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+
+ // and logical operator
case 8:
var l = this.parse_expression(precedence - 1);
while (this.has("and")) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+ // relational operators
case 7:
var l = this.parse_expression(precedence - 1);
while (this.hasAny('<', '>', '==', '!=', '>=', '<=', '≠', '≥', '≤')) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+
+ // addition, subtraction
case 6:
var l = this.parse_expression(precedence - 1);
while (this.hasAny('+', '-')) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+
+ // multiplication, division, modulo
case 5:
var l = this.parse_expression(precedence - 1);
while (this.hasAny('*', '/', '%')) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+
+ // raise to the power
case 4:
var l = this.parse_expression(precedence - 1);
while (this.hasAny('^')) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
- const r = this.parse_expression(precedence);
+ const r = this.parse_expression(precedence - 1);
l = this.binaryOpNode_new(operation, l, r, line);
}
return l;
+ // not logical operator, negation operator
case 3:
- if (this.hasNotAny('not', '!', '-')) {
+ if (this.hasNotAny('not', '-')) {
return this.parse_expression(precedence - 1);
}
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
var exp = this.parse_expression(precedence - 1);
return this.unaryOPNode_new(operation, exp, line, startIndex);
- // This is the dot operator
+ // dot operator (for string methods)
case 2:
var l = this.parse_expression(precedence - 1);
while (this.hasAny('.')) {
operation = this.getCurrentToken().token_type;
- line = this.getCurrentToken().line;
+ line = this.getCurrentLine();
this.advance();
- const r = this.parse_expression(precedence);
+ const r = this.parse_expression(precedence - 1);
if (r.type != NODETYPES.FUNCCALL) {
- textError('parsing', "classes are not fully supported yet. the right side of the . operator must be a supported string function", line);
+ textError('parsing', "classes are not fully supported yet. the right side of the dot operator must be a supported string function", line);
}
l = {
left: l,
@@ -579,13 +618,13 @@ class Parser {
}
return l;
- //This one gets really complicated
+ // This one gets really complicated
case 1:
switch (operation) {
case 'EOF':
- this.eof = true;
return 'EOF';
+ // literal value
case NODETYPES.BOOLEAN:
case NODETYPES.CHAR:
case NODETYPES.DOUBLE:
@@ -596,13 +635,10 @@ class Parser {
this.advance();
return this.literalNode_new(this.tokens[this.i - 1]);
- case NODETYPES.BUILTIN_FUNCTION_CALL:
- case 'Type': // type conversion function
- return this.parse_builtin_function_call(line);
-
+ // parentheses
case '(':
const leftToken = this.advance();
- const expression = this.parse_expression(9);
+ const expression = this.parse_expression();
if (this.has(")")) {
const rightToken = this.advance();
return {
@@ -614,10 +650,10 @@ class Parser {
endIndex: rightToken.endIndex,
};
} else {
- textError('parsing', 'did not detect closing parentheses', line,);
+ textError('parsing', 'did not detect closing parentheses', line);
}
- //ah yes, array literals....very fun
+ // ah yes, array literals....very fun
case '{':
let result = {
blockID: 'code',
@@ -630,7 +666,7 @@ class Parser {
this.advance();
var loopBreak = 0;
while (this.hasNot('}') && loopBreak < MAX_LOOP) {
- var param = this.parse_expression(9);
+ var param = this.parse_expression();
args.push(param);
if (this.has(',')) {
this.advance();
@@ -639,19 +675,30 @@ class Parser {
}
result.params = args;
if (this.hasNot('}')) {
- textError('parsing', "didn't detect closing curly brace in the array declaration", this.tokens[this.i].line);
+ textError('parsing', "didn't detect closing curly brace in the array declaration", this.getCurrentLine());
}
result.endIndex = this.getCurrentToken().endIndex;
this.advance();
return result;
+ // built-in function call
+ case NODETYPES.BUILTIN_FUNCTION_CALL:
+ case 'Type': // type conversion function
+ if (this.has_ahead('(')) {
+ return this.parse_builtin_function_call(line);
+ }
+ else {
+ // parse as 'Location' instead (Ex: variable named max)
+ }
+
+ // variable assignment or procedure call
case 'Location':
var l = this.parse_location();
if (this.hasAny('=', '<-', "←", "⟵")) {
this.advance();
- var value = this.parse_expression(9);
+ var value = this.parse_expression();
l = {
- type: NODETYPES.ASSIGNMENT,
+ type: l.isArray ? NODETYPES.ARRAY_REFERENCE_ASSIGNMENT : NODETYPES.ASSIGNMENT,
blockID: "code",
line: line,
location: l,
@@ -663,7 +710,7 @@ class Parser {
var args = [];
var loopBreak = 0;
while (this.hasNot(')') && loopBreak < MAX_LOOP) {
- var param = this.parse_expression(9);
+ var param = this.parse_expression();
args.push(param);
if (this.has(',')) {
this.advance();
@@ -680,21 +727,20 @@ class Parser {
args: args,
startIndex: startIndex,
}
- // this is used to give different behavior to these functions in particular
- // if (this.specialStringFunctionKEywords.includes(l.name)){
- // l.type = NODETYPES.SPECIAL_STRING_FUNCCALL;
- // }
}
l.endIndex = this.getCurrentToken().endIndex;
return l;
default:
- // TODO: this case needs to raise an exception or return some error
- // object. Right now if an expression can't be parsed, it
- // implicitly returns null/undefined.
- textError('parsing', `invalid Token ${this.getCurrentToken().value}`, line);
- }
- }
+ if (this.getCurrentValue() != '\n') {
+ textError('parsing', `invalid token ${this.getCurrentValue()}`, line);
+ this.advance();
+ } else {
+ textError('parsing', `invalid end of line`, line);
+ }
+
+ } // switch (operation)
+ } // switch (precedence)
}
parse_builtin_function_call(line) {
@@ -703,15 +749,15 @@ class Parser {
if (this.has('(')) {
this.advance();
- // Expect 0 or more parameters.
- const parameters = [];
+ // Expect 0 or more arguments.
+ const args = [];
if (this.hasNot(')')) {
- const parameter = this.parse_expression(9);
- parameters.push(parameter);
+ const parameter = this.parse_expression();
+ args.push(parameter);
while (this.has(',')) {
this.advance();
- const parameter = this.parse_expression(9);
- parameters.push(parameter);
+ const parameter = this.parse_expression();
+ args.push(parameter);
}
}
@@ -721,7 +767,7 @@ class Parser {
blockID: "code",
name: nameToken.value,
line,
- parameters,
+ args,
type: NODETYPES.BUILTIN_FUNCTION_CALL,
startIndex: nameToken.startIndex,
endIndex: rightParenthesisToken.endIndex,
@@ -730,17 +776,20 @@ class Parser {
textError('parsing', 'did not detect right parenthesis', line);
}
} else {
- textError('parsing', 'did not detect left parenthesis', line);
+ textError('parsing', `missing parentheses for built-in ${nameToken.value}() function`, line);
}
}
parse_location() {
+ if (this.getCurrentValue() == '\n') {
+ return; // incomplete line
+ }
var result = {
type: NODETYPES.LOCATION,
- name: this.tokens[this.i].value,
+ name: this.getCurrentValue(),
isArray: false,
blockID: 'code',
- line: this.tokens[this.i].line,
+ line: this.getCurrentLine(),
index: null,
startIndex: this.tokens[this.i].startIndex,
}
@@ -757,9 +806,11 @@ class Parser {
// this one kinda a mess ngl, but my goal is to support variable initialization and assignment all together
parse_funcdecl_or_vardecl() {
+
+ // return type
var isArray = false;
var type = NODETYPES.VARDECL;
- var vartype = this.tokens[this.i].value;
+ var vartype = this.getCurrentToken().value;
var startIndex = this.getCurrentToken().startIndex;
this.advance();
if (this.has('[') && this.has_ahead(']')) {
@@ -769,37 +820,42 @@ class Parser {
isArray = true;
}
+ // variable/procedure name
var location = this.parse_location();
var result = {
type: type,
varType: vartype,
- name: location.name,
+ name: location?.name,
isArray: isArray,
blockID: 'code',
location: location,
- line: this.tokens[this.i].line,
+ line: this.getCurrentLine(),
index: null,
startIndex: startIndex,
}
+
+ // initialization (optional)
if (this.hasAny('=', '<-', "←", "⟵")) {
this.advance();
- result.value = this.parse_expression(9);
- if (this.has(';')) {
- this.advance();
- }
- } else if (this.has('(')) {
+ result.value = this.parse_expression();
+ }
+
+ // procedure definition
+ else if (this.has('(')) {
result.type = NODETYPES.FUNCDECL;
result.returnType = vartype;
if (type == NODETYPES.ARRAY_ASSIGNMENT) {
result.returnType += "[]";
}
+
+ // parameter list
this.match_and_discard_next_token('(');
var params = [];
var stopLoop = 0;
while (this.hasNot(')') && stopLoop < MAX_LOOP) {
var param = [];
if (this.has_type()) {
- param.push(this.tokens[this.i].value);
+ param.push(this.getCurrentValue());
this.advance();
}
if (this.has('[') && this.has_ahead(']')) {
@@ -808,7 +864,7 @@ class Parser {
param[0] += "[]";
}
if (this.has('Location')) {
- param.push(this.tokens[this.i].value);
+ param.push(this.getCurrentValue());
this.advance();
}
params.push(param);
@@ -817,34 +873,18 @@ class Parser {
}
stopLoop += 1;
}
-
-
result.endIndex = this.getCurrentToken().endIndex;
this.match_and_discard_next_token(')');
result.params = params;
- if (this.has(';')) {
- this.advance();
- }
- if (this.has('\n')) {
- this.advance();
- }
-
- var contents = this.parse_block('end ' + result.name);
- result.contents = contents;
- if (this.hasNot('end ' + result.name)) {
- textError('parsing', `missing the \'end ${result.name}\' token`, result.line);
- result.endIndex = this.getCurrentToken().endIndex;
- return result;
- }
- this.advance();
+ // procedure body
+ result.codeblock = this.parse_block('end ' + result.name);
}
+
result.endIndex = this.getCurrentToken().endIndex;
return result;
}
-
-
parse_program() {
return {
type: "PROGRAM",
@@ -854,110 +894,131 @@ class Parser {
}
/**
- * parses a block of statements (think curlybraces)
+ * parses a block of statements (think curly braces)
* @param {...any} endToken any tokens that will determine the end of the block.
* @returns
*/
parse_block(...endToken) {
- let praxly_blocks = [];
- while (this.hasNotAny(...endToken)) {
+ let block_statements = [];
+
+ // parse end of line at beginning of the block
+ if (this.i > 0) {
+ let comment = this.parse_end_of_line(true);
+ if (comment) {
+ // move trailing comment into the new block
+ block_statements.push(comment);
+ }
+ }
- if (this.has('EOF')) {
- break;
+ // parse all statements inside the block
+ while (!this.has('EOF') && this.hasNotAny(...endToken)) {
+ let stmt = this.parse_statement();
+ let comment = this.parse_end_of_line(false);
+ if (stmt?.codeblock) {
+ // move trailing comment after the block end
+ block_statements.push(stmt);
+ if (comment) {
+ block_statements.push(comment);
+ }
+ } else {
+ // move trailing comment above the statement
+ if (comment) {
+ block_statements.push(comment);
+ }
+ block_statements.push(stmt);
}
- praxly_blocks.push(this.parse_statement());
- // note: I for some reason always assumed that statements will not parse the final token, so I always did it here.
- // I think its because I assumed that all statements end with a newline.
+ }
+
+ // make sure the block is correctly terminated
+ if (this.hasAny(...endToken)) {
this.advance();
+ } else {
+ let name = endToken[endToken.length - 1];
+ textError('parsing', `missing the '${name}' token`, this.getCurrentLine());
}
+
+ // return the resulting block of statements
return {
type: NODETYPES.CODEBLOCK,
- statements: praxly_blocks,
+ statements: block_statements,
blockID: "code"
}
}
parse_statement() {
- var line = this.tokens[this.i].line;
+ var line = this.getCurrentLine();
let result = {
startIndex: this.getCurrentToken().startIndex,
endIndex: this.getCurrentToken().endIndex,
blockID: 'code',
line: line,
};
+
+ // blank line (no statement)
if (this.has('\n')) {
result.type = NODETYPES.NEWLINE;
return result;
}
- if (this.has("if")) {
+ // if or if-else
+ else if (this.has("if")) {
result.type = NODETYPES.IF;
this.advance();
if (this.hasNot('(')) {
return result;
}
this.advance();
- result.condition = this.parse_expression(9);
+ result.condition = this.parse_expression();
result.endIndex = this.getCurrentToken().endIndex;
if (this.hasNot(')')) {
return result;
}
this.advance();
- if (this.has('\n')) {
- this.advance();
- result.statement = this.parse_block('else', 'end if');
- if (this.has('else')) {
- this.advance();
- if (this.has('\n')) {
- this.advance();
- }
- result.type = NODETYPES.IF_ELSE;
- result.alternative = this.parse_block('end if');
- }
- if (this.has('end if')) {
- this.advance();
- return result;
- }
- else {
- textError('parsing', "missing the \'end if\' token", result.line);
- }
+
+ result.codeblock = this.parse_block('else', 'end if');
+ if (this.getPrevTokenType() == 'else') {
+ result.type = NODETYPES.IF_ELSE;
+ result.alternative = this.parse_block('end if');
}
+ }
- } else if (this.has('for')) {
+ // for loop
+ else if (this.has('for')) {
result.type = NODETYPES.FOR;
this.advance();
if (this.hasNot('(')) {
return result;
}
this.advance();
- result.initialization = this.parse_statement();
- result.initialization.endIndex[1] -= 3; // HACK
- result.condition = this.parse_expression(9);
- if (this.has(';')) {
- this.advance();
- result.increment = this.parse_expression(1);
- if (this.hasNot(')')) {
- return result;
- }
- this.advance();
- if (this.has('\n')) {
- result.endIndex = this.getCurrentToken().endIndex;
- this.advance();
- result.statement = this.parse_block('end for');
- if (this.has('end for')) {
- this.advance();
- return result;
- }
- else {
- textError('parsing', "missing the \'end for\' token", result.line);
- }
- }
+ if (this.has_type()) {
+ result.initialization = this.parse_funcdecl_or_vardecl();
+ } else {
+ result.initialization = this.parse_expression(1);
+ }
+ if (this.hasNot(';')) { // 1st required
+ return result;
}
- console.log(`parser messing up, current token is ${this.tokens[this.i].token_type}`);
+ this.advance();
+
+ result.condition = this.parse_expression();
+ if (this.hasNot(';')) { // 2nd required
+ return result;
+ }
+ this.advance();
+
+ result.increment = this.parse_expression(1);
+ if (this.hasNot(')')) {
+ return result;
+ }
+ this.advance();
+
+ result.endIndex = this.getCurrentToken().endIndex;
+ result.codeblock = this.parse_block('end for');
return result;
}
+ // while loop
else if (this.has('while')) {
result.type = NODETYPES.WHILE;
this.advance();
@@ -965,74 +1026,56 @@ class Parser {
return result;
}
this.advance();
- result.condition = this.parse_expression(9);
+ result.condition = this.parse_expression();
result.endIndex = this.getCurrentToken().endIndex;
if (this.hasNot(')')) {
return result;
}
this.advance();
- if (this.has('\n')) {
- this.advance();
- result.statement = this.parse_block('end while');
- }
- if (this.has('end while')) {
- this.advance();
- return result;
- } else {
- textError('parsing', "missing the \'end while\' token", result.line);
- }
+ result.codeblock = this.parse_block('end while');
}
+ // do-while loop
else if (this.has('do')) {
result.type = NODETYPES.DO_WHILE;
this.advance();
- if (this.has('\n')) {
- this.advance();
- result.statement = this.parse_block('while');
+ result.codeblock = this.parse_block('while');
+ if (this.hasNot('(')) {
+ return result;
}
- if (this.has('while')) {
- this.advance();
- if (this.hasNot('(')) {
- return result;
- }
- this.advance();
- result.condition = this.parse_expression(9);
- if (this.hasNot(')')) {
- return result;
- }
- this.advance();
+ this.advance();
+ result.condition = this.parse_expression();
+ if (this.hasNot(')')) {
return result;
}
+ this.advance();
+ return result;
}
+ // repeat-until loop
else if (this.has('repeat')) {
result.type = NODETYPES.REPEAT_UNTIL;
this.advance();
- if (this.has('\n')) {
- this.advance();
- result.statement = this.parse_block('until');
+ result.codeblock = this.parse_block('until');
+ if (this.hasNot('(')) {
+ return result;
}
- if (this.has('until')) {
- this.advance();
- if (this.hasNot('(')) {
- return result;
- }
- this.advance();
- result.condition = this.parse_expression(9);
- if (this.hasNot(')')) {
- return result;
- }
- this.advance();
+ this.advance();
+ result.condition = this.parse_expression();
+ if (this.hasNot(')')) {
return result;
}
+ this.advance();
+ return result;
}
+ // print statement
else if (this.has("print")) {
this.advance();
- const expression = this.parse_expression(9);
+ const expression = this.parse_expression();
result.endIndex = expression?.endIndex ?? this.getCurrentToken().endIndex;
- if (this.has(';')) {
+ while (this.has(';')) { // special case: bundle comment with print node
this.advance();
}
@@ -1048,31 +1091,32 @@ class Parser {
return result;
}
+ // return statement
else if (this.has("return")) {
this.advance();
- const expression = this.parse_expression(9);
- if (this.has(';')) {
- this.advance();
- }
- if (this.has('\n')) {
- result.type = NODETYPES.RETURN;
- result.value = expression;
- return result;
- }
+ const expression = this.parse_expression();
+ result.type = NODETYPES.RETURN;
+ result.value = expression;
+ return result;
+ }
- } else if (this.has(NODETYPES.COMMENT)) {
+ // missing code (/* comment */)
+ else if (this.has(NODETYPES.COMMENT)) {
const token = this.advance();
result.type = NODETYPES.COMMENT;
result.value = token.value;
return result;
+ }
- } else if (this.has(NODETYPES.SINGLE_LINE_COMMENT)) {
+ // end-of-line comment
+ else if (this.has(NODETYPES.SINGLE_LINE_COMMENT)) {
const token = this.advance();
result.type = NODETYPES.SINGLE_LINE_COMMENT;
result.value = token.value;
return result;
}
+ // variable/function declaration
else if (this.has_type()) {
if (this.hasNot_ahead('(')) {
return this.parse_funcdecl_or_vardecl();
@@ -1089,24 +1133,13 @@ class Parser {
}
}
- else if (this.has('\n')) {
- return {
- type: NODETYPES.NEWLINE,
- blockID: 'code',
- }
- }
-
+ // most likely a function call
else {
- // this is a stand alone expression as a statement.
- let contents = this.parse_expression(9);
-
- // if (!contents?.startIndex ?? false){
- // console.error(this.getCurrentToken());
- // console.error(this.tokens[this.i + 1]);
- // }
-
- if (this.has(';')) {
- this.advance();
+ let contents = this.parse_expression();
+ // special case: assume that assignment is a statement
+ // if in a for loop, later code will rebuild the object
+ if (contents?.type.endsWith("ASSIGNMENT")) {
+ return contents;
}
result = {
type: NODETYPES.STATEMENT,
@@ -1118,4 +1151,48 @@ class Parser {
}
return result;
}
+
+ /**
+ * Called at the expected end of each line to advance the parser to
+ * the next line. Parses any semicolons, optional comments, and the
+ * newline character.
+ *
+ * @param {boolean} new_block true if starting a new block (no semicolons)
+ */
+ parse_end_of_line(new_block) {
+
+ // optional semicolons
+ if (new_block && this.has(';')) {
+ textError('parsing', "semicolon not allowed here", this.getCurrentLine());
+ }
+ while (this.has(';')) {
+ this.advance();
+ }
+
+ // optional end-of-line comment
+ let comment;
+ if (this.has(NODETYPES.SINGLE_LINE_COMMENT) || this.has(NODETYPES.COMMENT)) {
+ const token = this.advance();
+ comment = {
+ type: this.getPrevTokenType(),
+ value: token.value,
+ startIndex: token.startIndex,
+ endIndex: token.endIndex,
+ blockID: 'code',
+ line: token.line,
+ };
+ }
+
+ // required newline character
+ if (this.has('\n')) {
+ this.advance();
+ } else if (!this.has('EOF')) {
+ textError('parsing', "expected end of line", this.getCurrentLine())
+ while (!this.has('\n')) {
+ this.advance();
+ }
+ this.advance();
+ }
+ return comment;
+ }
}
diff --git a/src/theme.js b/src/theme.js
index a003568..92d7204 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -63,13 +63,13 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', {
'base': Blockly.Themes.Classic,
'blockStyles': {
'loop_blocks': {
- 'colourPrimary': '#0361FF'
+ 'colourPrimary': '#395BBF'
},
'array_blocks': {
'colourPrimary': '#FA0000'
},
'logic_blocks': {
- 'colourPrimary': '#00D084'
+ 'colourPrimary': '#3CB371'
},
'class_blocks': {
'colourPrimary': '#6381fe'
@@ -78,19 +78,22 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', {
'colourPrimary': '#808080'
},
'procedure_blocks': {
- 'colourPrimary': '#6381fe'
+ 'colourPrimary': '#9370DB'
},
'variable_blocks': {
'colourPrimary': '#f80069'
},
- 'expression_blocks': {
- 'colourPrimary': '#a7ca00'
+ 'math_blocks': {
+ 'colourPrimary': '#FF9966'
},
'parameter_blocks': {
'colourPrimary': '#8F48B7'
},
'other_blocks': {
'colourPrimary': '#00BFFF'
+ },
+ 'text_blocks': {
+ 'colourPrimary': '#FFC050',
}
},
'categoryStyles': {
@@ -98,10 +101,10 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', {
'colour': '#395BBF'
},
'procedure_blocks': {
- 'colour': '#6381fe'
+ 'colour': '#9370DB'
},
'logic_blocks': {
- 'colour': '#00D084'
+ 'colour': '#3CB371'
},
'class_blocks': {
'colour': '#6381fe'
@@ -115,8 +118,11 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', {
'variable_blocks': {
'colour': '#f80069'
},
- 'expression_blocks': {
- 'colour': '#a7ca00'
+ 'math_blocks': {
+ 'colour': '#FF9966'
+ },
+ 'text_blocks': {
+ 'colour': '#FFC050'
}
},
'componentStyles': {
diff --git a/src/toolbox.js b/src/toolbox.js
index 86b4759..7d78699 100644
--- a/src/toolbox.js
+++ b/src/toolbox.js
@@ -17,7 +17,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': '\"hello, world\"',
+ 'LITERAL': '\"Hello, World!\"',
}
},
},
@@ -39,25 +39,6 @@ export const toolbox = {
'kind': 'block',
'type': 'praxly_emptyline_block'
},
- {
- 'kind': 'block',
- 'type': 'praxly_StringFunc_block',
- 'inputs': {
- 'EXPRESSION': {
- 'shadow': {
- 'type': 'praxly_literal_block',
- 'fields': {
- 'LITERAL': '\"hello, world\"',
- }
- },
- },
- 'PARAMS': {
- 'block': {
- 'type': 'praxly_parameter_block',
- }
- }
- }
- }
]
},
{
@@ -65,10 +46,6 @@ export const toolbox = {
"name": "variables",
"categorystyle": "variable_blocks",
"contents": [
- {
- 'kind': 'block',
- 'type': 'praxly_variable_block'
- },
{
'kind': 'block',
'type': 'praxly_vardecl_block',
@@ -81,7 +58,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': 0,
+ 'LITERAL': 'value',
}
},
},
@@ -93,14 +70,18 @@ export const toolbox = {
'inputs': {
'EXPRESSION': {
'shadow': {
- 'type': 'praxly_literal_block',
+ 'type': 'praxly_variable_block',
'fields': {
- 'LITERAL': 0,
+ 'LITERAL': 'value',
}
},
},
}
},
+ {
+ 'kind': 'block',
+ 'type': 'praxly_variable_block'
+ },
{
'kind': 'block',
'type': 'praxly_array_assignment_block',
@@ -120,10 +101,18 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': '0',
+ 'LITERAL': 'index',
}
},
},
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'value'
+ }
+ }
+ }
}
},
{
@@ -134,7 +123,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': '0',
+ 'LITERAL': 'index',
}
},
},
@@ -145,12 +134,8 @@ export const toolbox = {
{
"kind": "category",
"name": "math",
- "categorystyle": "expression_blocks",
+ "categorystyle": "math_blocks",
"contents": [
- {
- 'kind': 'block',
- 'type': 'praxly_literal_block'
- },
// {
// 'kind': 'block',
// 'type': 'praxly_String_block'
@@ -191,6 +176,10 @@ export const toolbox = {
},
}
},
+ {
+ 'kind': 'block',
+ 'type': 'praxly_literal_block'
+ },
{
'kind': 'block',
'type': 'praxly_random_block'
@@ -303,7 +292,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': 1
+ 'LITERAL': -5
}
}
}
@@ -317,7 +306,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': 1
+ 'LITERAL': 2.718
}
}
}
@@ -331,7 +320,7 @@ export const toolbox = {
'shadow': {
'type': 'praxly_literal_block',
'fields': {
- 'LITERAL': 1
+ 'LITERAL': 25
}
}
}
@@ -341,24 +330,194 @@ export const toolbox = {
},
{
"kind": "category",
- "name": "logic",
- "categorystyle": "logic_blocks",
+ "name": "text",
+ "categorystyle": "text_blocks",
"contents": [
+ // {
+ // 'kind': 'block',
+ // 'type': 'praxly_StringFunc_block',
+ // 'inputs': {
+ // 'EXPRESSION': {
+ // 'shadow': {
+ // 'type': 'praxly_literal_block',
+ // 'fields': {
+ // 'LITERAL': '\"hello, world\"',
+ // }
+ // },
+ // },
+ // 'PARAMS': {
+ // 'block': {
+ // 'type': 'praxly_parameter_block',
+ // }
+ // }
+ // }
+ // },
{
'kind': 'block',
- 'type': 'praxly_true_block'
+ 'type': 'praxly_charAt_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ 'INDEX': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'index'
+ }
+ }
+ }
+ }
},
{
'kind': 'block',
- 'type': 'praxly_false_block'
- },
+ 'type': 'praxly_contains_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ 'PARAM': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'string'
+ }
+ }
+ }
+ }
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_indexOf_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ 'PARAM': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'string'
+ }
+ }
+ }
+ }
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_length_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ }
+ }
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_substring_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ 'PARAM1': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'indexStart'
+ }
+ }
+ },
+ 'PARAM2': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': 'indexEnd'
+ }
+ }
+ }
+ }
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_toLowerCase_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ }
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_toUpperCase_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': '"Hello"',
+ }
+ },
+ },
+ }
+ },
+ ]
+ },
+ {
+ "kind": "category",
+ "name": "logic",
+ "categorystyle": "logic_blocks",
+ "contents": [
{
'kind': 'block',
- 'type': 'praxly_if_block'
+ 'type': 'praxly_if_block',
+ 'inputs': {
+ 'CONDITION': {
+ 'shadow': {
+ 'type': 'praxly_true_block'
+ }
+ }
+ }
},
{
'kind': 'block',
- 'type': 'praxly_if_else_block'
+ 'type': 'praxly_if_else_block',
+ 'inputs': {
+ 'CONDITION': {
+ 'shadow': {
+ 'type': 'praxly_true_block'
+ }
+ }
+ }
},
{
'kind': 'block',
@@ -366,25 +525,26 @@ export const toolbox = {
'inputs': {
'A_OPERAND': {
'shadow': {
- 'type': 'praxly_literal_block',
- 'fields': {
- 'LITERAL': true,
- }
+ 'type': 'praxly_true_block',
},
},
'B_OPERAND': {
'shadow': {
- 'type': 'praxly_literal_block',
- 'fields': {
- 'LITERAL': false,
- }
+ 'type': 'praxly_false_block',
},
}
}
},
{
'kind': 'block',
- 'type': 'praxly_not_block'
+ 'type': 'praxly_not_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'shadow': {
+ 'type': 'praxly_true_block'
+ }
+ }
+ }
},
{
'kind': 'block',
@@ -407,6 +567,14 @@ export const toolbox = {
},
}
}
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_true_block'
+ },
+ {
+ 'kind': 'block',
+ 'type': 'praxly_false_block'
}
]
},
@@ -655,7 +823,17 @@ export const toolbox = {
},
{
'kind': 'block',
- 'type': 'praxly_return_block'
+ 'type': 'praxly_return_block',
+ 'inputs': {
+ 'EXPRESSION': {
+ 'block': {
+ 'type': 'praxly_literal_block',
+ 'fields': {
+ 'LITERAL': "value"
+ }
+ }
+ }
+ }
},
{
'kind': 'block',
diff --git a/src/tree2blocks.js b/src/tree2blocks.js
index 2a5e597..ab8a9e8 100644
--- a/src/tree2blocks.js
+++ b/src/tree2blocks.js
@@ -1,4 +1,4 @@
-import { NODETYPES, TYPES } from "./common";
+import { NODETYPES, StringFuncs, TYPES } from "./common";
function connectStatements(statements) {
for (let i = 0; i < statements.length - 1; i++) {
@@ -18,7 +18,7 @@ function connectStatements(statements) {
}
}
else {
- console.log("connection failed");
+ console.error("connection failed");
}
}
}
@@ -137,47 +137,43 @@ export const tree2blocks = (workspace, node) => {
result = workspace.newBlock('praxly_random_block');
} else if (node.name === 'randomInt') {
result = workspace.newBlock('praxly_random_int_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('MAX').connection.connect(child?.outputConnection);
} else if (node.name === 'randomSeed') {
result = workspace.newBlock('praxly_random_seed_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('SEED').connection.connect(child?.outputConnection);
} else if (node.name === 'int') {
result = workspace.newBlock('praxly_int_conversion_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('CONVERSION').connection.connect(child?.outputConnection);
} else if (node.name === 'float') {
result = workspace.newBlock('praxly_float_conversion_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('CONVERSION').connection.connect(child?.outputConnection);
} else if (node.name === 'min') {
result = workspace.newBlock('praxly_min_block');
-
- const child1 = tree2blocks(workspace, node?.parameters[0]);
+ const child1 = tree2blocks(workspace, node?.args[0]);
result.getInput('A_MIN').connection.connect(child1?.outputConnection);
-
- const child2 = tree2blocks(workspace, node?.parameters[1]);
+ const child2 = tree2blocks(workspace, node?.args[1]);
result.getInput('B_MIN').connection.connect(child2?.outputConnection);
} else if (node.name === 'max') {
result = workspace.newBlock('praxly_max_block');
-
- const child1 = tree2blocks(workspace, node?.parameters[0]);
+ const child1 = tree2blocks(workspace, node?.args[0]);
result.getInput('A_MAX').connection.connect(child1?.outputConnection);
-
- const child2 = tree2blocks(workspace, node?.parameters[1]);
+ const child2 = tree2blocks(workspace, node?.args[1]);
result.getInput('B_MAX').connection.connect(child2?.outputConnection);
} else if (node.name === 'abs') {
result = workspace.newBlock('praxly_abs_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('VALUE').connection.connect(child?.outputConnection);
} else if (node.name === 'log') {
result = workspace.newBlock('praxly_log_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('VALUE').connection.connect(child?.outputConnection);
} else if (node.name === 'sqrt') {
result = workspace.newBlock('praxly_sqrt_block');
- const child = tree2blocks(workspace, node?.parameters[0]);
+ const child = tree2blocks(workspace, node?.args[0]);
result.getInput('VALUE').connection.connect(child?.outputConnection);
}
break;
@@ -189,10 +185,9 @@ export const tree2blocks = (workspace, node) => {
return tree2blocks(workspace, element);
}
catch (error) {
- console.error('An error occurred: empty statement', error);
+ console.error('invalid statement', error);
return null;
}
-
});
connectStatements(statements);
return statements;
@@ -209,24 +204,24 @@ export const tree2blocks = (workspace, node) => {
case NODETYPES.IF:
var result = workspace.newBlock('praxly_if_block');
var condition = tree2blocks(workspace, node?.condition);
- var codeblocks = tree2blocks(workspace, node?.statement);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
- if (codeblocks && codeblocks.length > 0) {
- result.getInput('STATEMENT').connection.connect(codeblocks[0]?.previousConnection);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('STATEMENT').connection.connect(codeblock[0]?.previousConnection);
}
break;
case NODETYPES.IF_ELSE:
var result = workspace.newBlock('praxly_if_else_block');
var condition = tree2blocks(workspace, node?.condition);
- var statements = tree2blocks(workspace, node?.statement);
- var alternatives = tree2blocks(workspace, node?.alternative);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
+ var alternative = tree2blocks(workspace, node?.alternative);
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
- if (statements && statements.length > 0) {
- result.getInput('STATEMENT').connection.connect(statements[0]?.previousConnection);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('STATEMENT').connection.connect(codeblock[0]?.previousConnection);
}
- if (alternatives && alternatives.length > 0) {
- result.getInput('ALTERNATIVE').connection.connect(alternatives[0]?.previousConnection);
+ if (alternative && alternative.length > 0) {
+ result.getInput('ALTERNATIVE').connection.connect(alternative[0]?.previousConnection);
}
break;
@@ -243,11 +238,10 @@ export const tree2blocks = (workspace, node) => {
break;
case NODETYPES.ASSIGNMENT:
+ var result = workspace.newBlock('praxly_reassignment_block');
+ result.setFieldValue(node.location.name, "VARIABLENAME");
var expression = tree2blocks(workspace, node?.value);
- var result = workspace.newBlock('praxly_reassignment_expression_block');
- var location = tree2blocks(workspace, node.location);
result.getInput('EXPRESSION').connection.connect(expression?.outputConnection);
- result.getInput('LOCATION').connection.connect(location?.outputConnection);
break;
case NODETYPES.VARDECL:
@@ -257,6 +251,12 @@ export const tree2blocks = (workspace, node) => {
result.setFieldValue(node.varType, "VARTYPE");
result.setFieldValue(node.name, "VARIABLENAME");
result.getInput('EXPRESSION').connection.connect(expression?.outputConnection);
+ } else if (node.varType == TYPES.VOID) {
+ // procedures look like variables until left paren is typed
+ var result = workspace.newBlock('praxly_procedure_block');
+ result.setFieldValue(node.varType, "RETURNTYPE");
+ result.setFieldValue(node.name, 'PROCEDURE_NAME');
+ result.setFieldValue(node.name, 'END_PROCEDURE_NAME');
} else {
var result = workspace.newBlock('praxly_vardecl_block');
result.setFieldValue(node.varType, "VARTYPE");
@@ -267,30 +267,30 @@ export const tree2blocks = (workspace, node) => {
case NODETYPES.WHILE:
var result = workspace.newBlock('praxly_while_loop_block');
var condition = tree2blocks(workspace, node?.condition);
- var codeblocks = tree2blocks(workspace, node?.statement);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
- if (codeblocks && codeblocks.length > 0) {
- result.getInput('STATEMENT').connection.connect(codeblocks[0]?.previousConnection);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('STATEMENT').connection.connect(codeblock[0]?.previousConnection);
}
break;
case NODETYPES.DO_WHILE:
var result = workspace.newBlock('praxly_do_while_loop_block');
var condition = tree2blocks(workspace, node?.condition);
- var codeblocks = tree2blocks(workspace, node?.statement);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
- if (codeblocks && codeblocks.length > 0) {
- result.getInput('STATEMENT').connection.connect(codeblocks[0]?.previousConnection);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('STATEMENT').connection.connect(codeblock[0]?.previousConnection);
}
break;
case NODETYPES.REPEAT_UNTIL:
var result = workspace.newBlock('praxly_repeat_until_loop_block');
var condition = tree2blocks(workspace, node?.condition);
- var codeblocks = tree2blocks(workspace, node?.statement);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
- if (codeblocks && codeblocks.length > 0) {
- result.getInput('STATEMENT').connection.connect(codeblocks[0]?.previousConnection);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('STATEMENT').connection.connect(codeblock[0]?.previousConnection);
}
break;
@@ -327,22 +327,60 @@ export const tree2blocks = (workspace, node) => {
break;
case NODETYPES.SPECIAL_STRING_FUNCCALL:
- var result = workspace.newBlock('praxly_StringFunc_block');
- var params = workspace.newBlock('praxly_parameter_block');
- var recipient = tree2blocks(workspace, node.left);
- result.setFieldValue(node?.right?.name, 'FUNCTYPE');
- result.getInput('PARAMS').connection.connect(params?.outputConnection);
- var argsList = node?.right?.args;
- for (var i = 0; i < (argsList?.length ?? 0); i++) {
- params.appendValueInput(`PARAM_${i}`);
- var argument = tree2blocks(workspace, argsList[i]);
- params.getInput(`PARAM_${i}`).connection.connect(argument?.outputConnection);
+ if (!node.right) {
+ break; // user still typing (nothing after the dot)
+ }
+ const name = node.right.name;
+ const args = node.right.args;
+
+ // create applicable string method block and connect args
+ if (name === StringFuncs.CHARAT) {
+ var result = workspace.newBlock('praxly_charAt_block');
+ if (args?.length == 1) {
+ const index = tree2blocks(workspace, args[0]);
+ result.getInput('INDEX').connection.connect(index?.outputConnection);
+ }
+ }
+ else if (name === StringFuncs.CONTAINS) {
+ var result = workspace.newBlock('praxly_contains_block');
+ if (args?.length == 1) {
+ const param = tree2blocks(workspace, args[0]);
+ result.getInput('PARAM').connection.connect(param?.outputConnection);
+ }
+ }
+ else if (name === StringFuncs.INDEXOF) {
+ result = workspace.newBlock('praxly_indexOf_block');
+ if (args?.length == 1) {
+ const param = tree2blocks(workspace, args[0]);
+ result.getInput('PARAM').connection.connect(param?.outputConnection);
+ }
}
+ else if (name === StringFuncs.LENGTH) {
+ result = workspace.newBlock('praxly_length_block');
+ }
+ else if (name === StringFuncs.SUBSTRING) {
+ result = workspace.newBlock('praxly_substring_block');
+ if (args?.length == 2) {
+ const param1 = tree2blocks(workspace, args[0]);
+ const param2 = tree2blocks(workspace, args[1]);
+ result.getInput('PARAM1').connection.connect(param1?.outputConnection);
+ result.getInput('PARAM2').connection.connect(param2?.outputConnection);
+ }
+ }
+ else if (name === StringFuncs.TOLOWERCSE) {
+ result = workspace.newBlock('praxly_toLowerCase_block');
+ }
+ else if (name === StringFuncs.TOUPPERCASE) {
+ result = workspace.newBlock('praxly_toUpperCase_block');
+ } else {
+ break; // user still typing or misspelled name
+ }
+
+ // connect the string on the left of the result block
+ var recipient = tree2blocks(workspace, node.left);
result.getInput("EXPRESSION").connection.connect(recipient.outputConnection);
- params.initSvg();
break;
-
case NODETYPES.FUNCDECL:
var returnType = node?.returnType;
var argsList = node?.params;
@@ -352,9 +390,9 @@ export const tree2blocks = (workspace, node) => {
result.setFieldValue(node?.name, 'PROCEDURE_NAME');
result.setFieldValue(node?.name, 'END_PROCEDURE_NAME');
result.getInput('PARAMS').connection.connect(params?.outputConnection);
- var contents = tree2blocks(workspace, node?.contents);
- if (contents && contents.length > 0) {
- result.getInput('CONTENTS').connection.connect(contents[0]?.previousConnection);
+ var codeblock = tree2blocks(workspace, node?.codeblock);
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('CODEBLOCK').connection.connect(codeblock[0]?.previousConnection);
}
for (var i = 0; i < (argsList?.length ?? 0); i++) {
params.appendValueInput(`PARAM_${i}`);
@@ -371,43 +409,71 @@ export const tree2blocks = (workspace, node) => {
var result = workspace.newBlock('praxly_for_loop_block');
try {
var initialization = tree2blocks(workspace, node?.initialization);
- if (!initialization || initialization.type !== 'praxly_statement_block') {
+ if (!initialization) {
+ // do nothing; user still typing
+ } else if (initialization.type == 'praxly_statement_block') {
+ // unpack the expression statement
+ var container1 = initialization;
+ initialization = initialization.getInputTargetBlock('EXPRESSION');
+ } else if (initialization.type == 'praxly_assignment_block'
+ || initialization.type == 'praxly_reassignment_block') {
+ // convert statement to expression
initialization.dispose();
- var initialization = workspace.newBlock('praxly_assignment_expression_block');
+ if (node?.initialization?.varType) {
+ initialization = workspace.newBlock('praxly_assignment_expression_block');
+ initialization.setFieldValue(node?.initialization?.varType, "VARTYPE");
+ initialization.setFieldValue(node?.initialization?.name, "VARIABLENAME");
+ } else {
+ initialization = workspace.newBlock('praxly_reassignment_expression_block');
+ var location = tree2blocks(workspace, node?.initialization?.location);
+ initialization.getInput('LOCATION').connection.connect(location?.outputConnection);
+ }
var expression = tree2blocks(workspace, node?.initialization?.value);
- initialization.setFieldValue(node?.initialization?.varType, "VARTYPE");
- initialization.setFieldValue(node?.initialization?.name, "VARIABLENAME");
initialization.getInput('EXPRESSION').connection.connect(expression?.outputConnection);
initialization.initSvg();
- } else {
- var container = initialization;
- initialization = initialization.getInputTargetBlock('EXPRESSION');
-
}
- // var increment = workspace.newBlock('praxly_reassignment_expression_block');
- // var expression2 = tree2blocks(workspace, node?.increment?.value);
- // increment.setFieldValue(node?.increment?.name, "VARIABLENAME");
- // increment.getInput('EXPRESSION').connection.connect(expression2?.outputConnection);
- // increment.initSvg();
-
+ // this will always be an expression, so nothing more to do
var condition = tree2blocks(workspace, node?.condition);
+
var increment = tree2blocks(workspace, node?.increment);
- var codeblocks = tree2blocks(workspace, node?.statement);
+ if (!increment) {
+ // do nothing; user still typing
+ } else if (increment.type == 'praxly_statement_block') {
+ // unpack the expression statement
+ var container2 = increment;
+ increment = increment.getInputTargetBlock('EXPRESSION');
+ } else {
+ // was likely praxly_reassignment_block
+ increment.dispose();
+ increment = workspace.newBlock('praxly_reassignment_expression_block');
+ var location2 = tree2blocks(workspace, node?.increment?.location);
+ var expression2 = tree2blocks(workspace, node?.increment?.value);
+ increment.getInput('LOCATION').connection.connect(location2?.outputConnection);
+ increment.getInput('EXPRESSION').connection.connect(expression2?.outputConnection);
+ increment.initSvg();
+ }
+
+ // get the for loop body
+ var codeblock = tree2blocks(workspace, node?.codeblock);
+ // connect everything together
result.getInput('INITIALIZATION').connection.connect(initialization?.outputConnection);
- container?.dispose();
+ container1?.dispose();
result.getInput('CONDITION').connection.connect(condition?.outputConnection);
result.getInput('REASSIGNMENT').connection.connect(increment?.outputConnection);
- if (codeblocks && codeblocks.length > 0) {
- result.getInput('CODEBLOCK').connection.connect(codeblocks[0]?.previousConnection);
+ container2?.dispose();
+ if (codeblock && codeblock.length > 0) {
+ result.getInput('CODEBLOCK').connection.connect(codeblock[0]?.previousConnection);
}
}
catch (error) {
- console.error('An error occurred: could not generate the nested block', error);
- initialization?.dispose();
- increment?.dispose(); // the question marks here helped the for loop block generate when just typing
+ console.error('for loop header', error);
+ // the question marks here helped the for loop block generate when just typing
// the word "for", giving a little bit of predictive block rendering.
+ initialization?.dispose();
+ condition?.dispose();
+ increment?.dispose();
}
break;
@@ -424,8 +490,8 @@ export const tree2blocks = (workspace, node) => {
case NODETYPES.ARRAY_REFERENCE_ASSIGNMENT:
var result = workspace.newBlock('praxly_array_reference_reassignment_block');
- result.setFieldValue(node.name, "VARIABLENAME");
- var child = tree2blocks(workspace, node?.index);
+ result.setFieldValue(node.location.name, "VARIABLENAME");
+ var child = tree2blocks(workspace, node.location.index);
result.getInput('INDEX').connection.connect(child?.outputConnection);
var expression = tree2blocks(workspace, node?.value);
result.getInput('EXPRESSION').connection.connect(expression?.outputConnection);
diff --git a/src/tree2text.js b/src/tree2text.js
index 06c4b56..5fd4978 100644
--- a/src/tree2text.js
+++ b/src/tree2text.js
@@ -2,7 +2,7 @@ import { NODETYPES, TYPES } from "./common";
import { text2tree } from "./text2tree";
export const tree2text = (node, indentation) => {
- if (!node.type) {
+ if (!node?.type) {
return; // undefined
}
@@ -21,7 +21,7 @@ export const tree2text = (node, indentation) => {
try {
var result = node.name.toString();
if (node.isArray) {
- result += `[${tree2text(node.index)}]`;
+ result += `[${tree2text(node.index, 0)}]`;
}
return result;
} catch (error) {
@@ -77,92 +77,92 @@ export const tree2text = (node, indentation) => {
}
case NODETYPES.ADDITION:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " + ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.SUBTRACTION:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " - ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.MULTIPLICATION:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " * ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.DIVISION:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " / ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.EXPONENTIATION:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " ^ ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.MODULUS:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " % ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.AND:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " and ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.OR:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " or ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.EQUALITY:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " == ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.LESS_THAN_OR_EQUAL:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " ≤ ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.GREATER_THAN_OR_EQUAL:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " ≥ ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.GREATER_THAN:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " > ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.LESS_THAN:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " < ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.INEQUALITY:
- var a_operand = tree2text(node.left, indentation);
+ var a_operand = tree2text(node.left, 0);
var operator = " ≠ ";
- var b_operand = tree2text(node.right, node.endIndex, indentation);
+ var b_operand = tree2text(node.right, 0);
return a_operand + operator + b_operand;
case NODETYPES.PRINT:
var result = ' '.repeat(indentation) + "print ";
- var expression = tree2text(node.value, node.endIndex, indentation);
+ var expression = tree2text(node.value, 0);
if (node.comment) {
expression += ' // ' + node.comment;
}
@@ -170,7 +170,7 @@ export const tree2text = (node, indentation) => {
return result + expression;
case NODETYPES.ASSOCIATION:
- return `(${tree2text(node.expression, node.endIndex, indentation)})`;
+ return `(${tree2text(node.expression, 0)})`;
case NODETYPES.BUILTIN_FUNCTION_CALL: {
if (node.name === 'input') {
@@ -178,40 +178,40 @@ export const tree2text = (node, indentation) => {
} else if (node.name === 'random') {
return "random()";
} else if (node.name === 'randomInt') {
- const max = tree2text(node.parameters[0], indentation);
+ const max = tree2text(node.args[0], 0);
return `randomInt(${max})`;
} else if (node.name === 'randomSeed') {
- const seed = tree2text(node.parameters[0], indentation);
+ const seed = tree2text(node.args[0], 0);
return `randomSeed(${seed})`;
} else if (node.name === 'int') {
- const conversion = tree2text(node.parameters[0], indentation);
+ const conversion = tree2text(node.args[0], 0);
return `int(${conversion})`;
} else if (node.name === 'float') {
- const conversion = tree2text(node.parameters[0], indentation);
+ const conversion = tree2text(node.args[0], 0);
return `float(${conversion})`;
} else if (node.name === 'min') {
- const a_value = tree2text(node.parameters[0], indentation);
- const b_value = tree2text(node.parameters[1], indentation);
+ const a_value = tree2text(node.args[0], 0);
+ const b_value = tree2text(node.args[1], 0);
return "min(" + a_value + ", " + b_value + ")";
} else if (node.name === 'max') {
- const a_value = tree2text(node.parameters[0], indentation);
- const b_value = tree2text(node.parameters[1], indentation);
+ const a_value = tree2text(node.args[0], 0);
+ const b_value = tree2text(node.args[1], 0);
return "max(" + a_value + ", " + b_value + ")";
} else if (node.name === 'abs') {
- const value = tree2text(node.parameters[0], indentation);
+ const value = tree2text(node.args[0], 0);
return `abs(${value})`;
} else if (node.name === 'log') {
- const value = tree2text(node.parameters[0], indentation);
+ const value = tree2text(node.args[0], 0);
return `log(${value})`;
} else if (node.name = 'sqrt') {
- const value = tree2text(node.parameters[0], indentation);
+ const value = tree2text(node.args[0], 0);
return `sqrt(${value})`;
}
}
case NODETYPES.RETURN:
var result = ' '.repeat(indentation) + "return ";
- var expression = tree2text(node.value, node.endIndex, indentation) + '\n';
+ var expression = tree2text(node.value, 0) + '\n';
return result + expression;
case NODETYPES.PROGRAM:
@@ -219,7 +219,7 @@ export const tree2text = (node, indentation) => {
case NODETYPES.STATEMENT:
var result = ' '.repeat(indentation);
- var expression = tree2text(node.value, node.endIndex, indentation) + '\n';
+ var expression = tree2text(node.value, 0) + '\n';
return result + expression;
case NODETYPES.CODEBLOCK:
@@ -227,8 +227,7 @@ export const tree2text = (node, indentation) => {
try {
return tree2text(element, indentation);
} catch (error) {
- console.error('An error occurred: empty statement', error);
- return null;
+ return " ";
}
});
return statements.join('');
@@ -236,68 +235,69 @@ export const tree2text = (node, indentation) => {
case NODETYPES.IF:
var result = ' '.repeat(indentation) + "if (";
var condition = tree2text(node.condition, 0) + ")\n";
- var contents = tree2text(node.statement, indentation + 1) +
+ var codeblock = tree2text(node.codeblock, indentation + 1) +
' '.repeat(indentation) + 'end if\n';
- return result + condition + contents;
+ return result + condition + codeblock;
case NODETYPES.IF_ELSE:
var result = ' '.repeat(indentation) + "if (";
- var condition = tree2text(node.condition, indentation) + ")\n";
- var contents = tree2text(node.statement, indentation + 1);
+ var condition = tree2text(node.condition, 0) + ")\n";
+ var codeblock = tree2text(node.codeblock, indentation + 1);
var alternative = ' '.repeat(indentation) + '\else\n' +
tree2text(node.alternative, indentation + 1) +
' '.repeat(indentation) + 'end if\n';
- return result + condition + contents + alternative;
+ return result + condition + codeblock + alternative;
+ // Note: reassignment (either a statement or in a for loop)
case NODETYPES.ASSIGNMENT:
- var varname = tree2text(node.location, node.endIndex, indentation);
+ var varname = tree2text(node.location, indentation);
var operator = ' ← ';
- var expression = tree2text(node.value, node.endIndex, indentation);
- return varname + operator + expression;
+ var expression = tree2text(node.value, 0);
+ return ' '.repeat(indentation) + varname + operator + expression + '\n';
+ // Note: declaration and assignment (possibly in a for loop)
case NODETYPES.VARDECL:
try {
var vartype = node.varType.toString();
var varname = vartype + ' ' + node.name.toString();
if (node.value !== undefined) {
var operator = ' ← ';
- var expression = tree2text(node.value, node.endIndex, indentation);
+ var expression = tree2text(node.value, 0);
return ' '.repeat(indentation) + varname + operator + expression + '\n';
} else {
return ' '.repeat(indentation) + varname + '\n';
}
} catch (error) {
- console.error(error);
return " ";
}
case NODETYPES.WHILE:
var result = ' '.repeat(indentation) + "while";
- var condition = " (" + tree2text(node.condition, indentation) + ")\n";
- var contents = tree2text(node.statement, indentation + 1) +
+ var condition = " (" + tree2text(node.condition, 0) + ")\n";
+ var codeblock = tree2text(node.codeblock, indentation + 1) +
' '.repeat(indentation) + 'end while\n';
- return result + condition + contents;
+ return result + condition + codeblock;
case NODETYPES.DO_WHILE:
var result = ' '.repeat(indentation) + 'do\n';
- var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1);
- var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, indentation) + ")\n";
- return result + contents + condition;
+ var codeblock = tree2text(node.codeblock, indentation + 1);
+ var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, 0) + ")\n";
+ return result + codeblock + condition;
case NODETYPES.REPEAT_UNTIL:
var result = ' '.repeat(indentation) + 'repeat\n';
- var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1);
- var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, indentation) + ")\n";
- return result + contents + condition;
+ var codeblock = tree2text(node.codeblock, indentation + 1);
+ var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, 0) + ")\n";
+ return result + codeblock + condition;
case NODETYPES.NOT:
var result = "not ";
- var expression = tree2text(node.value, node.endIndex, indentation);
+ var expression = tree2text(node.value, 0);
return result + expression;
case NODETYPES.NEGATE:
var result = "-";
- var expression = tree2text(node.value, node.endIndex, indentation);
+ var expression = tree2text(node.value, 0);
return result + expression;
case NODETYPES.FOR:
@@ -306,10 +306,10 @@ export const tree2text = (node, indentation) => {
initialization = initialization.replace("\n", "") + '; ';
var condition = tree2text(node.condition, 0) + '; ';
var increment = tree2text(node.increment, 0);
- increment = increment + ")\n";
- var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1) +
+ increment = increment.replace("\n", "") + ')\n';
+ var codeblock = tree2text(node.codeblock, indentation + 1) +
' '.repeat(indentation) + 'end for\n';
- return result + initialization + condition + increment + contents;
+ return result + initialization + condition + increment + codeblock;
case NODETYPES.FUNCDECL:
var vartype = node.returnType.toString();
@@ -323,8 +323,8 @@ export const tree2text = (node, indentation) => {
}
result += ')';
result += '\n';
- var contents = ' '.repeat(indentation) + tree2text(node.contents, indentation + 1);
- result += contents;
+ var codeblock = tree2text(node.codeblock, indentation + 1);
+ result += codeblock;
result += ' '.repeat(indentation) + `end ${node.name}\n`;
return result;
@@ -333,7 +333,7 @@ export const tree2text = (node, indentation) => {
var argsList = node.args;
if (argsList !== null && argsList.length > 0) {
argsList.forEach(element => {
- result += tree2text(element, indentation) + ', ';
+ result += tree2text(element, 0) + ', ';
});
result = result.slice(0, result.length - 2);
}
@@ -341,12 +341,12 @@ export const tree2text = (node, indentation) => {
return result;
case NODETYPES.SPECIAL_STRING_FUNCCALL:
- var result = ' '.repeat(indentation) + tree2text(node.left, indentation) + '.' + node.right.name;
+ var result = ' '.repeat(indentation) + tree2text(node.left, 0) + '.' + node.right.name;
result += '(';
var argsList = node.right.args;
if (argsList !== null && argsList.length !== 0) {
argsList.forEach(element => {
- result += tree2text(element, indentation) + ', ';
+ result += tree2text(element, 0) + ', ';
});
result = result.slice(0, result.length - 2)
}
@@ -359,7 +359,7 @@ export const tree2text = (node, indentation) => {
var argsList = node.params;
if (argsList !== null && argsList.length > 0) {
argsList.forEach(element => {
- result += tree2text(element, indentation) + ', ';
+ result += tree2text(element, 0) + ', ';
});
result = result.slice(0, result.length - 2);
}
@@ -368,7 +368,7 @@ export const tree2text = (node, indentation) => {
case NODETYPES.ARRAY_REFERENCE:
result = node.name + '[';
- var expression = tree2text(node.index, node.endIndex, indentation) + ']';
+ var expression = tree2text(node.index, 0) + ']';
return result + expression;
case NODETYPES.ARRAY_ASSIGNMENT:
@@ -379,30 +379,29 @@ export const tree2text = (node, indentation) => {
var argsList = node.value.params;
if (argsList !== null && argsList.length > 0) {
argsList.forEach(element => {
- result += tree2text(element, indentation) + ', ';
+ result += tree2text(element, 0) + ', ';
});
result = result.slice(0, result.length - 2);
}
result += '}\n';
return ' '.repeat(indentation) + varname + operator + result;
} catch (error) {
- console.error(error);
- return "assignment for arrays broke";
+ return " ";
}
case NODETYPES.ARRAY_REFERENCE_ASSIGNMENT:
try {
- var index = tree2text(node.index, node.endIndex, indentation) + ']';
+ var index = tree2text(node.index, 0) + ']';
var varname = node.name.toString() + '[' + index;
var operator = ' ← ';
- var expression = tree2text(node.value, node.endIndex, indentation) + '\n';
+ var expression = tree2text(node.value, 0) + '\n';
return ' '.repeat(indentation) + varname + operator + expression;
} catch (error) {
return " ";
}
default:
- console.warn("Unknown node.type:" + node.type);
- break;
+ console.error("unknown node type " + node.type);
+ return " ";
}
}
diff --git a/test/basics.csv b/test/basics.csv
index a4874a6..1762a1d 100644
--- a/test/basics.csv
+++ b/test/basics.csv
@@ -30,14 +30,14 @@ a
7
8
nine",
-Assign float to int,int x ← 1.2,,,"error occurred on line 1:
- incompatible types: possible lossy conversion from double to int"
+Assign float to int,int x ← 1.2,,,"runtime error occurred on line 1:
+incompatible types: possible lossy conversion from double to int"
Assign int to float,"float y = 1
print y",,1.0,
-Assign str to int,String s = 1,,,"error occurred on line 1:
- incompatible types: int cannot be converted to String"
-Assign int to str,"int i = ""0""",,,"error occurred on line 1:
- incompatible types: String cannot be converted to int"
+Assign str to int,String s = 1,,,"runtime error occurred on line 1:
+incompatible types: int cannot be converted to String"
+Assign int to str,"int i = ""0""",,,"runtime error occurred on line 1:
+incompatible types: String cannot be converted to int"
Arithmetic ops,"int x ← 2
int y ← 3
print x + y
@@ -94,3 +94,24 @@ Placeholder,"print ""Before""
/* missing code */
print ""After""",,"Before
After",
+Print quotes,"int i ← 5
+double d ← 6
+char c ← '$'
+String s ← ""Hi!""
+int[] a1 ← {1, 2, 3}
+char[] a2 ← {'a', 'b', 'c'}
+String[] a3 ← {""Hi"", ""Mom""}
+
+print i
+print d
+print c
+print s
+print a1
+print a2
+print a3",,"5
+6.0
+$
+Hi!
+{1, 2, 3}
+{'a', 'b', 'c'}
+{""Hi"", ""Mom""}",
diff --git a/test/canvas.csv b/test/canvas.csv
index 33a3317..64b9c7f 100644
--- a/test/canvas.csv
+++ b/test/canvas.csv
@@ -154,25 +154,24 @@ if( secret_number != guess )
end if",14,"Guess a number!
You did not get it...",
-conditionals/5.4 if-statements.md random guess,"// get the random number
-int r ⟵ random
+conditionals/5.4 if-statements.md random guess,"// get a random integer between 0 and 99
+int n ← randomInt(100);
-// scale the number so it is between 1 and 100
-int nf ⟵ float (r) / 6.0 * 100.0;
-int n ⟵ int (nf);
+// adjust integer to be between 1 and 100
+n ← n + 1;
-// get the guess from the user;
-int guess ⟵ int(input())
+// get the guess from the user
+int guess ← int(input());
// compare n to guess
-if(n == guess)
+if (n == guess)
print 'y';
end if
-if(n != guess)
+if (n != guess)
print 'n';
-end if
-",,TODO random,
+end if",123,"
+n",
conditionals/5.5 more-ifs.md if guess 5,"print ""Guess a number between 1 and 10"";
int n ⟵ int(input())
@@ -184,32 +183,36 @@ end if",5,"Guess a number between 1 and 10
You got it!",
conditionals/5.5 more-ifs.md if else-if else,"print ""Guess a number between 1 and 10"";
-int n ⟵ int(input())
+int n ← int(input());
-if(n == 5)
+if (n == 5)
print ""Correct!"";
-else if (n == 4 or n == 6)
- print ""You're close!"";
else
- print ""Incorrect!"";
+ if (n == 4 or n == 6)
+ print ""You're close!"";
+ else
+ print ""Incorrect!"";
+ end if
end if",4,"Guess a number between 1 and 10
You're close!",
conditionals/5.5 more-ifs.md nested if-else,"print ""Guess a number between 1 and 10"";
-int n ⟵ int(input())
+int n ← int(input());
if (n < 5)
print ""Too low!"";
if (n == 4)
print ""Pretty close, though."";
end if
-else if (n > 5)
- print ""Too high!"";
- if(n == 6)
- print ""Pretty close, though."";
- end if
else
- print ""You got it!"";
+ if (n > 5)
+ print ""Too high!"";
+ if (n == 6)
+ print ""Pretty close, though."";
+ end if
+ else
+ print ""You got it!"";
+ end if
end if",6,"Guess a number between 1 and 10
Too high!
@@ -218,8 +221,8 @@ conditionals/5.6 loops.md infinite loop,"int count ⟵ 0;
while ( count < 5)
// print ""Looping!"";
-end while",,,"error occurred on line 3:
- This is probably an infinite loop."
+end while",,,"runtime error occurred on line 3:
+This is probably an infinite loop."
conditionals/5.6 loops.md while loop 5,"int count ⟵ 0;
while (count < 5 )
@@ -249,13 +252,16 @@ conditionals/5.6 loops.md do-while false,"int count ⟵ 2;
do
print ""Loop!"";
while ( count < 2 )",,Loop!,
-conditionals/5.7 compound-boolean.md game,"print ""Welcome to the game!"";
-int answer ⟵ random;
+conditionals/5.7 compound-boolean.md game,"randomSeed(0)
+
+print ""Welcome to the game!"";
+int answer ⟵ randomInt(6) + 1;
int guesses ⟵ 0;
+int n;
while (guesses <= 3)
print ""Guess a number between 1 and 6"";
- int n ⟵ int(input())
+ n ⟵ int(input())
if(n == answer)
print ""You got it!"";
@@ -273,7 +279,15 @@ if (n == answer)
end if
else
print ""You lost!"";
-end if",,TODO random,
+end if","1
+2","Welcome to the game!
+Guess a number between 1 and 6
+
+That's not it!
+Guess a number between 1 and 6
+
+You got it!
+You won!",
conditionals/5.7 compound-boolean.md license,"print ""How old are you?"";
int age = int(input())
@@ -328,13 +342,13 @@ Hello!
How are you?",
procedures/2-procedure-basics.md global vs local,"// procedure definition
void getRandom()
- int r ⟵ random // local variable
+ double r ⟵ random(); // local variable
end getRandom
// main program
-int r ⟵ 10; // global variable
-getRandom(); // call the procedure
-print r;",,TODO random,
+double r ⟵ 10; // global variable
+getRandom(); // call the procedure
+print r;",,10.0,
procedures/2-procedure-basics.md no global vars,"// procedure definition
void printSum()
int a ⟵ 10;
@@ -344,8 +358,8 @@ end printSum
// main program
printSum();
-print a * b; // ERROR!",,25,"error occurred on line 10:
- Variable a does not exist."
+print a * b; // ERROR!",,25,"runtime error occurred on line 10:
+Variable a does not exist."
procedures/3-params-and-return.md capital of VA,"print ""What is the capital of Virginia?"";
print ""1: Washington 2: Richmond 3: Norfolk"";
int ans ⟵ int(input())
@@ -534,7 +548,7 @@ print index",,2,
search-sort/4-search-algorithms.md binary search,"int binarySearch( String [] arr, int arrLength, String target )
int lower ← 0 // lower and upper indices define range
int upper ← arrLength - 1
- while ( lower < upper )
+ while ( lower <= upper )
int middle ← (lower+upper) / 2 // middle index in range
String current ← arr[middle]
if ( target > current ) // adjust range for next iteration
@@ -574,6 +588,7 @@ search-sort/5-sorting-algorithms.md selection sort,"void selectionSort( String [
for ( int k ← j + 1; k < length; k ← k + 1 ) // find minimum
if (arr[k] < arr[min])
min ← k
+ end if
end for
String temp ← arr[j] // swap
arr[j] ← arr[min]
diff --git a/test/colors.py b/test/colors.py
new file mode 100644
index 0000000..7e7e239
--- /dev/null
+++ b/test/colors.py
@@ -0,0 +1,26 @@
+import matplotlib.pyplot as plt
+
+# Define the darker pastel colors
+darker_colors = {
+ "Darker Pastel Violet": "#9966CC",
+ "Darker Pastel Blue": "#6699CC",
+ "Darker Pastel Green": "#66B266",
+ "Darker Pastel Yellow": "#FFCC66",
+ "Darker Pastel Orange": "#FF9966",
+ "Darker Pastel Red": "#FF6666",
+}
+
+# Create a figure and axis
+fig, ax = plt.subplots(figsize=(8, 6))
+
+# Plot each darker color as a rectangle with white text on top
+for i, (color_name, color_hex) in enumerate(darker_colors.items()):
+ ax.add_patch(plt.Rectangle((0, i), 1, 1, color=color_hex))
+ ax.text(0.5, i + 0.5, color_name, ha='center', va='center', fontsize=14, color='white')
+
+# Remove axes
+ax.set_xlim(0, 1)
+ax.set_ylim(0, len(darker_colors))
+ax.axis('off')
+
+plt.show()
diff --git a/test/run_csv.py b/test/run_csv.py
index 4f157ae..258125b 100644
--- a/test/run_csv.py
+++ b/test/run_csv.py
@@ -22,7 +22,7 @@
WAIT = 3
# How long to sleep before performing actions.
-PAUSE = 0.2
+PAUSE = 0.25
def main(csv_name, html_name):
@@ -65,17 +65,18 @@ def main(csv_name, html_name):
expect_out = row[3].rstrip().replace("\r", "")
expect_err = row[4].rstrip().replace("\r", "")
+ time.sleep(PAUSE)
print(f"Test {test_id}: {name}...", end="", flush=True)
if expect_out.startswith("TODO"):
print(todo_msg)
continue
# reset Praxly, paste code, and run
- time.sleep(PAUSE)
reset.click()
yes.click()
driver.execute_script(f'ace.edit("aceCode").setValue(`{code}`);')
editor.click()
+ time.sleep(PAUSE)
play.click()
# simulate each line of user input
diff --git a/test/studycomp.csv b/test/studycomp.csv
index 288798f..e6681b6 100644
--- a/test/studycomp.csv
+++ b/test/studycomp.csv
@@ -125,8 +125,7 @@ print ( age > 10 ) and ( height > 36 )
print not ( ( age ≤ 10 ) or ( height ≤ 36 ) )",,"true
true",
Q14,"String value ← ""Applesauce""
-value ← value.substring ( 0, 1 ).toLowerCase () +
- value.substring ( 1, value.length () ).toUpperCase ()
+value ← value.substring ( 0, 1 ).toLowerCase () + value.substring ( 1, value.length () ).toUpperCase ()
print value",,aPPLESAUCE,
Q15,"int mystery ( int n )
int temp ← 0
diff --git a/test/variables.csv b/test/variables.csv
index 921f576..ae61b15 100644
--- a/test/variables.csv
+++ b/test/variables.csv
@@ -4,15 +4,15 @@ double u ← 6
short t ← 12.2
float f ← 3.75
boolean b ← true
-char c ← a",,,"error occurred on line 3:
- incompatible types: possible lossy conversion from double to short"
+char c ← a",,,"runtime error occurred on line 3:
+incompatible types: possible lossy conversion from double to short"
Missing quotes,"int n ← 12
double u ← 6
//short t ← 12.2
float f ← 3.75
boolean b ← true
-char c ← a",,,"error occurred on line 6:
- Variable a does not exist."
+char c ← a",,,"runtime error occurred on line 6:
+Variable a does not exist."
Correct vars,"int n ← 12
double u ← 6
//short t ← 12.2
@@ -43,8 +43,8 @@ print f1 / f2",,"0
0.5",
Redeclare,"double a = 3.5 * 2.1
double a = 3.5 * 2.1
-print a",,,"error occurred on line 2:
- variable a has already been declared in this scope."
+print a",,,"runtime error occurred on line 2:
+variable a has already been declared in this scope."
Number arrays,"int[] a ⟵ {5, 9, 2, 10, 15};
print a
double[] b ⟵ {5, 9, 2, 10, 15};
@@ -59,5 +59,5 @@ end printSum
// main program
printSum();
-print a * b; // ERROR!",,25,"error occurred on line 10:
- Variable a does not exist."
+print a * b; // ERROR!",,25,"runtime error occurred on line 10:
+Variable a does not exist."