packages/wescheme-blocks/src/parseNode.ts
import * as structures from "wescheme-js/src/structures";import { Nodes } from "codemirror-blocks";import { LetLikeExpr, WhenUnless, Sequence } from "./ast";import * as types from "wescheme-js/src/runtime/types";const { isString, isChar, TRUE, FALSE } = types; const { Blank, Literal, StructDefinition, IdentifierList, FunctionDefinition, IfExpression, CondExpression, LambdaExpression, CondClause, Comment, VariableDefinition, FunctionApp, Unknown,} = Nodes; const symbolMap = new Map();symbolMap.set("*", "multiply");symbolMap.set("-", "subtract");symbolMap.set("/", "divide"); // symbolAria : String -> Stringexport function symbolAria(str: string): string { // if there's no str available, it's an anonymous function if (!str) { return "anonymous function"; // make sure it's a string (in the event of a number in the fn position } else { str = str.toString(); } if (symbolMap.get(str)) { // translate simple symbols return symbolMap.get(str); } else { // pronounce special chars, scheme-style str = str.replace("?", "-huh").replace("!", "-bang").replace("*", "-star"); // pronounce quotes str = str.replace('"', " quote"); // pronounce braces str = str.replace("(", " open paren").replace(")", " close paren"); str = str.replace("[", " open bracket").replace("]", " close bracket"); return str; }} function pluralize(noun: string, set: unknown[]) { return set.length + " " + noun + (set.length != 1 ? "s" : "");} // expressionAria: String Array -> Stringfunction expressionAria(func: string, args: unknown[]) { return symbolAria(func) + " expression, " + pluralize("input", args);} // enumerateIdentifierList : [Literals] -> String// do the right thing with commas, "and", etcfunction enumerateIdentifierList(lst: structures.Program[]) { if (lst.length === 0) { return ""; } lst = [...lst]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const last = lst.pop()!; return lst.length == 0 ? last.toString() : lst.join(", ") + " and " + last.toString();} // makeComment : WeSchemeComment -> ASTNodeCommentfunction makeComment(node: structures.Program | structures.comment) { const from = { line: (node.comment || node).location.startRow - 1, ch: (node.comment || node).location.startCol, }; const to = { line: (node.comment || node).location.endRow - 1, ch: (node.comment || node).location.endCol, }; return Comment(from, to, node.comment?.txt || "");} // parseNode : WeSchemeNode Number -> ASTNodeFunction `parseNode` has a Cognitive Complexity of 72 (exceeds 5 allowed). Consider refactoring.export function parseNode(node: structures.Program, i = 0) { function locationFromNode(node: structures.Program) { const from = { line: node.location.startRow - 1, ch: node.location.startCol, }; const to = { line: node.location.endRow - 1, ch: node.location.endCol, }; return { from, to }; } function parseBinding(b: structures.couple) { const loc = locationFromNode(b); const name = parseNode(b.first); const val = parseNode(b.second); return VariableDefinition(loc.from, loc.to, name, val, { ariaLabel: symbolAria(b.first.val) + " bound to " + val.options.ariaLabel, comment: comment, }); } const { from, to } = locationFromNode(node); const comment = node.comment ? makeComment(node) : undefined; if (node instanceof structures.callExpr) { let func, label; const children = node.args.map(parseNode).filter((item) => item !== null); if (!node.func) { func = Blank( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 1 }, "...", "blank", { ariaLabel: "*blank*" } ); // special case for Unit Tests } else { func = parseNode(node.func); if (func.value == "check-expect" && node.args[0].func) { label = "Unit Test for " + symbolAria(node.args[0].func.val); } else { label = expressionAria( node.func ? symbolAria(node.func.val) : "empty", node.args ); } } return FunctionApp(from, to, func, children, { ariaLabel: label, comment: comment, }); } else if (node instanceof structures.andExpr) { const comment = node.comment ? makeComment(node) : undefined; return FunctionApp( from, to, Literal( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 4 }, "and", "symbol", { ariaLabel: "and" } ), node.exprs.map(parseNode).filter((item) => item !== null), { ariaLabel: expressionAria("and", node.exprs), comment: comment } ); } else if (node instanceof structures.orExpr) { const comment = node.comment ? makeComment(node) : undefined; return FunctionApp( from, to, Literal( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 3 }, "or", "symbol", { ariaLabel: "or" } ), node.exprs.map(parseNode).filter((item) => item !== null), { ariaLabel: expressionAria("or", node.exprs), comment: comment } ); } else if (node instanceof structures.defVar) { return VariableDefinition( from, to, parseNode(node.name), parseNode(node.expr), { ariaLabel: symbolAria(node.name.val) + ": value definition", comment: comment, } ); } else if (node instanceof structures.defStruct) { const fieldsLoc = locationFromNode(node.fields); const fields = IdentifierList( fieldsLoc.from, fieldsLoc.to, "fields", node.fields.map(parseNode), { ariaLabel: pluralize("field", node.fields) + ": " + enumerateIdentifierList(node.fields), comment: node.fields.comment ? makeComment(node.fields) : undefined, } );Avoid too many `return` statements within this function. return StructDefinition(from, to, parseNode(node.name), fields, { ariaLabel: symbolAria(node.name.val) + ": structure definition with " + pluralize("field", node.fields) + ": " + enumerateIdentifierList(node.fields), comment: comment, }); } else if (node instanceof structures.defFunc) { const argsLoc = locationFromNode(node.args); const args = IdentifierList( argsLoc.from, argsLoc.to, "arguments:", node.args.map(parseNode), { ariaLabel: pluralize("argument", node.args) + ": " + enumerateIdentifierList(node.args), comment: node.args.comment ? makeComment(node.args) : undefined, } );Avoid too many `return` statements within this function. return FunctionDefinition( from, to, parseNode(node.name), args, parseNode(node.body), { ariaLabel: symbolAria(node.name.val) + ": function definition with " + pluralize("argument", node.args) + ": " + enumerateIdentifierList(node.args), comment: comment, } ); } else if (node instanceof structures.lambdaExpr) { const argsLoc = locationFromNode(node.args); const args = IdentifierList( argsLoc.from, argsLoc.to, "arguments", node.args.map(parseNode), { ariaLabel: pluralize("argument", node.args) + ": " + enumerateIdentifierList(node.args), comment: node.args.comment ? makeComment(node.args) : undefined, } );Avoid too many `return` statements within this function. return LambdaExpression(from, to, args, parseNode(node.body), { ariaLabel: "anonymous function with " + pluralize("argument", node.args) + ": " + enumerateIdentifierList(node.args), }); } else if (node instanceof structures.condExpr) {Avoid too many `return` statements within this function. return CondExpression(from, to, node.clauses.map(parseNode), { ariaLabel: "conditional expression with " + pluralize("clause", node.clauses), }); } else if (node instanceof structures.couple) {Avoid too many `return` statements within this function. return CondClause( from, to, parseNode(node.first), [parseNode(node.second)], { ariaLabel: "condition " + (i + 1) } ); } else if (node instanceof structures.ifExpr) { const predicate = parseNode(node.predicate); const consequence = parseNode(node.consequence); const alternative = parseNode(node.alternative); const predLabel = predicate.options.ariaLabel; const conLabel = consequence.options.ariaLabel; const altLabel = alternative.options.ariaLabel; predicate.options.ariaLabel = "if, " + predLabel; consequence.options.ariaLabel = "then, " + conLabel; alternative.options.ariaLabel = "else, " + altLabel;Avoid too many `return` statements within this function. return IfExpression(from, to, predicate, consequence, alternative, { ariaLabel: "if-then-else expression", comment: comment, }); } else if (node instanceof structures.symbolExpr) { if (node.val == "...") {Avoid too many `return` statements within this function. return Blank(from, to, node.val, "symbol", { ariaLabel: "blank" }); } else if (["true", "false"].includes(node.val)) {Avoid too many `return` statements within this function. return Literal(from, to, node.val, "boolean", { ariaLabel: symbolAria(node.val) + ", a Boolean", comment: comment, }); } else {Avoid too many `return` statements within this function. return Literal(from, to, node.val, "symbol", { ariaLabel: symbolAria(node.val), comment: comment, }); } } else if (node instanceof structures.literal) { let dataType: string = typeof node.val, aria = node.toString(), value = node.toString(); if (isString(node.val)) { dataType = "string"; aria = `${node.val}, a String`; value = '"' + node.val + '"'; // use the raw value, plus the quotes (for unicode symbols) } else if (isChar(node.val)) { dataType = "character"; aria = `${node.val.val}, a Character`; } else if (node.val === FALSE || node.val === TRUE) { dataType = "boolean"; aria = `${node.val}, a Boolean`; } else if (node.val.isRational && node.val.isRational()) { dataType = "number"; aria = `${String(node.val.numerator())} over ${String( node.val.denominator() )}, a Rational`; }Avoid too many `return` statements within this function. return Literal(from, to, value, dataType, { ariaLabel: aria, comment: comment, }); } else if (node instanceof structures.comment) {Avoid too many `return` statements within this function. return Comment(from, to, node.txt); } else if (node instanceof structures.beginExpr) {Avoid too many `return` statements within this function. return Sequence( from, to, node.exprs.map(parseNode), Literal( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 6 }, "begin", "symbol", { ariaLabel: "begin" } ), { ariaLabel: `sequence containing ${pluralize("expression", node.exprs)}`, } ); } else if ( node instanceof structures.letExpr || node instanceof structures.letStarExpr || node instanceof structures.letrecExpr ) { const loc = locationFromNode(node.bindings), form = node.stx[0].val; const bindings = node.bindings.map(parseBinding); const sequence = Sequence( loc.from, loc.to, bindings, Literal( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 9 }, "bindings", "symbol", { ariaLabel: "bindings" } ), { ariaLabel: `${pluralize("binding", node.bindings)}`, } );Avoid too many `return` statements within this function. return LetLikeExpr(from, to, form, sequence, parseNode(node.body), { ariaLabel: `${symbolAria(form)} expression with ${pluralize( "binding", node.bindings )}`, }); } else if (node instanceof structures.whenUnlessExpr) { const loc = locationFromNode(node.exprs), form = node.stx.val;Avoid too many `return` statements within this function. return WhenUnless( from, to, form, parseNode(node.predicate), Sequence( loc.from, loc.to, node.exprs.map(parseNode), Literal( { line: from.line, ch: from.ch + 1 }, { line: from.line, ch: from.ch + 6 }, "begin", "symbol", { ariaLabel: "begin" } ), { ariaLabel: `${pluralize("expression", node.exprs)}`, } ), { ariaLabel: `${symbolAria(form)} expression` } ); } else if (node instanceof structures.unsupportedExpr) {Avoid deeply nested control flow statements. if (node.val.constructor !== Array) {Avoid too many `return` statements within this function. return null; } const unknown = Unknown( from, to, node.val.map(parseNode).filter((item) => item !== null), { ariaLabel: "invalid expression", comment: comment, } );Avoid too many `return` statements within this function. return unknown;Avoid deeply nested control flow statements. } else if (node instanceof structures.requireExpr) {Avoid too many `return` statements within this function. return FunctionApp(from, to, parseNode(node.stx), [parseNode(node.spec)], { ariaLabel: "require " + node.spec.val, comment: comment, }); }Avoid too many `return` statements within this function. return null;}