From 1c12279767e7c6986c223fb90d146283286d3132 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 09:39:09 -0400 Subject: [PATCH 01/35] fix lexing of missing quote marks --- src/text2tree.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index 5e7714d..f8a37b8 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -43,7 +43,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']; @@ -167,29 +167,37 @@ class Lexer { 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); + this.token_so_far = "?"; 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); + this.token_so_far = "?"; + } + this.emit_token(NODETYPES.CHAR); + continue; } 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); + this.token_so_far = "?"; 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; } From 892e130d73bcf642ea3fc8cb84cff92b9b969a71 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 09:49:21 -0400 Subject: [PATCH 02/35] fix format of textError and defaultError --- index.html | 2 +- public/themes.css | 9 +++++++++ src/ast.js | 2 +- src/common.js | 9 ++++++--- src/main.js | 3 +-- 5 files changed, 18 insertions(+), 7 deletions(-) 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..a2e1fee 100644 --- a/public/themes.css +++ b/public/themes.css @@ -554,6 +554,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..60ec6b9 100644 --- a/src/ast.js +++ b/src/ast.js @@ -64,7 +64,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); } diff --git a/src/common.js b/src/common.js index 6d90a72..7d9b4d4 100644 --- a/src/common.js +++ b/src/common.js @@ -94,7 +94,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 +110,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() { diff --git a/src/main.js b/src/main.js index b21e5be..f052780 100644 --- a/src/main.js +++ b/src/main.js @@ -493,9 +493,8 @@ async function runTasks(startDebug) { // special case: abort running (not an error) clear(); } else if (!errorOutput) { - // error not previously handled (by PraxlyError) + // error not previously handled by PraxlyError defaultError(error); - console.error(error); } } From 8cf3b9c782828c1eef1355496aaa5aa82247b1b6 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 10:33:48 -0400 Subject: [PATCH 03/35] remove console noise --- src/ast.js | 50 ++++++++++++++++++---------------------------- src/blocks2tree.js | 9 --------- src/common.js | 5 ++--- src/debugger.js | 1 - src/main.js | 1 - src/text2tree.js | 15 +------------- src/tree2blocks.js | 7 +++---- src/tree2text.js | 11 ++++------ 8 files changed, 29 insertions(+), 70 deletions(-) diff --git a/src/ast.js b/src/ast.js index 60ec6b9..464a9f7 100644 --- a/src/ast.js +++ b/src/ast.js @@ -42,10 +42,10 @@ 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; } } @@ -167,7 +167,7 @@ export function createExecutable(tree) { } else if (tree.name == 'sqrt') { return new Praxly_sqrt(createExecutable(tree.parameters[0]), tree); } else { - throw new Error("unknown builtin function: " + tree.name); + throw new PraxlyError("unknown builtin function: " + tree.name, tree.line); } } @@ -212,7 +212,6 @@ export function createExecutable(tree) { return new Praxly_assignment(tree, createExecutable(tree.location), createExecutable(tree.value), tree); } catch (error) { - // console.error('assignment error: ', error); return null; } @@ -229,7 +228,6 @@ export function createExecutable(tree) { return new Praxly_array_assignment(tree, createExecutable(tree.location), createExecutable(tree.value)); } catch (error) { - // console.error('array assignment error: ', error); return null; } @@ -242,7 +240,7 @@ export function createExecutable(tree) { return new Praxly_Location(tree, index); } catch (error) { - return; + return null; } case NODETYPES.FOR: @@ -254,7 +252,6 @@ export function createExecutable(tree) { return new Praxly_for(initialization, condition, incrementation, statement, tree); } catch (error) { - // console.error('for statement error: ', error); return new Praxly_statement(null); } @@ -265,7 +262,6 @@ export function createExecutable(tree) { return new Praxly_while(condition, statement, tree); } catch (error) { - // console.error('while statement error: ', error); return new Praxly_statement(null); } @@ -276,7 +272,6 @@ export function createExecutable(tree) { return new Praxly_do_while(condition, statement, tree); } catch (error) { - // console.error('do while statement error: ', error); return new Praxly_statement(null); } @@ -287,7 +282,6 @@ export function createExecutable(tree) { return new Praxly_repeat_until(condition, statement, tree); } catch (error) { - // console.error('repeat until statement error: ', error); return new Praxly_statement(null); } @@ -337,8 +331,7 @@ export function createExecutable(tree) { return new Praxly_emptyLine(tree); default: - console.error("Unhandled node type: " + tree.type); - return new Praxly_invalid(tree); + throw new PraxlyError("unhandled node type " + tree.type, tree.line); } } @@ -1171,7 +1164,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,8 +1192,7 @@ 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); } } @@ -1226,8 +1218,7 @@ class Praxly_assignment { + `${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; @@ -1277,8 +1268,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 +1278,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; @@ -1311,7 +1301,7 @@ 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); } environment.variableList[this.name] = valueEvaluated; } @@ -1327,7 +1317,6 @@ class Praxly_variable { 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]; } @@ -1560,11 +1549,11 @@ 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"); } } @@ -1642,7 +1631,7 @@ class Praxly_function_call { } catch (error) { if (error instanceof ReturnException) { - result = error.errorData; + result = error.returnValue; } else if (error instanceof RangeError) { // most likely infinite recursion @@ -1714,7 +1703,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 +1752,7 @@ function can_assign(varType, expressionType, line) { } else if (varType === TYPES.CHAR) { return expressionType === TYPES.CHAR; } else { - return false; // Invalid varType + throw new PraxlyError("unknown variable type", line); } } @@ -1916,7 +1905,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 +1926,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..3f17908 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -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, @@ -49,9 +48,6 @@ export const makeGenerator = () => { const praxlyGenerator = []; praxlyGenerator['codeBlockJsonBuilder'] = (headBlock) => { - // console.log('this is the head block'); - // console.log(headBlock); - var codeblock = { type: NODETYPES.CODEBLOCK, blockID: "blocks[]", @@ -419,7 +415,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); @@ -448,7 +443,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); @@ -478,7 +472,6 @@ export const makeGenerator = () => { 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); @@ -497,7 +490,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); @@ -580,7 +572,6 @@ export const makeGenerator = () => { 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 = []; diff --git a/src/common.js b/src/common.js index 7d9b4d4..31a5c86 100644 --- a/src/common.js +++ b/src/common.js @@ -191,10 +191,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]); @@ -286,5 +285,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..777a705 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]; diff --git a/src/main.js b/src/main.js index f052780..2176599 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); } }); diff --git a/src/text2tree.js b/src/text2tree.js index f8a37b8..cb0ad25 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -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; @@ -124,13 +122,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); } /** @@ -422,7 +416,7 @@ class Parser { type = OP.OR; break; default: - // handle unknown type + textError('parsing', 'invalid operator ' + operation, line); break; } return { @@ -962,7 +956,6 @@ class Parser { } } } - console.log(`parser messing up, current token is ${this.tokens[this.i].token_type}`); return result; } @@ -1107,12 +1100,6 @@ class Parser { 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(); } diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 2a5e597..111f43e 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -18,7 +18,7 @@ function connectStatements(statements) { } } else { - console.log("connection failed"); + console.error("connection failed"); } } } @@ -189,10 +189,9 @@ export const tree2blocks = (workspace, node) => { return tree2blocks(workspace, element); } catch (error) { - console.error('An error occurred: empty statement', error); + console.error('empty statement', error); return null; } - }); connectStatements(statements); return statements; @@ -404,7 +403,7 @@ export const tree2blocks = (workspace, node) => { } } catch (error) { - console.error('An error occurred: could not generate the nested block', error); + console.error('could not generate nested block', error); initialization?.dispose(); increment?.dispose(); // the question marks here helped the for loop block generate when just typing // the word "for", giving a little bit of predictive block rendering. diff --git a/src/tree2text.js b/src/tree2text.js index 06c4b56..b82ac0b 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -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(''); @@ -267,7 +266,6 @@ export const tree2text = (node, indentation) => { return ' '.repeat(indentation) + varname + '\n'; } } catch (error) { - console.error(error); return " "; } @@ -386,8 +384,7 @@ export const tree2text = (node, indentation) => { result += '}\n'; return ' '.repeat(indentation) + varname + operator + result; } catch (error) { - console.error(error); - return "assignment for arrays broke"; + return " "; } case NODETYPES.ARRAY_REFERENCE_ASSIGNMENT: @@ -402,7 +399,7 @@ export const tree2text = (node, indentation) => { } default: - console.warn("Unknown node.type:" + node.type); - break; + console.error("unknown node type " + node.type); + return " "; } } From 211263f6cf585ce22ef9e1a57d1e7a8dd7f5b1a7 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Thu, 1 Aug 2024 10:41:34 -0400 Subject: [PATCH 04/35] work in progress for charAt --- src/blocks2tree.js | 19 +++++++++- src/newBlocks.js | 86 ++++++++++++++++++++++++++++------------------ src/toolbox.js | 67 ++++++++++++++++++++++++++---------- src/tree2blocks.js | 10 ++++++ 4 files changed, 129 insertions(+), 53 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 71dec56..1ec403f 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); @@ -649,5 +649,22 @@ export const makeGenerator = () => { }) } + praxlyGenerator['praxly_charAt_block'] = (block) => { + const procedureName = StringFuncs.CHARAT; + const expression = block.getInputTargetBlock('EXPRESSION'); + 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 + } + }); + } + return praxlyGenerator; } diff --git a/src/newBlocks.js b/src/newBlocks.js index e38f8e6..3cd7a31 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", @@ -614,6 +581,59 @@ export function definePraxlyBlocks(workspace) { "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": 'other_blocks', + "tooltip": "Returns the character at index i", + "helpUrl": "" + }, { // logic 1 "type": "praxly_true_block", "message0": "true", diff --git a/src/toolbox.js b/src/toolbox.js index 86b4759..cbd4784 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -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', - } - } - } - } ] }, { @@ -339,6 +320,54 @@ export const toolbox = { } ] }, + { + "kind": "category", + "name": "text", + "categorystyle": "class_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_charAt_block', + 'inputs': { + 'EXPRESSION': { + 'shadow': { + 'type': 'praxly_literal_block', + 'fields': { + 'Literal': '\"hello, world\"', + } + }, + }, + 'INDEX': { + 'block': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': 0 + } + } + } + } + } + ] + }, { "kind": "category", "name": "logic", diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 2a5e597..42b899c 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -327,6 +327,16 @@ export const tree2blocks = (workspace, node) => { break; case NODETYPES.SPECIAL_STRING_FUNCCALL: + + if (node.name === 'charAt') { + result = workspace.newBlock('praxly_charAt_block'); + var recipient = tree2blocks(workspace, node.left); // left side + const child = tree2blocks(workspace, node?.args); + result.getInput('INDEX').connection.connect(child?.outputConnection); + result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + } + + var result = workspace.newBlock('praxly_StringFunc_block'); var params = workspace.newBlock('praxly_parameter_block'); var recipient = tree2blocks(workspace, node.left); From c138f7bdf7839fd44c4eec030eaf1d99fc762cf8 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 11:52:48 -0400 Subject: [PATCH 05/35] add null checks in blocks2tree to improve code generation and reduce console noise --- src/blocks2tree.js | 44 +++++++++++++++++++++++++------------------- src/main.js | 1 + src/tree2text.js | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 3f17908..71309c6 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -31,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 = { @@ -47,6 +47,10 @@ function customizeMaybe(block, node) { export const makeGenerator = () => { const praxlyGenerator = []; + praxlyGenerator[undefined] = (block) => { + return null; // incomplete block + } + praxlyGenerator['codeBlockJsonBuilder'] = (headBlock) => { var codeblock = { type: NODETYPES.CODEBLOCK, @@ -54,11 +58,13 @@ export const makeGenerator = () => { } 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); } @@ -81,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(), }) } @@ -222,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), }) } @@ -362,7 +368,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.IF, blockID: block.id, - condition: praxlyGenerator[condition.type](condition), + condition: praxlyGenerator[condition?.type](condition), statement: praxlyGenerator['codeBlockJsonBuilder'](statements) }) } @@ -374,7 +380,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.IF_ELSE, blockID: block.id, - condition: praxlyGenerator[condition.type](condition), + condition: praxlyGenerator[condition?.type](condition), statement: praxlyGenerator['codeBlockJsonBuilder'](statements), alternative: praxlyGenerator['codeBlockJsonBuilder'](alternative), }) @@ -459,8 +465,8 @@ 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, @@ -493,7 +499,7 @@ export const makeGenerator = () => { var location = block.getInputTargetBlock('LOCATION'); var expression = block.getInputTargetBlock('EXPRESSION'); var loc = praxlyGenerator[location.type](location); - var value = praxlyGenerator[expression.type](expression); + var value = praxlyGenerator[expression?.type](expression); return customizeMaybe(block, { type: NODETYPES.ASSIGNMENT, name: loc.name, @@ -510,7 +516,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.WHILE, blockID: block.id, - condition: praxlyGenerator[condition.type](condition), + condition: praxlyGenerator[condition?.type](condition), statement: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -521,7 +527,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.DO_WHILE, blockID: block.id, - condition: praxlyGenerator[condition.type](condition), + condition: praxlyGenerator[condition?.type](condition), statement: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -532,7 +538,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.REPEAT_UNTIL, blockID: block.id, - condition: praxlyGenerator[condition.type](condition), + condition: praxlyGenerator[condition?.type](condition), statement: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -542,7 +548,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { blockID: block.id, type: NODETYPES.NOT, - value: praxlyGenerator[expression.type](expression), + value: praxlyGenerator[expression?.type](expression), }) } @@ -563,10 +569,10 @@ export const makeGenerator = () => { return customizeMaybe(block, { type: NODETYPES.FOR, blockID: block.id, - initialization: praxlyGenerator[initialization.type](initialization), + initialization: praxlyGenerator[initialization?.type](initialization), statement: praxlyGenerator['codeBlockJsonBuilder'](statements), - increment: praxlyGenerator[reassignment.type](reassignment), - condition: praxlyGenerator[condition.type](condition), + increment: praxlyGenerator[reassignment?.type](reassignment), + condition: praxlyGenerator[condition?.type](condition), }); } @@ -615,7 +621,7 @@ export const makeGenerator = () => { return customizeMaybe(block, { blockID: block.id, type: NODETYPES.RETURN, - value: praxlyGenerator[expression.type](expression), + value: praxlyGenerator[expression?.type](expression), }) } diff --git a/src/main.js b/src/main.js index 2176599..907ff1b 100644 --- a/src/main.js +++ b/src/main.js @@ -493,6 +493,7 @@ async function runTasks(startDebug) { clear(); } else if (!errorOutput) { // error not previously handled by PraxlyError + console.error(error); defaultError(error); } } diff --git a/src/tree2text.js b/src/tree2text.js index b82ac0b..cbdc4c6 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 } From 9dcbba6314430d2e92cddf92c3e9797328a40607 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Thu, 1 Aug 2024 12:10:11 -0400 Subject: [PATCH 06/35] current charAt/contains block --- src/blocks2tree.js | 47 +++++++++++++++++++++++----------- src/newBlocks.js | 64 ++++++++++++++++++++++++++++++---------------- src/toolbox.js | 38 +++++++++++++++++++++------ src/tree2blocks.js | 32 +++++++++++++---------- 4 files changed, 123 insertions(+), 58 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 1ec403f..24de151 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -628,39 +628,56 @@ export const makeGenerator = () => { }) } - 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: argsList, + args: [praxlyGenerator[index.type](index)], type: NODETYPES.FUNCCALL } - }) + }); } - praxlyGenerator['praxly_charAt_block'] = (block) => { - const procedureName = StringFuncs.CHARAT; + praxlyGenerator['praxly_contains_block'] = (block) => { + const procedureName = StringFuncs.CONTAINS; const expression = block.getInputTargetBlock('EXPRESSION'); - const index = block.getInputTargetBlock('INDEX'); + const param = block.getInputTargetBlock('PARAM'); return customizeMaybe(block, { blockID: block.id, - name : procedureName, + name: procedureName, type: NODETYPES.SPECIAL_STRING_FUNCCALL, left: praxlyGenerator[expression.type](expression), right: { name: procedureName, - args: praxlyGenerator[index.type](index), + args: [praxlyGenerator[param.type](param)], type: NODETYPES.FUNCCALL } }); diff --git a/src/newBlocks.js b/src/newBlocks.js index 3cd7a31..020c456 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -581,42 +581,62 @@ export function definePraxlyBlocks(workspace) { "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_StringFunc_block", - "message0": "%1.%2(%3)", + "type": "praxly_charAt_block", + "message0": "%1.charAt (%2)", "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", + "name": "INDEX", "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", + "tooltip": "Returns the character at index i", "helpUrl": "" }, - { // text 1 - "type": "praxly_charAt_block", - "message0": "%1.charAt (%2)", + { // text 2 + "type": "praxly_contains_block", + "message0": "%1.contains (%2)", "args0": [ { "type": "input_value", @@ -624,14 +644,14 @@ export function definePraxlyBlocks(workspace) { }, { "type": "input_value", - "name": "INDEX", + "name": "PARAM", "text": "params" }, ], "inputsInline": true, "output": null, "style": 'other_blocks', - "tooltip": "Returns the character at index i", + "tooltip": "Returns true if value is a substring", "helpUrl": "" }, { // logic 1 diff --git a/src/toolbox.js b/src/toolbox.js index cbd4784..8178fee 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -325,9 +325,28 @@ export const toolbox = { "name": "text", "categorystyle": "class_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_StringFunc_block', + 'type': 'praxly_charAt_block', 'inputs': { 'EXPRESSION': { 'shadow': { @@ -337,35 +356,38 @@ export const toolbox = { } }, }, - 'PARAMS': { + 'INDEX': { 'block': { - 'type': 'praxly_parameter_block', + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': 0 + } } } } }, { 'kind': 'block', - 'type': 'praxly_charAt_block', + 'type': 'praxly_contains_block', 'inputs': { 'EXPRESSION': { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'Literal': '\"hello, world\"', + 'LITERAL': '\"hello, world\"', } }, }, - 'INDEX': { + 'PARAM': { 'block': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 0 + 'LITERAL': 'value' } } } } - } + } ] }, { diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 42b899c..4dd6ace 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -334,22 +334,28 @@ export const tree2blocks = (workspace, node) => { const child = tree2blocks(workspace, node?.args); result.getInput('INDEX').connection.connect(child?.outputConnection); result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + } else if (node.name === 'contains') { + result = workspace.newBlock('praxly_contains_block'); + var recipient = tree2blocks(workspace, node.left); // left side + const child = tree2blocks(workspace, node?.args); + result.getInput('PARAM').connection.connect(child?.outputConnection); + result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); } - 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); - } - result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - params.initSvg(); + // 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); + // } + // result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + // params.initSvg(); break; From f9584905e064a891bc700758a9442c88d72764c3 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 12:12:14 -0400 Subject: [PATCH 07/35] rename parameters to args to make built-in functions consistent with string methods --- src/ast.js | 28 +++++++++++++++++----------- src/blocks2tree.js | 22 +++++++++++----------- src/text2tree.js | 10 +++++----- src/tree2blocks.js | 26 +++++++++++--------------- src/tree2text.js | 22 +++++++++++----------- 5 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/ast.js b/src/ast.js index 464a9f7..9edc30d 100644 --- a/src/ast.js +++ b/src/ast.js @@ -50,8 +50,9 @@ class ReturnException extends Error { } 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); } } @@ -146,26 +147,31 @@ 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 PraxlyError("unknown builtin function: " + tree.name, tree.line); } diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 71309c6..c54e759 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -97,7 +97,7 @@ export const makeGenerator = () => { name: 'random', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [], + args: [], }); } @@ -107,7 +107,7 @@ export const makeGenerator = () => { name: 'randomInt', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -119,7 +119,7 @@ export const makeGenerator = () => { name: 'randomSeed', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -131,7 +131,7 @@ export const makeGenerator = () => { name: 'int', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -143,7 +143,7 @@ export const makeGenerator = () => { name: 'float', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -156,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), ], @@ -170,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), ], @@ -183,7 +183,7 @@ export const makeGenerator = () => { name: 'abs', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -195,7 +195,7 @@ export const makeGenerator = () => { name: 'log', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -207,7 +207,7 @@ export const makeGenerator = () => { name: 'sqrt', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [ + args: [ praxlyGenerator[expression.type](expression), ], }); @@ -219,7 +219,7 @@ export const makeGenerator = () => { name: 'input', blockID: block.id, type: NODETYPES.BUILTIN_FUNCTION_CALL, - parameters: [], + args: [], }); } diff --git a/src/text2tree.js b/src/text2tree.js index cb0ad25..e188db6 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -705,15 +705,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); + args.push(parameter); while (this.has(',')) { this.advance(); const parameter = this.parse_expression(9); - parameters.push(parameter); + args.push(parameter); } } @@ -723,7 +723,7 @@ class Parser { blockID: "code", name: nameToken.value, line, - parameters, + args, type: NODETYPES.BUILTIN_FUNCTION_CALL, startIndex: nameToken.startIndex, endIndex: rightParenthesisToken.endIndex, diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 111f43e..137e4f9 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -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; diff --git a/src/tree2text.js b/src/tree2text.js index cbdc4c6..0527c38 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -178,33 +178,33 @@ 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], indentation); return `randomInt(${max})`; } else if (node.name === 'randomSeed') { - const seed = tree2text(node.parameters[0], indentation); + const seed = tree2text(node.args[0], indentation); return `randomSeed(${seed})`; } else if (node.name === 'int') { - const conversion = tree2text(node.parameters[0], indentation); + const conversion = tree2text(node.args[0], indentation); return `int(${conversion})`; } else if (node.name === 'float') { - const conversion = tree2text(node.parameters[0], indentation); + const conversion = tree2text(node.args[0], indentation); 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], indentation); + const b_value = tree2text(node.args[1], indentation); 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], indentation); + const b_value = tree2text(node.args[1], indentation); return "max(" + a_value + ", " + b_value + ")"; } else if (node.name === 'abs') { - const value = tree2text(node.parameters[0], indentation); + const value = tree2text(node.args[0], indentation); return `abs(${value})`; } else if (node.name === 'log') { - const value = tree2text(node.parameters[0], indentation); + const value = tree2text(node.args[0], indentation); return `log(${value})`; } else if (node.name = 'sqrt') { - const value = tree2text(node.parameters[0], indentation); + const value = tree2text(node.args[0], indentation); return `sqrt(${value})`; } } From 0cf771173e2a7e6bda3eac8156dabb0bf3819b40 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 12:15:38 -0400 Subject: [PATCH 08/35] add arity checking for string methods --- src/ast.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ast.js b/src/ast.js index 9edc30d..7adad7c 100644 --- a/src/ast.js +++ b/src/ast.js @@ -178,6 +178,23 @@ export function createExecutable(tree) { } 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 new PraxlyError("unknown string method: " + tree.right.name, tree.line); + } var args = []; tree.right.args.forEach((arg) => { args.push(createExecutable(arg)); From d8cc7e2aaa6e88db686735ab7787d0d11b386fa7 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 12:59:50 -0400 Subject: [PATCH 09/35] bug fix: clear errors and stop debugger when editing blocks --- src/main.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 907ff1b..617c7a3 100644 --- a/src/main.js +++ b/src/main.js @@ -514,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(); @@ -528,6 +529,7 @@ export function turnCodeToBlocks() { console.log(mainTree); } + // update block side to match workspace.clear(); tree2blocks(workspace, mainTree); workspace.render(); @@ -542,11 +544,20 @@ 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); } + + // update text side to match const text = tree2text(mainTree, 0, 0); textEditor.setValue(text, -1); }; From 81dea7139c0035a3244a6ad70d67c80eda698dc2 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Thu, 1 Aug 2024 14:53:03 -0400 Subject: [PATCH 10/35] fix bugs related to reassignment --- src/ast.js | 3 +++ src/blocks2tree.js | 16 +++++++++++----- src/common.js | 1 - src/tree2text.js | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ast.js b/src/ast.js index 7adad7c..c4aeccd 100644 --- a/src/ast.js +++ b/src/ast.js @@ -1156,6 +1156,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; diff --git a/src/blocks2tree.js b/src/blocks2tree.js index c54e759..2b4e606 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -243,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 = { @@ -401,6 +405,7 @@ export const makeGenerator = () => { }) } + // Note: declaration and assignment praxlyGenerator['praxly_assignment_block'] = (block) => { var varType = block.getFieldValue('VARTYPE'); var variableName = block.getFieldValue('VARIABLENAME'); @@ -448,7 +453,6 @@ export const makeGenerator = () => { } praxlyGenerator['praxly_reassignment_block'] = (block) => { - var varType = block.getFieldValue('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, + }, }) } @@ -476,6 +483,7 @@ export const makeGenerator = () => { }) } + // declaration and assignment (Ex: in a for loop) praxlyGenerator['praxly_assignment_expression_block'] = (block) => { var varType = block.getFieldValue('VARTYPE'); var variableName = block.getFieldValue('VARIABLENAME'); @@ -495,18 +503,16 @@ export const makeGenerator = () => { } praxlyGenerator['praxly_reassignment_expression_block'] = (block) => { - var varType = block.getFieldValue('VARTYPE'); var location = block.getInputTargetBlock('LOCATION'); var expression = block.getInputTargetBlock('EXPRESSION'); var loc = praxlyGenerator[location.type](location); - var value = praxlyGenerator[expression?.type](expression); + var value = praxlyGenerator[expression.type](expression); return customizeMaybe(block, { type: NODETYPES.ASSIGNMENT, name: loc.name, location: loc, value: value, blockID: block.id, - varType: varType, }) } diff --git a/src/common.js b/src/common.js index 31a5c86..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", diff --git a/src/tree2text.js b/src/tree2text.js index 0527c38..900d91a 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -252,7 +252,7 @@ export const tree2text = (node, indentation) => { var varname = tree2text(node.location, node.endIndex, indentation); var operator = ' ← '; var expression = tree2text(node.value, node.endIndex, indentation); - return varname + operator + expression; + return varname + operator + expression + '\n'; case NODETYPES.VARDECL: try { From a867f9ddbfa2c229f32b6542392817dba6cc0e03 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Thu, 1 Aug 2024 15:52:51 -0400 Subject: [PATCH 11/35] consistency changes to variables, new numbers for builtins, no more empty spaces --- src/newBlocks.js | 6 ++-- src/toolbox.js | 73 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/newBlocks.js b/src/newBlocks.js index 020c456..0930a6b 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -165,7 +165,7 @@ export function definePraxlyBlocks(workspace) { { "type": "field_input", "name": "VARIABLENAME", - "text": "VariableName" + "text": "variableName" }, { "type": "input_dummy" @@ -198,7 +198,7 @@ export function definePraxlyBlocks(workspace) { { "type": "field_input", "name": "VARIABLENAME", - "text": "VariableName" + "text": "variableName" }, { "type": "input_value", @@ -222,7 +222,7 @@ export function definePraxlyBlocks(workspace) { { "type": "field_input", "name": "VARIABLENAME", - "text": "VariableName" + "text": "variableName" }, { "type": "input_value", diff --git a/src/toolbox.js b/src/toolbox.js index 8178fee..d4758c3 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -62,7 +62,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 0, + 'LITERAL': 'value', } }, }, @@ -74,9 +74,9 @@ export const toolbox = { 'inputs': { 'EXPRESSION': { 'shadow': { - 'type': 'praxly_literal_block', + 'type': 'praxly_variable_block', 'fields': { - 'LITERAL': 0, + 'LITERAL': 'value', } }, }, @@ -101,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' + } + } + } } }, { @@ -115,7 +123,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '0', + 'LITERAL': 'index', } }, }, @@ -284,7 +292,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 1 + 'LITERAL': -5 } } } @@ -298,7 +306,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 1 + 'LITERAL': 2.718 } } } @@ -312,7 +320,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 1 + 'LITERAL': 25 } } } @@ -405,11 +413,25 @@ export const toolbox = { }, { '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', @@ -417,25 +439,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', @@ -706,7 +729,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', From f73b0d9a05500c9878af8d2d33bbd6779a2e946f Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Fri, 2 Aug 2024 10:06:33 -0400 Subject: [PATCH 12/35] fix assignment (alone, in loops, and arrays); tested every block to eliminate console errors --- src/ast.js | 2 +- src/blocks2tree.js | 6 ++-- src/text2tree.js | 69 ++++++++++++++++++++++++++++++++------------ src/tree2blocks.js | 72 +++++++++++++++++++++++++++++++--------------- src/tree2text.js | 4 ++- 5 files changed, 106 insertions(+), 47 deletions(-) diff --git a/src/ast.js b/src/ast.js index c4aeccd..18e75aa 100644 --- a/src/ast.js +++ b/src/ast.js @@ -345,7 +345,7 @@ export function createExecutable(tree) { 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); + return new Praxly_array_reference_assignment(tree.location.name, createExecutable(tree.location.index), createExecutable(tree.value), tree); case 'INVALID': return new Praxly_invalid(tree); diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 2b4e606..1af5a8c 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -405,12 +405,12 @@ export const makeGenerator = () => { }) } - // Note: declaration and assignment + // 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, @@ -483,7 +483,7 @@ export const makeGenerator = () => { }) } - // declaration and assignment (Ex: in a for loop) + // declaration and assignment (likely in a for loop) praxlyGenerator['praxly_assignment_expression_block'] = (block) => { var varType = block.getFieldValue('VARTYPE'); var variableName = block.getFieldValue('VARIABLENAME'); diff --git a/src/text2tree.js b/src/text2tree.js index e188db6..9d4fc8e 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -225,10 +225,12 @@ class Lexer { this.emit_token(NODETYPES.INT); continue; } + if (this.has(' ')) { this.skip(); continue; } + if (this.has("\n")) { this.capture(); this.emit_token("\n"); @@ -237,21 +239,26 @@ class Lexer { this.startToken = [this.currentLine, this.i - this.index_before_this_line]; continue; } + if (!this.has_letter()) { textError('lexing', `unrecognized character \"${this.source[this.i]}\"`, this.i, this.i + 1); break; } + while (this.i < this.length && (this.has_letter() || this.has_digit())) { this.capture(); } + if (this.has_type()) { this.emit_token('Type'); continue; } + if (this.has_builtin()) { this.emit_token(NODETYPES.BUILTIN_FUNCTION_CALL); continue; } + if (this.token_so_far === 'end') { while (this.hasNot('\n')) { this.capture(); @@ -259,21 +266,25 @@ class Lexer { this.emit_token(); continue; } + if (this.has_boolean()) { this.emit_token(NODETYPES.BOOLEAN); continue; } + if (this.has_keyword()) { this.emit_token(); continue; } this.emit_token('Location'); } + this.emit_token("EOF"); return this.tokens; } } + class Parser { constructor(tokens) { @@ -304,7 +315,6 @@ class Parser { 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); @@ -325,6 +335,7 @@ class Parser { has_keyword() { return this.keywords.includes(this.tokens[this.i].token_type); } + has_statementKeyword() { return this.statementKeywords.includes(this.tokens[this.i].token_type); } @@ -349,8 +360,6 @@ class Parser { return this.parse_program(); } - - /** * This function creates new nodes for the AST for any binary operation * @param {*} operation the operation symbol @@ -425,13 +434,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 @@ -473,7 +480,7 @@ class Parser { value: expression, type: type, startIndex: startIndex, - endIndex: expression.endIndex, + endIndex: expression?.endIndex, } } @@ -488,6 +495,7 @@ class Parser { let startIndex = this.getCurrentToken().startIndex; let endIndex = this.getCurrentToken().endIndex; switch (precedence) { + case 9: var l = this.parse_expression(precedence - 1); while (this.has("or")) { @@ -498,6 +506,7 @@ class Parser { l = this.binaryOpNode_new(operation, l, r, line); } return l; + case 8: var l = this.parse_expression(precedence - 1); while (this.has("and")) { @@ -519,6 +528,7 @@ class Parser { l = this.binaryOpNode_new(operation, l, r, line); } return l; + case 6: var l = this.parse_expression(precedence - 1); while (this.hasAny('+', '-')) { @@ -529,6 +539,7 @@ class Parser { l = this.binaryOpNode_new(operation, l, r, line); } return l; + case 5: var l = this.parse_expression(precedence - 1); while (this.hasAny('*', '/', '%')) { @@ -539,6 +550,7 @@ class Parser { l = this.binaryOpNode_new(operation, l, r, line); } return l; + case 4: var l = this.parse_expression(precedence - 1); while (this.hasAny('^')) { @@ -581,7 +593,7 @@ class Parser { } return l; - //This one gets really complicated + // This one gets really complicated case 1: switch (operation) { case 'EOF': @@ -619,7 +631,7 @@ class Parser { 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', @@ -653,7 +665,7 @@ class Parser { this.advance(); var value = this.parse_expression(9); l = { - type: NODETYPES.ASSIGNMENT, + type: l.isArray ? NODETYPES.ARRAY_REFERENCE_ASSIGNMENT : NODETYPES.ASSIGNMENT, blockID: "code", line: line, location: l, @@ -820,7 +832,6 @@ class Parser { stopLoop += 1; } - result.endIndex = this.getCurrentToken().endIndex; this.match_and_discard_next_token(')'); result.params = params; @@ -845,8 +856,6 @@ class Parser { return result; } - - parse_program() { return { type: "PROGRAM", @@ -856,7 +865,7 @@ 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 */ @@ -924,20 +933,37 @@ class Parser { textError('parsing', "missing the \'end if\' token", result.line); } } + } - } else if (this.has('for')) { + else if (this.has('for')) { result.type = NODETYPES.FOR; this.advance(); if (this.hasNot('(')) { return result; } this.advance(); + if (this.has(')') || this.has('\n')) { + this.advance(); + return result; // incomplete + } + result.initialization = this.parse_statement(); - result.initialization.endIndex[1] -= 3; // HACK - result.condition = this.parse_expression(9); + if (result.initialization.endIndex) { + result.initialization.endIndex[1] -= 3; // HACK for debug highlighting + } + if (this.has(')') || this.has('\n')) { + this.advance(); + return result; + } + result.condition = this.parse_expression(9); + if (this.has(')') || this.has('\n')) { + this.advance(); + return result; + } if (this.has(';')) { this.advance(); + result.increment = this.parse_expression(1); if (this.hasNot(')')) { return result; @@ -1098,11 +1124,16 @@ class Parser { } else { - // this is a stand alone expression as a statement. + // most likely an expression statement let contents = this.parse_expression(9); if (this.has(';')) { this.advance(); } + // 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, value: contents, diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 137e4f9..236b589 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -185,7 +185,7 @@ export const tree2blocks = (workspace, node) => { return tree2blocks(workspace, element); } catch (error) { - console.error('empty statement', error); + console.error('invalid statement', error); return null; } }); @@ -238,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: @@ -252,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"); @@ -366,43 +371,64 @@ 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 { + // was likely praxly_assignment_block initialization.dispose(); - var initialization = workspace.newBlock('praxly_assignment_expression_block'); - var expression = tree2blocks(workspace, node?.initialization?.value); + initialization = workspace.newBlock('praxly_assignment_expression_block'); initialization.setFieldValue(node?.initialization?.varType, "VARTYPE"); initialization.setFieldValue(node?.initialization?.name, "VARIABLENAME"); + var expression = tree2blocks(workspace, node?.initialization?.value); 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); + 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 codeblocks = tree2blocks(workspace, node?.statement); + // 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); + container2?.dispose(); if (codeblocks && codeblocks.length > 0) { result.getInput('CODEBLOCK').connection.connect(codeblocks[0]?.previousConnection); } } catch (error) { - console.error('could not generate 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; @@ -419,8 +445,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 900d91a..c7fd4d1 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -248,12 +248,14 @@ export const tree2text = (node, indentation) => { ' '.repeat(indentation) + 'end if\n'; return result + condition + contents + alternative; + // Note: reassignment (either a statement or in a for loop) case NODETYPES.ASSIGNMENT: var varname = tree2text(node.location, node.endIndex, indentation); var operator = ' ← '; var expression = tree2text(node.value, node.endIndex, indentation); return varname + operator + expression + '\n'; + // Note: declaration and assignment (possibly in a for loop) case NODETYPES.VARDECL: try { var vartype = node.varType.toString(); @@ -304,7 +306,7 @@ export const tree2text = (node, indentation) => { initialization = initialization.replace("\n", "") + '; '; var condition = tree2text(node.condition, 0) + '; '; var increment = tree2text(node.increment, 0); - increment = increment + ")\n"; + increment = increment.replace("\n", "") + ')\n'; var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1) + ' '.repeat(indentation) + 'end for\n'; return result + initialization + condition + increment + contents; From 711ac9cb56ccdc93daa8cce6c6688264428b6220 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Fri, 2 Aug 2024 10:10:48 -0400 Subject: [PATCH 13/35] adding more blocks for string functions --- src/blocks2tree.js | 28 ++++++++++++++ src/newBlocks.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++ src/toolbox.js | 36 ++++++++++++++++++ src/tree2blocks.js | 22 +++++++++-- 4 files changed, 178 insertions(+), 3 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 24de151..9f951e8 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -683,5 +683,33 @@ export const makeGenerator = () => { }); } + 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), + }); + } + return praxlyGenerator; } diff --git a/src/newBlocks.js b/src/newBlocks.js index 0930a6b..3b3517a 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -654,6 +654,101 @@ export function definePraxlyBlocks(workspace) { "tooltip": "Returns true if value 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": 'other_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": 'other_blocks', + "tooltip": "Returns the length of the string", + "helpUrl": "" + }, + { // text 5 + "type": "praxly_substring_block", + "message0": "%1.substringÍ (%2)", + "args0": [ + { + "type": "input_value", + "name": "EXPRESSION" + }, + { + "type": "input_value", + "name": "PARAM", + "text": "params" + }, + ], + "inputsInline": true, + "output": null, + "style": 'other_blocks', + "tooltip": "Returns the first index of substring value, or -1 if not found", + "helpUrl": "" + }, + { // text 6 + "type": "praxly_toLowerCase_block", + "message0": "%1.toLowerCase (%2)", + "args0": [ + { + "type": "input_value", + "name": "EXPRESSION" + }, + { + "type": "input_value", + "name": "PARAM", + "text": "params" + }, + ], + "inputsInline": true, + "output": null, + "style": 'other_blocks', + "tooltip": "Returns the first index of substring value, or -1 if not found", + "helpUrl": "" + }, + { // text 7 + "type": "praxly_toUpperCase_block", + "message0": "%1.toUpperCase (%2)", + "args0": [ + { + "type": "input_value", + "name": "EXPRESSION" + }, + { + "type": "input_value", + "name": "PARAM", + "text": "params" + }, + ], + "inputsInline": true, + "output": null, + "style": 'other_blocks', + "tooltip": "Returns the first index of substring value, or -1 if not found", + "helpUrl": "" + }, { // logic 1 "type": "praxly_true_block", "message0": "true", diff --git a/src/toolbox.js b/src/toolbox.js index d4758c3..64f2cf7 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -395,6 +395,42 @@ export const toolbox = { } } } + }, + { + 'kind': 'block', + 'type': 'praxly_indexOf_block', + 'inputs': { + 'EXPRESSION': { + 'shadow': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': '\"hello, world\"', + } + }, + }, + 'PARAM': { + 'block': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': 'value' + } + } + } + } + }, + { + 'kind': 'block', + 'type': 'praxly_length_block', + 'inputs': { + 'EXPRESSION': { + 'shadow': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': '\"hello, world\"', + } + }, + } + } } ] }, diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 4dd6ace..485630a 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++) { @@ -330,16 +330,32 @@ export const tree2blocks = (workspace, node) => { if (node.name === 'charAt') { result = workspace.newBlock('praxly_charAt_block'); - var recipient = tree2blocks(workspace, node.left); // left side + var recipient = tree2blocks(workspace, node.left); const child = tree2blocks(workspace, node?.args); result.getInput('INDEX').connection.connect(child?.outputConnection); result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); } else if (node.name === 'contains') { result = workspace.newBlock('praxly_contains_block'); - var recipient = tree2blocks(workspace, node.left); // left side + var recipient = tree2blocks(workspace, node.left); const child = tree2blocks(workspace, node?.args); result.getInput('PARAM').connection.connect(child?.outputConnection); result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + } else if (node.name === 'indexOf') { + result = workspace.newBlock('praxly_indexOf_block'); + var recipient = tree2blocks(workspace, node.left); + const child = tree2blocks(workspace, node?.args); + result.getInput('PARAM').connection.connect(child?.outputConnection); + result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + } else if (node.name === 'length') { + result = workspace.newBlock('praxly_indexOf_block'); + var recipient = tree2blocks(workspace, node.left); + result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); + } else if (node.name === 'substring') { + + } else if (node.name === StringFuncs.TOLOWERCSE) { + + } else if (node.name === StringFuncs.TOUPPERCASE) { + } From b3d461372082b0b10d1e10b6dab93a833782312f Mon Sep 17 00:00:00 2001 From: ellonamac Date: Fri, 2 Aug 2024 10:55:00 -0400 Subject: [PATCH 14/35] string functions fully split and working BUT dissapearing~ --- src/blocks2tree.js | 52 ++++++++++++++++++++++++++++++++++++++++ src/newBlocks.js | 33 +++++++++++-------------- src/toolbox.js | 60 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 20 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 9f951e8..455c4d5 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -708,6 +708,58 @@ export const makeGenerator = () => { 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: [] + } }); } diff --git a/src/newBlocks.js b/src/newBlocks.js index 3b3517a..ed9a2fb 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -691,7 +691,7 @@ export function definePraxlyBlocks(workspace) { }, { // text 5 "type": "praxly_substring_block", - "message0": "%1.substringÍ (%2)", + "message0": "%1.substring (%2 , %3)", "args0": [ { "type": "input_value", @@ -699,54 +699,49 @@ export function definePraxlyBlocks(workspace) { }, { "type": "input_value", - "name": "PARAM", + "name": "PARAM1", "text": "params" }, + { + "type": "input_value", + "name": "PARAM2", + "text": "params" + } ], "inputsInline": true, "output": null, "style": 'other_blocks', - "tooltip": "Returns the first index of substring value, or -1 if not found", + "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 (%2)", + "message0": "%1.toLowerCase ( )", "args0": [ { "type": "input_value", "name": "EXPRESSION" - }, - { - "type": "input_value", - "name": "PARAM", - "text": "params" - }, + } ], "inputsInline": true, "output": null, "style": 'other_blocks', - "tooltip": "Returns the first index of substring value, or -1 if not found", + "tooltip": "Converts the string to all lowercase", "helpUrl": "" }, { // text 7 "type": "praxly_toUpperCase_block", - "message0": "%1.toUpperCase (%2)", + "message0": "%1.toUpperCase ( )", "args0": [ { "type": "input_value", "name": "EXPRESSION" - }, - { - "type": "input_value", - "name": "PARAM", - "text": "params" - }, + } ], "inputsInline": true, "output": null, "style": 'other_blocks', - "tooltip": "Returns the first index of substring value, or -1 if not found", + "tooltip": "Converts the string to all uppercase", "helpUrl": "" }, { // logic 1 diff --git a/src/toolbox.js b/src/toolbox.js index 64f2cf7..1419a24 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -431,7 +431,65 @@ export const toolbox = { }, } } - } + }, + { + 'kind': 'block', + 'type': 'praxly_substring_block', + 'inputs': { + 'EXPRESSION': { + 'shadow': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': '\"hello, world\"', + } + }, + }, + '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, world\"', + } + }, + }, + } + }, + { + 'kind': 'block', + 'type': 'praxly_toUpperCase_block', + 'inputs': { + 'EXPRESSION': { + 'shadow': { + 'type': 'praxly_literal_block', + 'fields': { + 'LITERAL': '\"hello, world\"', + } + }, + }, + } + }, ] }, { From dd53f9c54a8b7cae9fe929257168affb2660f7ad Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Fri, 2 Aug 2024 11:05:22 -0400 Subject: [PATCH 15/35] always call tree2text with two arguments; remove indent in expressions; fixes #13 --- src/main.js | 2 +- src/tree2text.js | 122 +++++++++++++++++++++++------------------------ 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/main.js b/src/main.js index 617c7a3..b9e880a 100644 --- a/src/main.js +++ b/src/main.js @@ -558,7 +558,7 @@ function turnBlocksToCode() { } // update text side to match - const text = tree2text(mainTree, 0, 0); + const text = tree2text(mainTree, 0); textEditor.setValue(text, -1); }; diff --git a/src/tree2text.js b/src/tree2text.js index c7fd4d1..8eeb54b 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -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.args[0], indentation); + const max = tree2text(node.args[0], 0); return `randomInt(${max})`; } else if (node.name === 'randomSeed') { - const seed = tree2text(node.args[0], indentation); + const seed = tree2text(node.args[0], 0); return `randomSeed(${seed})`; } else if (node.name === 'int') { - const conversion = tree2text(node.args[0], indentation); + const conversion = tree2text(node.args[0], 0); return `int(${conversion})`; } else if (node.name === 'float') { - const conversion = tree2text(node.args[0], indentation); + const conversion = tree2text(node.args[0], 0); return `float(${conversion})`; } else if (node.name === 'min') { - const a_value = tree2text(node.args[0], indentation); - const b_value = tree2text(node.args[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.args[0], indentation); - const b_value = tree2text(node.args[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.args[0], indentation); + const value = tree2text(node.args[0], 0); return `abs(${value})`; } else if (node.name === 'log') { - const value = tree2text(node.args[0], indentation); + const value = tree2text(node.args[0], 0); return `log(${value})`; } else if (node.name = 'sqrt') { - const value = tree2text(node.args[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: @@ -241,7 +241,7 @@ export const tree2text = (node, indentation) => { case NODETYPES.IF_ELSE: var result = ' '.repeat(indentation) + "if ("; - var condition = tree2text(node.condition, indentation) + ")\n"; + var condition = tree2text(node.condition, 0) + ")\n"; var contents = tree2text(node.statement, indentation + 1); var alternative = ' '.repeat(indentation) + '\else\n' + tree2text(node.alternative, indentation + 1) + @@ -250,9 +250,9 @@ export const tree2text = (node, indentation) => { // 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); + var expression = tree2text(node.value, 0); return varname + operator + expression + '\n'; // Note: declaration and assignment (possibly in a for loop) @@ -262,7 +262,7 @@ export const tree2text = (node, indentation) => { 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'; @@ -273,7 +273,7 @@ export const tree2text = (node, indentation) => { case NODETYPES.WHILE: var result = ' '.repeat(indentation) + "while"; - var condition = " (" + tree2text(node.condition, indentation) + ")\n"; + var condition = " (" + tree2text(node.condition, 0) + ")\n"; var contents = tree2text(node.statement, indentation + 1) + ' '.repeat(indentation) + 'end while\n'; return result + condition + contents; @@ -281,23 +281,23 @@ export const tree2text = (node, indentation) => { 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"; + var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, 0) + ")\n"; return result + contents + 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"; + var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, 0) + ")\n"; return result + contents + 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: @@ -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,7 +379,7 @@ 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); } @@ -391,10 +391,10 @@ export const tree2text = (node, indentation) => { 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 " "; From f4a576488d5619b14c2cc16ceb341e3f49a8f879 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Fri, 2 Aug 2024 11:36:44 -0400 Subject: [PATCH 16/35] fix array reference/assignment; add bounds checking --- src/ast.js | 65 +++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/src/ast.js b/src/ast.js index 18e75aa..b7fdd74 100644 --- a/src/ast.js +++ b/src/ast.js @@ -231,8 +231,9 @@ export function createExecutable(tree) { } 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) { return null; @@ -248,7 +249,7 @@ 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) { return null; @@ -344,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.location.name, createExecutable(tree.location.index), createExecutable(tree.value), tree); - case 'INVALID': return new Praxly_invalid(tree); @@ -358,21 +356,6 @@ export function createExecutable(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); - } -} - class Praxly_single_line_comment { constructor(value, node) { @@ -1224,20 +1207,27 @@ function typeCoercion(varType, praxlyObj, 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: ` @@ -1246,7 +1236,6 @@ class Praxly_assignment { 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; @@ -1313,7 +1302,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; @@ -1329,25 +1318,11 @@ class Praxly_array_assignment { } 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 environment.variableList[this.name]; - } -} - class Praxly_Location { constructor(json, index) { @@ -1364,12 +1339,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); } From 7dd989996ec2cab77bf1d4e0a78f01c483f01163 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Fri, 2 Aug 2024 12:37:59 -0400 Subject: [PATCH 17/35] finished and tested string methods blocks --- src/ast.js | 8 ++-- src/blocks2tree.js | 2 +- src/text2tree.js | 2 +- src/toolbox.js | 16 ++++---- src/tree2blocks.js | 92 ++++++++++++++++++++++++---------------------- 5 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/ast.js b/src/ast.js index 178298b..92ca86f 100644 --- a/src/ast.js +++ b/src/ast.js @@ -1693,12 +1693,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); diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 455c4d5..2faeafc 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -694,7 +694,7 @@ export const makeGenerator = () => { left: praxlyGenerator[expression.type](expression), right: { name: procedureName, - args: [[praxlyGenerator[param.type](param)]], + args: [praxlyGenerator[param.type](param)], type: NODETYPES.FUNCCALL } }); diff --git a/src/text2tree.js b/src/text2tree.js index 5e7714d..d052a86 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -566,7 +566,7 @@ class Parser { line = this.getCurrentToken().line; this.advance(); const r = this.parse_expression(precedence); - if (r.type != NODETYPES.FUNCCALL) { + 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); } l = { diff --git a/src/toolbox.js b/src/toolbox.js index 1419a24..fe68f64 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!\"', } }, }, @@ -360,7 +360,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, @@ -382,7 +382,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, @@ -404,7 +404,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, @@ -426,7 +426,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, } @@ -440,7 +440,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, @@ -470,7 +470,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, @@ -484,7 +484,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello, world\"', + 'LITERAL': '\"hello\"', } }, }, diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 485630a..e76ffa5 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -327,54 +327,60 @@ export const tree2blocks = (workspace, node) => { break; case NODETYPES.SPECIAL_STRING_FUNCCALL: - - if (node.name === 'charAt') { - result = workspace.newBlock('praxly_charAt_block'); - var recipient = tree2blocks(workspace, node.left); - const child = tree2blocks(workspace, node?.args); - result.getInput('INDEX').connection.connect(child?.outputConnection); - result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - } else if (node.name === 'contains') { - result = workspace.newBlock('praxly_contains_block'); - var recipient = tree2blocks(workspace, node.left); - const child = tree2blocks(workspace, node?.args); - result.getInput('PARAM').connection.connect(child?.outputConnection); - result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - } else if (node.name === 'indexOf') { - result = workspace.newBlock('praxly_indexOf_block'); - var recipient = tree2blocks(workspace, node.left); - const child = tree2blocks(workspace, node?.args); - result.getInput('PARAM').connection.connect(child?.outputConnection); - result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - } else if (node.name === 'length') { + 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'); - var recipient = tree2blocks(workspace, node.left); - result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - } else if (node.name === 'substring') { - - } else if (node.name === StringFuncs.TOLOWERCSE) { - - } else if (node.name === StringFuncs.TOUPPERCASE) { - + 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 } - - // 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); - // } - // result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); - // params.initSvg(); + // connect the string on the left of the result block + var recipient = tree2blocks(workspace, node.left); + result.getInput("EXPRESSION").connection.connect(recipient.outputConnection); break; - case NODETYPES.FUNCDECL: var returnType = node?.returnType; var argsList = node?.params; From 48776bbf7c1752f8bebfaeaab3b01ee92802ae56 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Fri, 2 Aug 2024 12:39:41 -0400 Subject: [PATCH 18/35] temporary color changes --- src/newBlocks.js | 40 ++++++++++++++++++++-------------------- src/theme.js | 18 ++++++++++++------ src/toolbox.js | 20 ++++++++++---------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/newBlocks.js b/src/newBlocks.js index ed9a2fb..ed6fdd3 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -354,7 +354,7 @@ export function definePraxlyBlocks(workspace) { } ], "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "A literal value in the code", "helpUrl": "" }, @@ -408,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": "", }, @@ -425,7 +425,7 @@ export function definePraxlyBlocks(workspace) { } ], "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Negates a value", "helpUrl": "" }, @@ -434,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": "" }, @@ -449,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": "" }, @@ -464,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": "" }, @@ -479,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": "" }, @@ -494,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": "" }, @@ -513,7 +513,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Returns the lower value", "helpURL": "" }, @@ -532,7 +532,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Returns the higher value", "helpURL": "" }, @@ -547,7 +547,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Returns the absolute value", "helpURL": "" }, @@ -562,7 +562,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Calculates the natural logarithm", "helpURL": "" }, @@ -577,7 +577,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'expression_blocks', + "style": 'math_blocks', "tooltip": "Calculates the square root", "helpURL": "" }, @@ -630,7 +630,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Returns the character at index i", "helpUrl": "" }, @@ -650,7 +650,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Returns true if value is a substring", "helpUrl": "" }, @@ -670,7 +670,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Returns the first index of substring value, or -1 if not found", "helpUrl": "" }, @@ -685,7 +685,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Returns the length of the string", "helpUrl": "" }, @@ -710,7 +710,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Extracts characters from the start index up to but not including the end index", "helpUrl": "" }, @@ -725,7 +725,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Converts the string to all lowercase", "helpUrl": "" }, @@ -740,7 +740,7 @@ export function definePraxlyBlocks(workspace) { ], "inputsInline": true, "output": null, - "style": 'other_blocks', + "style": 'text_blocks', "tooltip": "Converts the string to all uppercase", "helpUrl": "" }, diff --git a/src/theme.js b/src/theme.js index a003568..7a7ae9d 100644 --- a/src/theme.js +++ b/src/theme.js @@ -69,7 +69,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#FA0000' }, 'logic_blocks': { - 'colourPrimary': '#00D084' + 'colourPrimary': '#3CB371' }, 'class_blocks': { 'colourPrimary': '#6381fe' @@ -83,14 +83,17 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'variable_blocks': { 'colourPrimary': '#f80069' }, - 'expression_blocks': { - 'colourPrimary': '#a7ca00' + 'math_blocks': { + 'colourPrimary': '#4B0082' }, 'parameter_blocks': { 'colourPrimary': '#8F48B7' }, 'other_blocks': { 'colourPrimary': '#00BFFF' + }, + 'text_blocks': { + 'colourPrimary': '#1E90FF' } }, 'categoryStyles': { @@ -101,7 +104,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#6381fe' }, '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': '#4B0082' + }, + 'text_blocks': { + 'colour': '#1E90FF' } }, 'componentStyles': { diff --git a/src/toolbox.js b/src/toolbox.js index 1419a24..f920e78 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -134,7 +134,7 @@ export const toolbox = { { "kind": "category", "name": "math", - "categorystyle": "expression_blocks", + "categorystyle": "math_blocks", "contents": [ { 'kind': 'block', @@ -331,7 +331,7 @@ export const toolbox = { { "kind": "category", "name": "text", - "categorystyle": "class_blocks", + "categorystyle": "text_blocks", "contents": [ // { // 'kind': 'block', @@ -497,14 +497,6 @@ export const toolbox = { "name": "logic", "categorystyle": "logic_blocks", "contents": [ - { - 'kind': 'block', - 'type': 'praxly_true_block' - }, - { - 'kind': 'block', - 'type': 'praxly_false_block' - }, { 'kind': 'block', 'type': 'praxly_if_block', @@ -575,6 +567,14 @@ export const toolbox = { }, } } + }, + { + 'kind': 'block', + 'type': 'praxly_true_block' + }, + { + 'kind': 'block', + 'type': 'praxly_false_block' } ] }, From 25b05494b14ed28dc242f4eee901eab0db1744e0 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Fri, 2 Aug 2024 13:46:05 -0400 Subject: [PATCH 19/35] text select works on variables and output, updated text function blocks and slight tooltip changes --- archive/style.css | 1 + public/themes.css | 1 + src/newBlocks.js | 4 ++-- src/toolbox.js | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) 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/public/themes.css b/public/themes.css index 0031732..c57af4f 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 { diff --git a/src/newBlocks.js b/src/newBlocks.js index ed6fdd3..bab3101 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -631,7 +631,7 @@ export function definePraxlyBlocks(workspace) { "inputsInline": true, "output": null, "style": 'text_blocks', - "tooltip": "Returns the character at index i", + "tooltip": "Returns the character at the index", "helpUrl": "" }, { // text 2 @@ -651,7 +651,7 @@ export function definePraxlyBlocks(workspace) { "inputsInline": true, "output": null, "style": 'text_blocks', - "tooltip": "Returns true if value is a substring", + "tooltip": "Returns true if string is a substring", "helpUrl": "" }, { // text 3 diff --git a/src/toolbox.js b/src/toolbox.js index a52b92a..787d569 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -368,7 +368,7 @@ export const toolbox = { 'block': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 0 + 'LITERAL': 'index' } } } @@ -390,7 +390,7 @@ export const toolbox = { 'block': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 'value' + 'LITERAL': '"string"' } } } @@ -412,7 +412,7 @@ export const toolbox = { 'block': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': 'value' + 'LITERAL': 'string' } } } From f1f6dfdcd3b3ad96108fdb9324ede2bd12e184c4 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Fri, 2 Aug 2024 14:37:05 -0400 Subject: [PATCH 20/35] new colors --- src/theme.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/theme.js b/src/theme.js index 7a7ae9d..a670ae2 100644 --- a/src/theme.js +++ b/src/theme.js @@ -78,13 +78,13 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#808080' }, 'procedure_blocks': { - 'colourPrimary': '#6381fe' + 'colourPrimary': '#4B0082' }, 'variable_blocks': { 'colourPrimary': '#f80069' }, 'math_blocks': { - 'colourPrimary': '#4B0082' + 'colourPrimary': '#DAA520' }, 'parameter_blocks': { 'colourPrimary': '#8F48B7' @@ -101,7 +101,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#395BBF' }, 'procedure_blocks': { - 'colour': '#6381fe' + 'colour': '#4B0082' }, 'logic_blocks': { 'colour': '#3CB371' @@ -119,7 +119,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#f80069' }, 'math_blocks': { - 'colour': '#4B0082' + 'colour': '#DAA520' }, 'text_blocks': { 'colour': '#1E90FF' From f9dbc526d67c4f4f3ff45089c761c8582508092f Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Fri, 2 Aug 2024 15:04:05 -0400 Subject: [PATCH 21/35] the first 40 tests are passing now --- src/text2tree.js | 11 ++++++- src/tree2text.js | 2 +- test/canvas.csv | 84 ++++++++++++++++++++++++++++-------------------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index 9d4fc8e..6a10bb5 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -795,13 +795,15 @@ class Parser { index: null, startIndex: startIndex, } + if (this.hasAny('=', '<-', "←", "⟵")) { this.advance(); result.value = this.parse_expression(9); if (this.has(';')) { this.advance(); } - } else if (this.has('(')) { + } + else if (this.has('(')) { result.type = NODETYPES.FUNCDECL; result.returnType = vartype; if (type == NODETYPES.ARRAY_ASSIGNMENT) { @@ -852,6 +854,13 @@ class Parser { } this.advance(); } + else { + // variable declaration without assignment + if (this.has(';')) { + this.advance(); + } + } + result.endIndex = this.getCurrentToken().endIndex; return result; } diff --git a/src/tree2text.js b/src/tree2text.js index 8eeb54b..a4d3942 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -253,7 +253,7 @@ export const tree2text = (node, indentation) => { var varname = tree2text(node.location, indentation); var operator = ' ← '; var expression = tree2text(node.value, 0); - return varname + operator + expression + '\n'; + return ' '.repeat(indentation) + varname + operator + expression + '\n'; // Note: declaration and assignment (possibly in a for loop) case NODETYPES.VARDECL: diff --git a/test/canvas.csv b/test/canvas.csv index 33a3317..89a46d1 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()) From 361ae45b62016bc2d42a58b0b42a81dd8adec05b Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sat, 3 Aug 2024 12:58:49 -0400 Subject: [PATCH 22/35] add comments to lexer/parser to make debugging easier --- src/text2tree.js | 75 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index 6a10bb5..b47aa1f 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 . @@ -134,6 +134,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')) { @@ -145,6 +146,7 @@ class Lexer { continue; } + // missing code (/* comment */) if (this.has_long_comment()) { this.skip(2); while (this.hasNot('*') && this.hasNot_ahead('/')) { @@ -159,6 +161,7 @@ class Lexer { continue; } + // character literal (single quotes) if (this.has('\'')) { this.skip(); while (this.i < this.length && !this.has("\'") && !this.has("\n")) { @@ -180,6 +183,7 @@ class Lexer { continue; } + // string literal (double quotes) if (this.has("\"")) { this.skip(); while (this.i < this.length && !this.has("\"") && !this.has("\n")) { @@ -196,12 +200,12 @@ class Lexer { 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(); @@ -210,6 +214,7 @@ class Lexer { continue; } + // integers and floating-points if (this.has_digit()) { while (this.i < this.length && this.has_digit()) { this.capture(); @@ -226,11 +231,13 @@ class Lexer { continue; } + // ignore spaces if (this.has(' ')) { this.skip(); continue; } + // newline (increment line number) if (this.has("\n")) { this.capture(); this.emit_token("\n"); @@ -240,25 +247,26 @@ class Lexer { continue; } + // words if (!this.has_letter()) { textError('lexing', `unrecognized character \"${this.source[this.i]}\"`, this.i, this.i + 1); break; } - 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; } - if (this.has_builtin()) { this.emit_token(NODETYPES.BUILTIN_FUNCTION_CALL); continue; } + // end keyword (if, for, while, proc) if (this.token_so_far === 'end') { while (this.hasNot('\n')) { this.capture(); @@ -267,18 +275,23 @@ class Lexer { 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; } @@ -466,7 +479,6 @@ class Parser { unaryOPNode_new(operation, expression, line, startIndex) { var type; switch (operation) { - case '!': case 'not': type = OP.NOT; break; @@ -493,9 +505,9 @@ class Parser { let operation = this.getCurrentToken().token_type; let line = this.tokens[this.i].line; 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")) { @@ -507,6 +519,7 @@ class Parser { } return l; + // and logical operator case 8: var l = this.parse_expression(precedence - 1); while (this.has("and")) { @@ -518,6 +531,7 @@ class Parser { } return l; + // relational operators case 7: var l = this.parse_expression(precedence - 1); while (this.hasAny('<', '>', '==', '!=', '>=', '<=', '≠', '≥', '≤')) { @@ -529,6 +543,7 @@ class Parser { } return l; + // addition, subtraction case 6: var l = this.parse_expression(precedence - 1); while (this.hasAny('+', '-')) { @@ -540,6 +555,7 @@ class Parser { } return l; + // multiplication, division, modulo case 5: var l = this.parse_expression(precedence - 1); while (this.hasAny('*', '/', '%')) { @@ -551,6 +567,7 @@ class Parser { } return l; + // raise to the power case 4: var l = this.parse_expression(precedence - 1); while (this.hasAny('^')) { @@ -562,8 +579,9 @@ class Parser { } 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; @@ -572,7 +590,7 @@ class Parser { 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('.')) { @@ -600,6 +618,7 @@ class Parser { this.eof = true; return 'EOF'; + // literal value case NODETYPES.BOOLEAN: case NODETYPES.CHAR: case NODETYPES.DOUBLE: @@ -610,10 +629,12 @@ class Parser { this.advance(); return this.literalNode_new(this.tokens[this.i - 1]); + // function call 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); @@ -659,6 +680,7 @@ class Parser { this.advance(); return result; + // variable assignment or procedure call case 'Location': var l = this.parse_location(); if (this.hasAny('=', '<-', "←", "⟵")) { @@ -703,12 +725,10 @@ class Parser { 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); - } - } + + } // switch (operation) + } // switch (precedence) } parse_builtin_function_call(line) { @@ -905,12 +925,15 @@ class Parser { 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('(')) { @@ -944,6 +967,7 @@ class Parser { } } + // for loop else if (this.has('for')) { result.type = NODETYPES.FOR; this.advance(); @@ -994,6 +1018,7 @@ class Parser { return result; } + // while loop else if (this.has('while')) { result.type = NODETYPES.WHILE; this.advance(); @@ -1019,6 +1044,7 @@ class Parser { } } + // do-while loop else if (this.has('do')) { result.type = NODETYPES.DO_WHILE; this.advance(); @@ -1041,6 +1067,7 @@ class Parser { } } + // repeat-until loop else if (this.has('repeat')) { result.type = NODETYPES.REPEAT_UNTIL; this.advance(); @@ -1063,6 +1090,7 @@ class Parser { } } + // print statement else if (this.has("print")) { this.advance(); const expression = this.parse_expression(9); @@ -1084,6 +1112,7 @@ class Parser { return result; } + // return statement else if (this.has("return")) { this.advance(); const expression = this.parse_expression(9); @@ -1095,20 +1124,25 @@ class Parser { 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(); @@ -1125,15 +1159,8 @@ class Parser { } } - else if (this.has('\n')) { - return { - type: NODETYPES.NEWLINE, - blockID: 'code', - } - } - + // most likely a function call else { - // most likely an expression statement let contents = this.parse_expression(9); if (this.has(';')) { this.advance(); From 783b9ee8db60a8edb59397cdd2df0e92391d77fe Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sat, 3 Aug 2024 15:36:38 -0400 Subject: [PATCH 23/35] [Parser] more comments; remove unused code; add getCurrentLine() --- src/text2tree.js | 73 ++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index b47aa1f..62ea9db 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -31,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 = []; @@ -247,7 +248,7 @@ class Lexer { continue; } - // words + // get the next word if (!this.has_letter()) { textError('lexing', `unrecognized character \"${this.source[this.i]}\"`, this.i, this.i + 1); break; @@ -301,14 +302,9 @@ class Lexer { 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) { @@ -319,6 +315,10 @@ class Parser { return this.tokens[this.i]; } + getCurrentLine() { + return this.tokens[this.i].line; + } + has(type) { return this.i < this.length && this.tokens[this.i].token_type === type; } @@ -345,19 +345,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()); } } @@ -503,7 +495,7 @@ 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; switch (precedence) { @@ -512,7 +504,7 @@ class Parser { 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); @@ -524,7 +516,7 @@ class Parser { 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); @@ -536,7 +528,7 @@ class Parser { 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); @@ -548,7 +540,7 @@ class Parser { 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); @@ -560,7 +552,7 @@ class Parser { 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); @@ -572,7 +564,7 @@ class Parser { 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); l = this.binaryOpNode_new(operation, l, r, line); @@ -585,7 +577,7 @@ class Parser { 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); @@ -595,7 +587,7 @@ class Parser { 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); if (r.type != NODETYPES.FUNCCALL) { @@ -615,7 +607,6 @@ class Parser { case 1: switch (operation) { case 'EOF': - this.eof = true; return 'EOF'; // literal value @@ -674,7 +665,7 @@ 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(); @@ -716,10 +707,6 @@ 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; @@ -774,7 +761,7 @@ class Parser { name: this.tokens[this.i].value, isArray: false, blockID: 'code', - line: this.tokens[this.i].line, + line: this.getCurrentLine(), index: null, startIndex: this.tokens[this.i].startIndex, } @@ -791,9 +778,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(']')) { @@ -803,6 +792,7 @@ class Parser { isArray = true; } + // variable/procedure name var location = this.parse_location(); var result = { type: type, @@ -811,11 +801,12 @@ class Parser { 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); @@ -823,12 +814,16 @@ class Parser { this.advance(); } } + + // 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; @@ -853,7 +848,6 @@ class Parser { } stopLoop += 1; } - result.endIndex = this.getCurrentToken().endIndex; this.match_and_discard_next_token(')'); result.params = params; @@ -865,6 +859,7 @@ class Parser { this.advance(); } + // procedure body var contents = this.parse_block('end ' + result.name); result.contents = contents; if (this.hasNot('end ' + result.name)) { @@ -899,26 +894,26 @@ class Parser { * @returns */ parse_block(...endToken) { - let praxly_blocks = []; + let block_statements = []; while (this.hasNotAny(...endToken)) { if (this.has('EOF')) { break; } - praxly_blocks.push(this.parse_statement()); + block_statements.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. this.advance(); } 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, From edc9085f33045187ef0c1c452ab96e33ea52b9c4 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sat, 3 Aug 2024 18:57:06 -0400 Subject: [PATCH 24/35] add parse_end_of_line() to handle semicolons, optional comment, and newline --- src/text2tree.js | 334 +++++++++++++++++++++++---------------------- src/tree2blocks.js | 17 ++- 2 files changed, 183 insertions(+), 168 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index 62ea9db..ce00bc1 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -150,14 +150,15 @@ class Lexer { // 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; } @@ -169,16 +170,14 @@ class Lexer { this.capture(); } 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); - this.token_so_far = "?"; + 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; } 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); - this.token_so_far = "?"; + textError('lexing', 'incorrect character length: ' + length, this.currentLine + 1); } this.emit_token(NODETYPES.CHAR); continue; @@ -191,8 +190,7 @@ class Lexer { this.capture(); } 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); - this.token_so_far = "?"; + 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; } @@ -250,8 +248,9 @@ class Lexer { // 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(); @@ -269,7 +268,15 @@ class Lexer { // 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(); @@ -319,6 +326,14 @@ class Parser { 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; } @@ -566,7 +581,7 @@ class Parser { operation = this.getCurrentToken().token_type; 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; @@ -589,9 +604,9 @@ class Parser { operation = this.getCurrentToken().token_type; 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, @@ -628,7 +643,7 @@ class Parser { // 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 { @@ -640,7 +655,7 @@ 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 @@ -656,7 +671,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(); @@ -676,7 +691,7 @@ class Parser { var l = this.parse_location(); if (this.hasAny('=', '<-', "←", "⟵")) { this.advance(); - var value = this.parse_expression(9); + var value = this.parse_expression(); l = { type: l.isArray ? NODETYPES.ARRAY_REFERENCE_ASSIGNMENT : NODETYPES.ASSIGNMENT, blockID: "code", @@ -690,7 +705,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(); @@ -712,7 +727,12 @@ class Parser { return l; default: - 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) @@ -727,11 +747,11 @@ class Parser { // Expect 0 or more arguments. const args = []; if (this.hasNot(')')) { - const parameter = this.parse_expression(9); + const parameter = this.parse_expression(); args.push(parameter); while (this.has(',')) { this.advance(); - const parameter = this.parse_expression(9); + const parameter = this.parse_expression(); args.push(parameter); } } @@ -756,9 +776,12 @@ class Parser { } 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.getCurrentLine(), @@ -797,7 +820,7 @@ class Parser { var result = { type: type, varType: vartype, - name: location.name, + name: location?.name, isArray: isArray, blockID: 'code', location: location, @@ -809,10 +832,7 @@ class Parser { // initialization (optional) if (this.hasAny('=', '<-', "←", "⟵")) { this.advance(); - result.value = this.parse_expression(9); - if (this.has(';')) { - this.advance(); - } + result.value = this.parse_expression(); } // procedure definition @@ -830,7 +850,7 @@ class Parser { 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(']')) { @@ -839,7 +859,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); @@ -852,28 +872,8 @@ class Parser { this.match_and_discard_next_token(')'); result.params = params; - if (this.has(';')) { - this.advance(); - } - if (this.has('\n')) { - this.advance(); - } - // procedure body - 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(); - } - else { - // variable declaration without assignment - if (this.has(';')) { - this.advance(); - } + result.contents = this.parse_block('end ' + result.name); } result.endIndex = this.getCurrentToken().endIndex; @@ -895,16 +895,37 @@ class Parser { */ parse_block(...endToken) { let block_statements = []; - while (this.hasNotAny(...endToken)) { - if (this.has('EOF')) { - break; + // parse end of line at beginning of the block + if (this.i > 0) { + let comment = this.parse_end_of_line(true); + if (comment) { + block_statements.push(this.comment); } - block_statements.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. + } + + // parse all statements inside the block + while (!this.has('EOF') && this.hasNotAny(...endToken)) { + let stmt = this.parse_statement(); + if (!stmt) { + break; // empty block + } + block_statements.push(stmt); + let comment = this.parse_end_of_line(false); + if (comment) { + block_statements.push(this.comment); + } + } + + // 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: block_statements, @@ -935,30 +956,17 @@ 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('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.statement = this.parse_block('else', 'end if'); + if (this.getPrevTokenType() == 'else') { + result.type = NODETYPES.IF_ELSE; + result.alternative = this.parse_block('end if'); } } @@ -970,46 +978,31 @@ class Parser { return result; } this.advance(); - if (this.has(')') || this.has('\n')) { - this.advance(); - return result; // incomplete - } - result.initialization = this.parse_statement(); - if (result.initialization.endIndex) { - result.initialization.endIndex[1] -= 3; // HACK for debug highlighting + if (this.has_type()) { + result.initialization = this.parse_funcdecl_or_vardecl(); + } else { + result.initialization = this.parse_expression(1); } - if (this.has(')') || this.has('\n')) { - this.advance(); + if (this.hasNot(';')) { // 1st required return result; } + this.advance(); - result.condition = this.parse_expression(9); - if (this.has(')') || this.has('\n')) { - this.advance(); + result.condition = this.parse_expression(); + if (this.hasNot(';')) { // 2nd required return result; } - if (this.has(';')) { - this.advance(); + 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); - } - } + result.increment = this.parse_expression(1); + if (this.hasNot(')')) { + return result; } + this.advance(); + + result.endIndex = this.getCurrentToken().endIndex; + result.statement = this.parse_block('end for'); return result; } @@ -1021,77 +1014,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.statement = 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.statement = 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.statement = 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(); } @@ -1110,15 +1082,10 @@ class Parser { // 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; } // missing code (/* comment */) @@ -1156,10 +1123,7 @@ class Parser { // most likely a function call else { - let contents = this.parse_expression(9); - 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")) { @@ -1175,4 +1139,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)) { + const token = this.advance(); + comment = { + type: NODETYPES.SINGLE_LINE_COMMENT, + value: token.value, + startIndex: token.startIndex, + endIndex: token.endIndex, + blockID: 'code', + line: 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/tree2blocks.js b/src/tree2blocks.js index 236b589..5271111 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -377,12 +377,19 @@ export const tree2blocks = (workspace, node) => { // unpack the expression statement var container1 = initialization; initialization = initialization.getInputTargetBlock('EXPRESSION'); - } else { - // was likely praxly_assignment_block + } else if (initialization.type == 'praxly_assignment_block' + || initialization.type == 'praxly_reassignment_block') { + // convert statement to expression initialization.dispose(); - initialization = workspace.newBlock('praxly_assignment_expression_block'); - initialization.setFieldValue(node?.initialization?.varType, "VARTYPE"); - initialization.setFieldValue(node?.initialization?.name, "VARIABLENAME"); + 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.getInput('EXPRESSION').connection.connect(expression?.outputConnection); initialization.initSvg(); From 0577d74c1b7aa3fb7ff5f8b5f1ed24cd295feaa1 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sat, 3 Aug 2024 19:18:55 -0400 Subject: [PATCH 25/35] rename statement to codeblock; fix comment bugs --- src/ast.js | 32 ++++++++++++++++---------------- src/text2tree.js | 19 ++++++++----------- src/tree2blocks.js | 32 ++++++++++++++++---------------- src/tree2text.js | 10 +++++----- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/ast.js b/src/ast.js index b7fdd74..223378f 100644 --- a/src/ast.js +++ b/src/ast.js @@ -216,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); @@ -224,7 +224,7 @@ 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); @@ -272,8 +272,8 @@ 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) { return new Praxly_statement(null); @@ -282,8 +282,8 @@ export function createExecutable(tree) { 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) { return new Praxly_statement(null); @@ -292,8 +292,8 @@ export function createExecutable(tree) { 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) { return new Praxly_statement(null); @@ -302,8 +302,8 @@ export function createExecutable(tree) { 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) { return new Praxly_statement(null); @@ -1059,10 +1059,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) { @@ -1071,7 +1071,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'; } @@ -1079,10 +1079,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; } @@ -1092,7 +1092,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); } diff --git a/src/text2tree.js b/src/text2tree.js index ce00bc1..01a8a6f 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -900,20 +900,17 @@ class Parser { if (this.i > 0) { let comment = this.parse_end_of_line(true); if (comment) { - block_statements.push(this.comment); + block_statements.push(comment); } } // parse all statements inside the block while (!this.has('EOF') && this.hasNotAny(...endToken)) { let stmt = this.parse_statement(); - if (!stmt) { - break; // empty block - } block_statements.push(stmt); let comment = this.parse_end_of_line(false); if (comment) { - block_statements.push(this.comment); + block_statements.push(comment); } } @@ -963,7 +960,7 @@ class Parser { } this.advance(); - result.statement = this.parse_block('else', 'end if'); + result.codeblock = this.parse_block('else', 'end if'); if (this.getPrevTokenType() == 'else') { result.type = NODETYPES.IF_ELSE; result.alternative = this.parse_block('end if'); @@ -1002,7 +999,7 @@ class Parser { this.advance(); result.endIndex = this.getCurrentToken().endIndex; - result.statement = this.parse_block('end for'); + result.codeblock = this.parse_block('end for'); return result; } @@ -1020,14 +1017,14 @@ class Parser { return result; } this.advance(); - result.statement = this.parse_block('end while'); + result.codeblock = this.parse_block('end while'); } // do-while loop else if (this.has('do')) { result.type = NODETYPES.DO_WHILE; this.advance(); - result.statement = this.parse_block('while'); + result.codeblock = this.parse_block('while'); if (this.hasNot('(')) { return result; } @@ -1044,7 +1041,7 @@ class Parser { else if (this.has('repeat')) { result.type = NODETYPES.REPEAT_UNTIL; this.advance(); - result.statement = this.parse_block('until'); + result.codeblock = this.parse_block('until'); if (this.hasNot('(')) { return result; } @@ -1167,7 +1164,7 @@ class Parser { startIndex: token.startIndex, endIndex: token.endIndex, blockID: 'code', - line: line, + line: token.line, }; } diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 5271111..6016227 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -204,17 +204,17 @@ 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 statements = tree2blocks(workspace, node?.codeblock); var alternatives = tree2blocks(workspace, node?.alternative); result.getInput('CONDITION').connection.connect(condition?.outputConnection); if (statements && statements.length > 0) { @@ -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; @@ -417,7 +417,7 @@ export const tree2blocks = (workspace, node) => { } // get the for loop body - var codeblocks = tree2blocks(workspace, node?.statement); + var codeblock = tree2blocks(workspace, node?.codeblock); // connect everything together result.getInput('INITIALIZATION').connection.connect(initialization?.outputConnection); @@ -425,8 +425,8 @@ export const tree2blocks = (workspace, node) => { result.getInput('CONDITION').connection.connect(condition?.outputConnection); result.getInput('REASSIGNMENT').connection.connect(increment?.outputConnection); container2?.dispose(); - if (codeblocks && codeblocks.length > 0) { - result.getInput('CODEBLOCK').connection.connect(codeblocks[0]?.previousConnection); + if (codeblock && codeblock.length > 0) { + result.getInput('CODEBLOCK').connection.connect(codeblock[0]?.previousConnection); } } catch (error) { diff --git a/src/tree2text.js b/src/tree2text.js index a4d3942..754981d 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -235,14 +235,14 @@ 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 contents = tree2text(node.codeblock, indentation + 1) + ' '.repeat(indentation) + 'end if\n'; return result + condition + contents; case NODETYPES.IF_ELSE: var result = ' '.repeat(indentation) + "if ("; var condition = tree2text(node.condition, 0) + ")\n"; - var contents = tree2text(node.statement, indentation + 1); + var contents = tree2text(node.codeblock, indentation + 1); var alternative = ' '.repeat(indentation) + '\else\n' + tree2text(node.alternative, indentation + 1) + ' '.repeat(indentation) + 'end if\n'; @@ -280,13 +280,13 @@ export const tree2text = (node, indentation) => { case NODETYPES.DO_WHILE: var result = ' '.repeat(indentation) + 'do\n'; - var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1); + var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, 0) + ")\n"; return result + contents + condition; case NODETYPES.REPEAT_UNTIL: var result = ' '.repeat(indentation) + 'repeat\n'; - var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1); + var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, 0) + ")\n"; return result + contents + condition; @@ -307,7 +307,7 @@ export const tree2text = (node, indentation) => { var condition = tree2text(node.condition, 0) + '; '; var increment = tree2text(node.increment, 0); increment = increment.replace("\n", "") + ')\n'; - var contents = ' '.repeat(indentation) + tree2text(node.statement, indentation + 1) + + var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1) + ' '.repeat(indentation) + 'end for\n'; return result + initialization + condition + increment + contents; From aeb6a069a4cb56d1d70c33f326faf2e5c8091749 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sat, 3 Aug 2024 19:55:12 -0400 Subject: [PATCH 26/35] missed one of four files in rename codeblock --- src/blocks2tree.js | 12 ++++++------ src/tree2blocks.js | 12 ++++++------ src/tree2text.js | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 1af5a8c..7ebf8e2 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -373,7 +373,7 @@ export const makeGenerator = () => { type: NODETYPES.IF, blockID: block.id, condition: praxlyGenerator[condition?.type](condition), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements) + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements) }) } @@ -385,7 +385,7 @@ export const makeGenerator = () => { type: NODETYPES.IF_ELSE, blockID: block.id, condition: praxlyGenerator[condition?.type](condition), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements), + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements), alternative: praxlyGenerator['codeBlockJsonBuilder'](alternative), }) } @@ -523,7 +523,7 @@ export const makeGenerator = () => { type: NODETYPES.WHILE, blockID: block.id, condition: praxlyGenerator[condition?.type](condition), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements) + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -534,7 +534,7 @@ export const makeGenerator = () => { type: NODETYPES.DO_WHILE, blockID: block.id, condition: praxlyGenerator[condition?.type](condition), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements) + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -545,7 +545,7 @@ export const makeGenerator = () => { type: NODETYPES.REPEAT_UNTIL, blockID: block.id, condition: praxlyGenerator[condition?.type](condition), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements) + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements) }); } @@ -576,7 +576,7 @@ export const makeGenerator = () => { type: NODETYPES.FOR, blockID: block.id, initialization: praxlyGenerator[initialization?.type](initialization), - statement: praxlyGenerator['codeBlockJsonBuilder'](statements), + codeblock: praxlyGenerator['codeBlockJsonBuilder'](statements), increment: praxlyGenerator[reassignment?.type](reassignment), condition: praxlyGenerator[condition?.type](condition), }); diff --git a/src/tree2blocks.js b/src/tree2blocks.js index 6016227..d061a18 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -214,14 +214,14 @@ export const tree2blocks = (workspace, node) => { case NODETYPES.IF_ELSE: var result = workspace.newBlock('praxly_if_else_block'); var condition = tree2blocks(workspace, node?.condition); - var statements = tree2blocks(workspace, node?.codeblock); - 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; diff --git a/src/tree2text.js b/src/tree2text.js index 754981d..7b7d060 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -274,7 +274,7 @@ export const tree2text = (node, indentation) => { case NODETYPES.WHILE: var result = ' '.repeat(indentation) + "while"; var condition = " (" + tree2text(node.condition, 0) + ")\n"; - var contents = tree2text(node.statement, indentation + 1) + + var contents = tree2text(node.codeblock, indentation + 1) + ' '.repeat(indentation) + 'end while\n'; return result + condition + contents; From cad25ec3fca8cf3b02a8486964368356211992e3 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Sun, 4 Aug 2024 00:07:59 -0400 Subject: [PATCH 27/35] all tests in run_all.sh are passing now! --- src/ast.js | 17 +++++++++++++---- src/debugger.js | 5 +---- src/text2tree.js | 23 ++++++++++++++--------- src/tree2text.js | 8 ++++---- test/basics.csv | 12 ++++++------ test/canvas.csv | 3 ++- test/studycomp.csv | 3 +-- test/variables.csv | 16 ++++++++-------- 8 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/ast.js b/src/ast.js index 223378f..42306bd 100644 --- a/src/ast.js +++ b/src/ast.js @@ -516,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(); @@ -531,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; } @@ -544,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) { diff --git a/src/debugger.js b/src/debugger.js index 777a705..3c8fdc7 100644 --- a/src/debugger.js +++ b/src/debugger.js @@ -75,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/text2tree.js b/src/text2tree.js index 01a8a6f..ed0bab7 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -635,11 +635,6 @@ class Parser { this.advance(); return this.literalNode_new(this.tokens[this.i - 1]); - // function call - case NODETYPES.BUILTIN_FUNCTION_CALL: - case 'Type': // type conversion function - return this.parse_builtin_function_call(line); - // parentheses case '(': const leftToken = this.advance(); @@ -686,6 +681,16 @@ class Parser { 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(); @@ -771,7 +776,7 @@ 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); } } @@ -900,18 +905,18 @@ class Parser { if (this.i > 0) { let comment = this.parse_end_of_line(true); if (comment) { - block_statements.push(comment); + block_statements.push(comment); // move trailing comment into the new block } } // parse all statements inside the block while (!this.has('EOF') && this.hasNotAny(...endToken)) { let stmt = this.parse_statement(); - block_statements.push(stmt); let comment = this.parse_end_of_line(false); if (comment) { - block_statements.push(comment); + block_statements.push(comment); // move trailing comment above the statement } + block_statements.push(stmt); } // make sure the block is correctly terminated diff --git a/src/tree2text.js b/src/tree2text.js index 7b7d060..321af03 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -280,13 +280,13 @@ export const tree2text = (node, indentation) => { case NODETYPES.DO_WHILE: var result = ' '.repeat(indentation) + 'do\n'; - var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1); + var contents = tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, 0) + ")\n"; return result + contents + condition; case NODETYPES.REPEAT_UNTIL: var result = ' '.repeat(indentation) + 'repeat\n'; - var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1); + var contents = tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, 0) + ")\n"; return result + contents + condition; @@ -307,7 +307,7 @@ export const tree2text = (node, indentation) => { var condition = tree2text(node.condition, 0) + '; '; var increment = tree2text(node.increment, 0); increment = increment.replace("\n", "") + ')\n'; - var contents = ' '.repeat(indentation) + tree2text(node.codeblock, indentation + 1) + + var contents = tree2text(node.codeblock, indentation + 1) + ' '.repeat(indentation) + 'end for\n'; return result + initialization + condition + increment + contents; @@ -323,7 +323,7 @@ export const tree2text = (node, indentation) => { } result += ')'; result += '\n'; - var contents = ' '.repeat(indentation) + tree2text(node.contents, indentation + 1); + var contents = tree2text(node.contents, indentation + 1); result += contents; result += ' '.repeat(indentation) + `end ${node.name}\n`; return result; diff --git a/test/basics.csv b/test/basics.csv index a4874a6..22c56f6 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 diff --git a/test/canvas.csv b/test/canvas.csv index 89a46d1..64b9c7f 100644 --- a/test/canvas.csv +++ b/test/canvas.csv @@ -548,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 @@ -588,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/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." From d58801e80ae965c4bc462a5b8967a7856c20b95f Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Mon, 5 Aug 2024 10:53:40 -0400 Subject: [PATCH 28/35] improve placement of comments; rename contents to codeblock for consistency --- src/ast.js | 24 ++++++++++++------------ src/blocks2tree.js | 4 ++-- src/newBlocks.js | 2 +- src/text2tree.js | 20 +++++++++++++++----- src/tree2blocks.js | 6 +++--- src/tree2text.js | 28 ++++++++++++++-------------- 6 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/ast.js b/src/ast.js index 42306bd..c898569 100644 --- a/src/ast.js +++ b/src/ast.js @@ -322,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 = []; @@ -1569,11 +1569,11 @@ class Praxly_invalid { 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; } @@ -1581,7 +1581,7 @@ class Praxly_function_declaration { environment.functionList[this.name] = { returnType: this.returnType, params: this.params, - contents: this.codeblock, + codeblock: this.codeblock, } } } @@ -1607,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. @@ -1623,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)) { @@ -1637,7 +1637,7 @@ 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) { diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 7ebf8e2..85f7e41 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -594,14 +594,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, }) } diff --git a/src/newBlocks.js b/src/newBlocks.js index e38f8e6..e8eb7bf 100644 --- a/src/newBlocks.js +++ b/src/newBlocks.js @@ -999,7 +999,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 ed0bab7..1f10785 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -878,7 +878,7 @@ class Parser { result.params = params; // procedure body - result.contents = this.parse_block('end ' + result.name); + result.codeblock = this.parse_block('end ' + result.name); } result.endIndex = this.getCurrentToken().endIndex; @@ -905,7 +905,8 @@ class Parser { if (this.i > 0) { let comment = this.parse_end_of_line(true); if (comment) { - block_statements.push(comment); // move trailing comment into the new block + // move trailing comment into the new block + block_statements.push(comment); } } @@ -913,10 +914,19 @@ class Parser { while (!this.has('EOF') && this.hasNotAny(...endToken)) { let stmt = this.parse_statement(); let comment = this.parse_end_of_line(false); - if (comment) { - block_statements.push(comment); // move trailing comment above the statement + 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); } - block_statements.push(stmt); } // make sure the block is correctly terminated diff --git a/src/tree2blocks.js b/src/tree2blocks.js index d061a18..40c43e5 100644 --- a/src/tree2blocks.js +++ b/src/tree2blocks.js @@ -352,9 +352,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}`); diff --git a/src/tree2text.js b/src/tree2text.js index 321af03..5fd4978 100644 --- a/src/tree2text.js +++ b/src/tree2text.js @@ -235,18 +235,18 @@ export const tree2text = (node, indentation) => { case NODETYPES.IF: var result = ' '.repeat(indentation) + "if ("; var condition = tree2text(node.condition, 0) + ")\n"; - var contents = tree2text(node.codeblock, 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, 0) + ")\n"; - var contents = tree2text(node.codeblock, indentation + 1); + 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: @@ -274,21 +274,21 @@ export const tree2text = (node, indentation) => { case NODETYPES.WHILE: var result = ' '.repeat(indentation) + "while"; var condition = " (" + tree2text(node.condition, 0) + ")\n"; - var contents = tree2text(node.codeblock, indentation + 1) + + 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 = tree2text(node.codeblock, indentation + 1); + var codeblock = tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "while (" + tree2text(node.condition, 0) + ")\n"; - return result + contents + condition; + return result + codeblock + condition; case NODETYPES.REPEAT_UNTIL: var result = ' '.repeat(indentation) + 'repeat\n'; - var contents = tree2text(node.codeblock, indentation + 1); + var codeblock = tree2text(node.codeblock, indentation + 1); var condition = ' '.repeat(indentation) + "until (" + tree2text(node.condition, 0) + ")\n"; - return result + contents + condition; + return result + codeblock + condition; case NODETYPES.NOT: var result = "not "; @@ -307,9 +307,9 @@ export const tree2text = (node, indentation) => { var condition = tree2text(node.condition, 0) + '; '; var increment = tree2text(node.increment, 0); increment = increment.replace("\n", "") + ')\n'; - var contents = tree2text(node.codeblock, indentation + 1) + + 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 = 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; From 4eb48cdad94c995f237338ed534a3de67492e0a3 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Mon, 5 Aug 2024 11:01:34 -0400 Subject: [PATCH 29/35] new procedure and text colors --- src/theme.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/theme.js b/src/theme.js index a670ae2..1b9936f 100644 --- a/src/theme.js +++ b/src/theme.js @@ -78,7 +78,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#808080' }, 'procedure_blocks': { - 'colourPrimary': '#4B0082' + 'colourPrimary': '#9370DB' }, 'variable_blocks': { 'colourPrimary': '#f80069' @@ -93,7 +93,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#00BFFF' }, 'text_blocks': { - 'colourPrimary': '#1E90FF' + 'colourPrimary': '#2F4F4F' } }, 'categoryStyles': { @@ -101,7 +101,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#395BBF' }, 'procedure_blocks': { - 'colour': '#4B0082' + 'colour': '#9370DB' }, 'logic_blocks': { 'colour': '#3CB371' @@ -122,7 +122,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#DAA520' }, 'text_blocks': { - 'colour': '#1E90FF' + 'colour': '#2F4F4F' } }, 'componentStyles': { From 7c97651d165d647edb73f3234b698dc56e191a27 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Mon, 5 Aug 2024 11:03:27 -0400 Subject: [PATCH 30/35] add one more test; tweak test delay; format examples --- src/examples.js | 14 +++++++------- test/basics.csv | 21 +++++++++++++++++++++ test/run_csv.py | 5 +++-- 3 files changed, 31 insertions(+), 9 deletions(-) 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/test/basics.csv b/test/basics.csv index 22c56f6..1762a1d 100644 --- a/test/basics.csv +++ b/test/basics.csv @@ -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/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 From 612284d33d1aefe9e860f2810803ebcd079ae70b Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Mon, 5 Aug 2024 13:54:12 -0400 Subject: [PATCH 31/35] improve error reporting and fix a bug with array reference assignment --- src/ast.js | 6 +++--- src/blocks2tree.js | 6 ++++++ src/main.js | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ast.js b/src/ast.js index c898569..87177e2 100644 --- a/src/ast.js +++ b/src/ast.js @@ -173,7 +173,7 @@ export function createExecutable(tree) { checkArity(tree, 1); return new Praxly_sqrt(createExecutable(tree.args[0]), tree); } else { - throw new PraxlyError("unknown builtin function: " + tree.name, tree.line); + throw Error(`unknown builtin function ${tree.name} (line ${tree.line})`); } } @@ -193,7 +193,7 @@ export function createExecutable(tree) { checkArity(tree.right, 2); break; default: - throw new PraxlyError("unknown string method: " + tree.right.name, tree.line); + throw Error(`unknown string method ${tree.right.name} (line ${tree.line})`); } var args = []; tree.right.args.forEach((arg) => { @@ -1762,7 +1762,7 @@ function can_assign(varType, expressionType, line) { } else if (varType === TYPES.CHAR) { return expressionType === TYPES.CHAR; } else { - throw new PraxlyError("unknown variable type", line); + throw Error(`unknown variable type ${varType} (line ${line})`); } } diff --git a/src/blocks2tree.js b/src/blocks2tree.js index 85f7e41..63a524a 100644 --- a/src/blocks2tree.js +++ b/src/blocks2tree.js @@ -480,6 +480,12 @@ export const makeGenerator = () => { index: index, value: value, blockID: block.id, + location: { + name: variableName, + type: NODETYPES.LOCATION, + isArray: true, + index: index, + }, }) } diff --git a/src/main.js b/src/main.js index b9e880a..f5a60cb 100644 --- a/src/main.js +++ b/src/main.js @@ -526,7 +526,7 @@ export function turnCodeToBlocks() { clearErrors(); mainTree = text2tree(); if (DEV_LOG) { - console.log(mainTree); + console.log("text2tree", mainTree); } // update block side to match @@ -554,7 +554,7 @@ function turnBlocksToCode() { clearErrors(); mainTree = blocks2tree(workspace, praxlyGenerator); if (DEV_LOG) { - console.log(mainTree); + console.log("blocks2tree", mainTree); } // update text side to match From d7bf434493fe2dd3474cd5c950c3e5ae301e6259 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Mon, 5 Aug 2024 15:08:21 -0400 Subject: [PATCH 32/35] parse both kinds of comments at end of line --- src/text2tree.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text2tree.js b/src/text2tree.js index 1f10785..84c6a20 100644 --- a/src/text2tree.js +++ b/src/text2tree.js @@ -314,10 +314,6 @@ class Parser { this.length = tokens?.length; } - hasNot(type) { - return this.i < this.length && this.tokens[this.i].token_type !== type; - } - getCurrentToken() { return this.tokens[this.i]; } @@ -338,6 +334,10 @@ class Parser { 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); @@ -1171,10 +1171,10 @@ class Parser { // optional end-of-line comment let comment; - if (this.has(NODETYPES.SINGLE_LINE_COMMENT)) { + if (this.has(NODETYPES.SINGLE_LINE_COMMENT) || this.has(NODETYPES.COMMENT)) { const token = this.advance(); comment = { - type: NODETYPES.SINGLE_LINE_COMMENT, + type: this.getPrevTokenType(), value: token.value, startIndex: token.startIndex, endIndex: token.endIndex, From 2c43d11199bce8479eafb3f7014eed25b6dd0958 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Mon, 5 Aug 2024 17:57:55 -0400 Subject: [PATCH 33/35] new text color --- src/theme.js | 4 ++-- src/toolbox.js | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/theme.js b/src/theme.js index 1b9936f..4bee7f9 100644 --- a/src/theme.js +++ b/src/theme.js @@ -93,7 +93,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#00BFFF' }, 'text_blocks': { - 'colourPrimary': '#2F4F4F' + 'colourPrimary': '#cdd481' } }, 'categoryStyles': { @@ -122,7 +122,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#DAA520' }, 'text_blocks': { - 'colour': '#2F4F4F' + 'colour': '#cdd481' } }, 'componentStyles': { diff --git a/src/toolbox.js b/src/toolbox.js index 787d569..ecfb815 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -46,10 +46,6 @@ export const toolbox = { "name": "variables", "categorystyle": "variable_blocks", "contents": [ - { - 'kind': 'block', - 'type': 'praxly_variable_block' - }, { 'kind': 'block', 'type': 'praxly_vardecl_block', @@ -82,6 +78,10 @@ export const toolbox = { }, } }, + { + 'kind': 'block', + 'type': 'praxly_variable_block' + }, { 'kind': 'block', 'type': 'praxly_array_assignment_block', @@ -136,10 +136,6 @@ export const toolbox = { "name": "math", "categorystyle": "math_blocks", "contents": [ - { - 'kind': 'block', - 'type': 'praxly_literal_block' - }, // { // 'kind': 'block', // 'type': 'praxly_String_block' @@ -180,6 +176,10 @@ export const toolbox = { }, } }, + { + 'kind': 'block', + 'type': 'praxly_literal_block' + }, { 'kind': 'block', 'type': 'praxly_random_block' @@ -390,7 +390,7 @@ export const toolbox = { 'block': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '"string"' + 'LITERAL': 'string' } } } From 2acad72c2e79da7c8bb944ab4d3495052d84fba6 Mon Sep 17 00:00:00 2001 From: ellonamac Date: Mon, 5 Aug 2024 18:00:20 -0400 Subject: [PATCH 34/35] alternative text option --- src/theme.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/theme.js b/src/theme.js index 4bee7f9..82a2752 100644 --- a/src/theme.js +++ b/src/theme.js @@ -94,6 +94,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { }, 'text_blocks': { 'colourPrimary': '#cdd481' + // bec47e } }, 'categoryStyles': { @@ -123,6 +124,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { }, 'text_blocks': { 'colour': '#cdd481' + // bec47e } }, 'componentStyles': { From 707fd3eb07d03387fb4d8b3535772f77a7c40101 Mon Sep 17 00:00:00 2001 From: Chris Mayfield Date: Mon, 5 Aug 2024 20:19:07 -0400 Subject: [PATCH 35/35] tweak orange and yellow; capital Hello in blocks --- src/theme.js | 12 +++++------- src/toolbox.js | 14 +++++++------- test/colors.py | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 test/colors.py diff --git a/src/theme.js b/src/theme.js index 82a2752..92d7204 100644 --- a/src/theme.js +++ b/src/theme.js @@ -63,7 +63,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'base': Blockly.Themes.Classic, 'blockStyles': { 'loop_blocks': { - 'colourPrimary': '#0361FF' + 'colourPrimary': '#395BBF' }, 'array_blocks': { 'colourPrimary': '#FA0000' @@ -84,7 +84,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#f80069' }, 'math_blocks': { - 'colourPrimary': '#DAA520' + 'colourPrimary': '#FF9966' }, 'parameter_blocks': { 'colourPrimary': '#8F48B7' @@ -93,8 +93,7 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colourPrimary': '#00BFFF' }, 'text_blocks': { - 'colourPrimary': '#cdd481' - // bec47e + 'colourPrimary': '#FFC050', } }, 'categoryStyles': { @@ -120,11 +119,10 @@ export const PraxlyDark = Blockly.Theme.defineTheme('PraxlyDark', { 'colour': '#f80069' }, 'math_blocks': { - 'colour': '#DAA520' + 'colour': '#FF9966' }, 'text_blocks': { - 'colour': '#cdd481' - // bec47e + 'colour': '#FFC050' } }, 'componentStyles': { diff --git a/src/toolbox.js b/src/toolbox.js index ecfb815..7d78699 100644 --- a/src/toolbox.js +++ b/src/toolbox.js @@ -360,7 +360,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, @@ -382,7 +382,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, @@ -404,7 +404,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, @@ -426,7 +426,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, } @@ -440,7 +440,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, @@ -470,7 +470,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, @@ -484,7 +484,7 @@ export const toolbox = { 'shadow': { 'type': 'praxly_literal_block', 'fields': { - 'LITERAL': '\"hello\"', + 'LITERAL': '"Hello"', } }, }, 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()