const fileSystem = require('fs');
const { parse, parseApply, parseExpression } = require('./parse.js');
//para que specialForms no tenga ningun metodo heredado
const specialForms = Object.create(null);
specialForms.if = (args, scope) => {
if (args.length !== 3) {
throw new SyntaxError('Wrong number of args to if');
} else if (evaluate(args[0], scope) !== false) {
return evaluate(args[1], scope);
} else {
return evaluate(args[2], scope);
}
};
specialForms.while = (args, scope) => {
debugger;
if (args.length !== 2) {
throw new SyntaxError('Wrong number of args to while');
}
// args[0] es la condicion, args[1] el codigo
while (evaluate(args[0], scope) !== false) {
evaluate(args[1], scope);
}
//egg no conoce undefined
return false;
};
// ejecuta las sentencias que le pasemos una a una
specialForms.do = (args, scope) => {
let value = false;
for (const arg of args) {
value = evaluate(arg, scope);
}
return value;
};
/**
*
* @param {Array<Object>} args array de objetos nodo a los que le asignaremos el valor
* el valor se considera que es el ultimo de los argumentos
* @param {Object} scope actual scope
* @returns El valor asignado
* @description recibe un array de nodos de los cuales, el ultimo de ellos se tratara
* como el valor a asignar y el resto de los nodos se tratar como variables a las que
* se le asignara ese valor
*/
specialForms.define = (args, scope) => {
/*if (args.length !== 2) {
throw new SyntaxError('Incorrect use of define');
}
if (args[0].type !== 'word') {
throw new SyntaxError(`Incorrect use of define, ${args[0].name} is not a correct variable name`);
}
let value = evaluate(args[1], scope);
scope[args[0].name] = value;
return value;*/
if (args.length < 2) {
throw new SyntaxError('Incorrect use of define, expected more arguments');
}
const value = evaluate(args[args.length - 1], scope);
for (let i = 0; i < args.length - 1; i++) {
if (args[i].type !== 'word') {
throw new SyntaxError(`Incorrect use of define, ${args[i].name} is not a correct variable name`);
}
//scope.hasOwnProperty = Object.hasOwnProperty;
// como no hereda de object, scope no tiene hasOwnProperty
//if (scope.hasOwnProperty(variable.name)) {
if (Object.prototype.hasOwnProperty.call(scope, args[i].name)) {
throw new SyntaxError(`Redefinition of ${args[i].name} variable.`);
} else {
scope[args[i].name] = value;
}
}
return value;
};
/**
*
* @param {Array<Object>} args array de objetos nodo a los que le asignaremos el valor
* el valor se considera que es el ultimo de los argumentos
* @param {Object} scope actual scope
* @returns El valor asignado
* @description recibe un array de nodos de los cuales, el ultimo de ellos se tratara
* como el valor a asignar y el resto de los nodos se tratar como variables ya existentes
* a las que se le asignara ese valor
*/
specialForms.set = (args, scope) => {
if (args.length < 2) {
throw new SyntaxError(`Incorrect use of set, more arguments expected`);
}
const value = evaluate(args[args.length - 1], scope);
for (let i = 0; i < args.length - 1; i++) {
if (args[i].type !== 'word') {
throw new SyntaxError(`Incorrect use of set, ${args[i].name} is not a correct variable name`);
}
//if (scope[variable.name] !== undefined) {
if (args[i].name in scope) {
scope[args[i].name] = value;
} else {
throw new SyntaxError(`Incorrect use of set, ${args[i].name} is not defined on this scope`);
}
}
return value;
};
/**
* @param {Array<Object>} args the AST of the function body
* @param {Object} scope the actual enviroment, the function can access
* external scope, but it has his own scope.
* @returns {Function} the JavaScript function version of the egg funcion
* definition
*/
specialForms['->'] = specialForms['fun'] = (args, scope) => {
if (!args.length) {
throw new SyntaxError('Functions need a body');
}
let body = args[args.length - 1];
// params contiene solo el nombre de los argumentos
let params = args.slice(0, args.length - 1).map(expr => {
if (expr.type !== 'word') {
throw new SyntaxError('Parameter names on functions must be words');
}
return expr.name;
});
return function () {
if (arguments.length !== params.length) {
throw new TypeError('Wrong number of arguments');
}
// el scope de la funcion es el scope actual + los argumentos
let localScope = Object.create(scope);
for (let i = 0; i < arguments.length; i++) {
localScope[params[i]] = arguments[i];
}
return evaluate(body, localScope);
};
};
/**
* @param {Object} expr Abstract Syntax Tree to evaluate
* @param {Object} scope Actual enviroment
* @returns The value of the expression
* @description evaluate the given expression on the given enviroment/scope
* and return his value
*/
function evaluate(expr, scope) {
if (expr.type === 'value') {
return expr.value;
} else if (expr.type === 'word') {
//si es una variable declarada, su valor debe estar en el scope
if (expr.name in scope) {
return scope[expr.name];
}
throw new ReferenceError(`Undefined binding: ${expr.name}`);
} else if (expr.type === 'apply') {//es una funcion
let { operator, args } = expr;
//special forms son if, while...
if (operator.type === 'word' && operator.name in specialForms) {
return specialForms[operator.name](args, scope);
} else {
let op = evaluate(operator, scope);
if (typeof op === 'function') {
// ... pasa como argumentos cada uno de los elementos del vector
// que devuelve map.
// map aplica la funcion pasada a cada elemento de un vector y almacena
// lo que retornen esas funciones en un nuevo vector
return op(...args.map(arg => evaluate(arg, scope)));
}
throw new TypeError('Applying a non-function');
}
}
}
const topScope = Object.create(null);
topScope.true = true;
topScope.false = false;
for (const operator of ['+', '-', '*', '/', '<', '>', '>=', '<=', '==', '===']) {
topScope[operator] = Function('a, b', `return a ${operator} b;`);
}
/**
* @param {...any} values Values to print
* @returns Last printed value
* @description print one by one the given arguments with console.log
*/
topScope.print = function (...values) {
for (const value of values) {
console.log(value);
}
return values[values.length - 1];
};
/**
* @param {String} program String that contains an egg program
* @returns the last evaluated value of the egg program
* @description evaluate the egg program that is inside of the given string
*/
function run(program) {
//utiliza Object.create para crear una copia y no modificar el scope global
return evaluate(parse(program), Object.create(topScope));
}
/**
* @param {Object} program compiled egg program (JSON) evm format
* @returns the last evaluated value of the egg program
* @description evaluate an egg program, the program must be on evm (JSON) format
*/
function runEVM(program) {
return evaluate(JSON.parse(program), Object.create(topScope));
}
/**
* @param {String} fileRoute to the .egg program
* @returns the last evaluated value of the egg program
* @description open a .egg file and run it with the run function
*/
function runFromFile(fileRoute) {
let output;
try {
output = run(fileSystem.readFileSync(fileRoute));
} catch (error) {
console.log('Error en runFromFile:', error);
}
return output;
}
/**
* @param {String} fileRoute to the file that contains a compiled egg program
* @returns the last evaluated value of the egg program
* @description read the file and run it with runEVM
*/
function runFromEVM(fileRoute) {
let output;
try {
output = runEVM(fileSystem.readFileSync(fileRoute, 'utf-8'));
} catch (error) {
console.log('Error en runFromEVM:', error);
}
return output;
}
module.exports = { run, topScope, specialForms, parse, evaluate, runFromFile, runFromEVM };