From 8dad70fefc22cb55d0beb2c8da2cad93d786ff8d Mon Sep 17 00:00:00 2001 From: Xitog Date: Sat, 14 Oct 2023 00:11:24 +0200 Subject: [PATCH] new parameters for function in Ash --- ash/index.html | 14 +- ash/interpreter.mjs | 293 +------------------------------- ash/library.mjs | 399 ++++++++++++++++++++++++++++++++++++++++++++ ash/translator.mjs | 1 - 4 files changed, 411 insertions(+), 296 deletions(-) create mode 100644 ash/library.mjs diff --git a/ash/index.html b/ash/index.html index 15a8804..031b550 100644 --- a/ash/index.html +++ b/ash/index.html @@ -55,8 +55,8 @@ try { code = code.trim(); if (debug) console.log(`Input code : |code|`) - if (debug) console.log('Lexing :'); - step = 'LEXING'; + step = 'Lexing'; + if (debug) console.log(`${step} :`); let lexer = new Lexer('ash', [], debug); let tokens = lexer.lex(code); if (debug) { @@ -64,16 +64,18 @@ console.log(' ' + index.toString().padStart(3) + `. ${tok}`); } } - if (debug) console.log('Parsing :'); - step = 'PARSING'; + step = 'Parsing'; + if (debug) console.log(`${step} :`); let root = new Parser().parse(tokens, debug); if (debug) console.log('Interpreter :'); - step = 'EXECUTING'; + step = 'Executing'; + if (debug) console.log(`${step} :`); GlobalInterpreter.setDebug(debug); let result = GlobalInterpreter.do(root); let output = document.createElement("pre"); if (debug) console.log('End of execution'); - step = 'TRANSPILING'; + step = 'Transpiling'; + if (debug) console.log(`${step} :`); let codejs = new Translator().do(root); let resultjs = eval(codejs); console.log('Code JS : ' + codejs); diff --git a/ash/interpreter.mjs b/ash/interpreter.mjs index c2e7601..7a042cd 100644 --- a/ash/interpreter.mjs +++ b/ash/interpreter.mjs @@ -55,6 +55,7 @@ const main = (node) ? path.basename(process.argv[1]) === FILENAME : false; import { Lexer, Language } from "./lexer.mjs"; import { Parser, Node } from "./parser.mjs"; +import { Library, Value, nil } from "./library.mjs"; Language.readDefinition(); @@ -64,13 +65,6 @@ Language.readDefinition(); let GlobalInterpreter = null; -class NilClass { - toString() { - return "nil"; - } -} -const nil = new NilClass(); - class NotAnExpression { toString() { return "not an expression" @@ -79,274 +73,6 @@ class NotAnExpression { // Shall a procedure returns nil or nae ? const notAnExpression = new NotAnExpression(); -class Value { - constructor(identifier, type, value) { - this.identifier = identifier; - this.type = type; - this.value = value; - } - - setValue(value) { - console.log(typeof value); - console.log(Number.isInteger(value)); - if ( - (this.type === "boolean" && typeof value !== "boolean") - || (this.type === "integer" && (typeof value !== "number" || !Number.isInteger(value))) - || (this.type === "float" && typeof value !== "number") - || (this.type === "string" && typeof value !== "string") - ) { - let expectedType = typeof value; - if (expectedType === "number") { - expectedType = Number.isInteger(value) ? "integer" : "float"; - } - throw new Error(`[ERROR] Variable ${this.identifier} is of type ${this.type} cannot set to ${value} of type ${expectedType}`); - } - this.value = value; - } - - getValue() { - return this.value; - } - - getType() { - return this.type; - } -} - -class Function extends Value { - constructor(identifier, type, value, parameters, code) { - // value is only "procedure" or "function" - // type is its return type - super(identifier, type, value); - this.parameters = parameters; - this.code = code; - } - - toString() { - return ( - `function ${this.identifier} (` + - this.parameters.map((x) => x.toString()).join(", ") + - ")" - ); - } - - isProcedure() { - return this.value === 'procedure'; - } - - call(args) { - if (!Array.isArray(args)) { - throw new Error(`Args should be a list, not ${typeof args}`); - } - if (args.length !== Object.keys(this.parameters).length) { - console.log("Parameters:"); - console.log(args, ' type:', typeof args); - throw new Error(`Too many or not enough parameters: expected number is ${Object.keys(this.parameters).length} and ${args.length} were provided.`); - } - return this.code(args); - } -} - -let log = new Function( - 'log', - nil, - 'procedure', - { - 'o': 'any' - }, - function (args) { - GlobalInterpreter.output_function(args.join(' ')); - return nil; - } -); - -let clear = new Function( - 'clear', - nil, - 'procedure', - {}, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - context.clearRect(0, 0, 640, 480); - } - return nil; - } -); - -let line = new Function( - 'line', - nil, - 'procedure', - {}, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - let x1 = args[0]; - let y1 = args[1]; - let x2 = args[2]; - let y2 = args[3]; - context.lineWidth = args[4]; - context.strokeStyle = args[5]; - context.beginPath(); - context.moveTo(x1, y1); - context.lineTo(x2, y2); - context.stroke(); - } - return nil; - } -); - -let circle = new Function( - 'circle', - nil, - 'procedure', - { - 'x': 'integer', - 'y': 'integer', - 'r': 'integer', - 'color': 'integer', - 'fill': 'boolean' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - let centerX = args[0]; - let centerY = args[1]; - let radius = args[2]; - let color = args[3]; - let full = args[4]; - context.beginPath(); - context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); - if (full) { - context.fillStyle = color; - context.fill(); - } else { - context.strokeStyle = color; - context.stroke(); - } - } - return nil; - } -); - -let rect = new Function( - 'rect', - nil, - 'procedure', - { - 'x': 'integer', - 'y': 'integer', - 'width': 'integer', - 'height': 'integer', - 'color': 'string', - 'fill': 'boolean' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - context.fillStyle = args[4]; - context.strokeStyle = args[4]; - if (args[5]) { - context.fillRect( - args[0], - args[1], - args[2], - args[3] - ); - } else { - context.strokeRect( - args[0], - args[1], - args[2], - args[3] - ); - } - } - return nil; - } -); - -let draw = new Function( - 'draw', - nil, - 'procedure', - { - 'x': 'integer', - 'y': 'integer', - 'image': 'string' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - context.drawImage(args[2], args[0], args[1]); - } - return nil; - } -); - -let text = new Function( - 'text', - nil, - 'procedure', - { - 's': 'string', - 'x': 'integer', - 'y': 'integer' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - ctx.fillText(args[2], args[0], args[1]); - } - return nil; - } -); - -let set_font = new Function( - 'set_font', - nil, - 'procedure', - { - 'police': 'string', - 'size': 'integer' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - context.font = `${args[1]}px ${args[0]}`; - } -); - -let set_fill = new Function( - 'set_fill', - nil, - 'procedure', - { - 'c': 'string' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - context.fillStyle = args[0]; - } - } -); - -let set_stroke = new Function( - 'set_stroke', - nil, - 'procedure', - { - 'c': 'string' - }, - function (args) { - let context = GlobalInterpreter.getContext(); - if (context !== null) { - context.strokeStyle = args[0]; - } - } -); - class Interpreter { constructor(output_function = null, output_screen = null, debug = false) { this.root = {}; @@ -355,6 +81,7 @@ class Interpreter { this.scope = {}; this.debug = debug; GlobalInterpreter = this; + Library.init(GlobalInterpreter); } getContext() { @@ -625,20 +352,8 @@ class Interpreter { } } - library(id, args=[]) { - let base = { - // Graphic functions - 'clear': clear, 'line': line, - 'circle': circle, 'rect': rect, 'draw': draw, - 'text': text, - 'set_font': set_font, 'set_fill': set_fill, 'set_stroke': set_stroke, - // Console functions - 'log': log, 'writeln': log - }; - if (id in base) { - return base[id].call(args); - } - throw new Error(`[ERROR] Unknown function ${id}`); + library(idFun, args=[]) { + return Library.call(idFun, args); } } diff --git a/ash/library.mjs b/ash/library.mjs new file mode 100644 index 0000000..b0e1bce --- /dev/null +++ b/ash/library.mjs @@ -0,0 +1,399 @@ +//----------------------------------------------------------------------------- +// Function +//----------------------------------------------------------------------------- + +function typeJStoAsh(value) { + let typeValue = typeof value; + let typeAsh = null; + if (typeValue === 'boolean') { + typeAsh = 'bool'; + } else if (typeValue === 'number') { + typeAsh = Number.isInteger(value) ? 'int' : 'num'; + typeAsh = (typeAsh === 'int' && value >= 0) ? 'nat' : 'int'; + } else if (typeValue === 'string') { + typeAsh = 'str'; + } + return typeAsh; +} + +function kindOf(t1, t2) { + return ( + (t2 === 'int' && t1 === 'nat') // int inclut nat + || (t2 === 'num' && ['int', 'nat'].includes(t2)) // num inclut int & nat + || (t1 === t2) + ); +} +//----------------------------------------------------------------------------- +// Classes +//----------------------------------------------------------------------------- + +class NilClass { + toString() { + return "nil"; + } +} +const nil = new NilClass(); + +class Value { + constructor(identifier, type, value) { + this.identifier = identifier; + this.type = type; + this.value = value; + } + + setValue(value) { + console.log(typeof value); + console.log(Number.isInteger(value)); + if ( + (this.type === "boolean" && typeof value !== "boolean") + || (this.type === "integer" && (typeof value !== "number" || !Number.isInteger(value))) + || (this.type === "float" && typeof value !== "number") + || (this.type === "string" && typeof value !== "string") + ) { + let expectedType = typeof value; + if (expectedType === "number") { + expectedType = Number.isInteger(value) ? "integer" : "float"; + } + throw new Error(`[ERROR] Variable ${this.identifier} is of type ${this.type} cannot set to ${value} of type ${expectedType}`); + } + this.value = value; + } + + getValue() { + return this.value; + } + + getType() { + return this.type; + } +} + +class Parameter { + /** + * Il y a une subtilité dans le defaultValue. + * Si sa valeur est à "nil" à la création de l'instance (et pas null) + * c'est qu'on autorise à ne pas donner de valeur à l'argument + * @param {*} name + * @param {*} type + * @param {*} defaultValue + */ + constructor(name, type, defaultValue = null) { + this.name = name; + this.type = type; + this.defaultValue = defaultValue; + } + + hasDefault() { + return this.defaultValue !== null; + } + + getDefault() { + if (this.hasDefault()) { + return this.defaultValue; + } + throw new Error(`Not default defined for parameter ${this}`); + } + + getType() { + return this.type; + } + + toString() { + let def = this.defaultValue !== null ? ` = ${this.defaultValue}` : ""; + return `${this.name} : ${this.type}${def}`; + } +} + +class Function extends Value { + constructor(identifier, type, value, parameters, code) { + // value is only "procedure" or "function" + // type is its return type + super(identifier, type, value); + this.parameters = parameters; + this.code = code; + } + + toString() { + return ( + `function ${this.identifier} (` + + this.parameters.map((x) => x.toString()).join(", ") + + ")" + ); + } + + isProcedure() { + return this.value === 'procedure'; + } + + call(args) { + if (!Array.isArray(args)) { + throw new Error(`Args should be a list, not ${typeof args}`); + } + for (let i = 0; i < this.parameters.length; i++) { + // On a moins d'arguments que de paramètres + if (i >= args.length) { + for (let j = i; j < this.parameters.length; j++) { + if (!this.parameters[j].hasDefault()) { + throw new Error(`Missing parameter #${j}: ${this.parameters[j]}`); + } + args.push(this.parameters[j].getDefault()); + } + } else if (!kindOf(typeJStoAsh(args[i]), this.parameters[i].getType())) { + throw new Error(`Wrong parameter type at #${i}: ${this.parameters[i]} expected vs ${args[i]} : ${typeJStoAsh(args[i])}`); + } + } + return this.code(args); + } +} + +class Library { + static GlobalInterpreter; + + static init(globalInterpreter) { + Library.GlobalInterpreter = globalInterpreter; + } + + static call(idFun, args) { + if (idFun in table) { + return table[idFun].call(args); + } + throw new Error(`[ERROR] Unknown function ${idFun}`); + } + + //------------------------------------------------------------------------- + // Console functions + //------------------------------------------------------------------------- + + static log (args) { + Library.GlobalInterpreter.output_function(args.join(' ')); + return nil; + } + + //------------------------------------------------------------------------- + // Graphic functions + //------------------------------------------------------------------------- + + static clear(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + context.clearRect(0, 0, 640, 480); + } + return nil; + } + + static line(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + let x1 = args[0]; + let y1 = args[1]; + let x2 = args[2]; + let y2 = args[3]; + context.lineWidth = args[4]; + context.strokeStyle = args[5]; + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + return nil; + } + + static circle(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + let centerX = args[0]; + let centerY = args[1]; + let radius = args[2]; + let color = args[3]; + let full = args[4]; + context.beginPath(); + context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false); + if (full) { + if (color !== nil) context.fillStyle = color; + context.fill(); + } else { + if (color !== nil) context.strokeStyle = color; + context.stroke(); + } + } + return nil; + } + + static rect(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + if (args[5]) { + if (args[4] !== nil) context.fillStyle = args[4]; + context.fillRect( + args[0], + args[1], + args[2], + args[3] + ); + } else { + if (args[4] !== nil) context.strokeStyle = args[4]; + context.strokeRect( + args[0], + args[1], + args[2], + args[3] + ); + } + } + return nil; + } + + static draw(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + context.drawImage(args[2], args[0], args[1]); + } + return nil; + } + + static text(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + ctx.fillText(args[0], args[1], args[2]); + } + return nil; + } + + static setFont(args) { + let context = Library.GlobalInterpreter.getContext(); + context.font = `${args[1]}px ${args[0]}`; + } + + static setFill(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + context.fillStyle = args[0]; + } + } + + static setStrokeStyle(args) { + let context = Library.GlobalInterpreter.getContext(); + if (context !== null) { + context.strokeStyle = args[0]; + } + } +} + +//----------------------------------------------------------------------------- +// Function descriptions +//----------------------------------------------------------------------------- + +const table = { + 'log': new Function( + 'log', + nil, + 'procedure', + [ + new Parameter('o', 'any') + ], + Library.log + ), + 'clear': new Function( + 'clear', + nil, + 'procedure', + [], + Library.clear + ), + 'line': new Function( + 'line', + nil, + 'procedure', + [ + new Parameter('x1', 'nat'), + new Parameter('y1', 'nat'), + new Parameter('x2', 'nat'), + new Parameter('y2', 'nat'), + new Parameter('color', 'str', nil) + ], + Library.line + ), + 'circle': new Function( + 'circle', + nil, + 'procedure', + [ + new Parameter('x', 'nat'), + new Parameter('y', 'nat'), + new Parameter('r', 'nat'), + new Parameter('color', 'str', nil), + new Parameter('fill', 'bool', false) + ], + Library.circle + ), + 'rect': new Function( + 'rect', + nil, + 'procedure', + [ + new Parameter('x', 'nat'), + new Parameter('y', 'nat'), + new Parameter('width', 'nat'), + new Parameter('height', 'nat'), + new Parameter('color', 'str', nil), + new Parameter('fill', 'bool', false) + ], + Library.rect + ), + 'draw': new Function( + 'draw', + nil, + 'procedure', + [ + new Parameter('x', 'nat'), + new Parameter('y', 'nat'), + new Parameter('image', 'str') + ], + Library.draw + ), + 'text': new Function( + 'text', + nil, + 'procedure', + [ + new Parameter('x', 'nat'), + new Parameter('y', 'nat'), + new Parameter('s', 'str') + ], + Library.text + ), + 'set_font': new Function( + 'set_font', + nil, + 'procedure', + [ + new Parameter('police', 'str'), + new Parameter('size', 'nat') + ], + Library.setFont + ), + 'set_fill': new Function( + 'set_fill', + nil, + 'procedure', + [ + new Parameter('c', 'str') + ], + Library.setFill + ), + 'set_stroke': new Function( + 'set_stroke', + nil, + 'procedure', + [ + new Parameter('c', 'str') + ], + Library.setStrokeStyle + ) +}; + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +export { Library, Function, Value, nil }; diff --git a/ash/translator.mjs b/ash/translator.mjs index 9b8d70b..fac35b5 100644 --- a/ash/translator.mjs +++ b/ash/translator.mjs @@ -128,7 +128,6 @@ class Translator { } return output; case 'Call': - // TODO return nil; case 'Integer': case 'Float':