@gobstones/gobstones-parser
Version:
Gobstones parser
1,512 lines (1,508 loc) • 185 kB
JavaScript
/* eslint-disable no-underscore-dangle */
/* A SourceReader represents the current position in a source file.
* It keeps track of line and column numbers.
* Methods are non-destructive. For example:
*
* let r = new SourceReader('foo.gbs', 'if\n(True)');
*
* r.peek(); // ~~> 'i'
* r = r.consumeCharacter(); // Note: returns a new file reader.
*
* r.peek(); // ~~> 'f'
* r = r.consumeCharacter();
*
* r.peek(); // ~~> '\n'
* r = r.consumeCharacter('\n');
*
* r.line(); // ~~> 2
*/
class SourceReader {
constructor(filename, string) {
this._filename = filename; // Filename
this._string = string; // Source of the current file
this._index = 0; // Index in the current file
this._line = 1; // Line in the current file
this._column = 1; // Column in the current file
this._regions = []; // Lexical (static) stack of regions
}
_clone() {
const r = new SourceReader(this._filename, this._string);
r._index = this._index;
r._line = this._line;
r._column = this._column;
r._regions = this._regions;
return r;
}
get filename() {
return this._filename;
}
get line() {
return this._line;
}
get column() {
return this._column;
}
get region() {
if (this._regions.length > 0) {
return this._regions[0];
}
else {
return '';
}
}
/* Consume one character */
consumeCharacter() {
const r = this._clone();
if (r.peek() === '\n') {
r._line++;
r._column = 1;
}
else {
r._column++;
}
r._index++;
return r;
}
/* Consume characters from the input, one per each character in the string
* (the contents of the string are ignored). */
consumeString(string) {
let r = this._clone();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ of string) {
r = r.consumeCharacter();
}
return r;
}
/* Returns the SourceReader after consuming an 'invisible' character.
* Invisible characters affect the index but not the line or column.
*/
consumeInvisibleCharacter() {
const r = this._clone();
r._index++;
return r;
}
/* Consume 'invisible' characters from the input, one per each character
* in the string */
consumeInvisibleString(string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let r = this;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const _ of string) {
r = r.consumeInvisibleCharacter();
}
return r;
}
/* Return true if the substring occurs at the current point. */
startsWith(sub) {
const i = this._index;
const j = this._index + sub.length;
return j <= this._string.length && this._string.substring(i, j) === sub;
}
/* Return true if we have reached the end of the current file */
eof() {
return this._index >= this._string.length;
}
/* Return the current character, assuming we have not reached EOF */
peek() {
return this._string[this._index];
}
/* Push a region to the stack of regions (non-destructively) */
beginRegion(region) {
const r = this._clone();
r._regions = [region].concat(r._regions);
return r;
}
/* Pop a region from the stack of regions (non-destructively) */
endRegion() {
const r = this._clone();
if (r._regions.length > 0) {
r._regions = r._regions.slice(1);
}
return r;
}
}
/* Return a source reader that represents an unknown position */
const UnknownPosition = new SourceReader('(?)', '');
/* An instance of MultifileReader represents a scanner for reading
* source code from a list of files.
*/
class MultifileReader {
/* The 'input' parameter should be either:
* (1) a string. e.g. 'program {}', or
* (2) a map from filenames to strings, e.g.
* {
* 'foo.gbs': 'program { P() }',
* 'bar.gbs': 'procedure P() {}',
* }
*/
constructor(input) {
if (typeof input === 'string') {
input = { '(?)': input };
}
this._filenames = Object.keys(input);
this._filenames.sort();
this._input = input;
this._index = 0;
}
/* Return true if there are more files */
moreFiles() {
return this._index + 1 < this._filenames.length;
}
/* Advance to the next file */
nextFile() {
this._index++;
}
/* Return a SourceReader for the current files */
readCurrentFile() {
if (this._index < this._filenames.length) {
const filename = this._filenames[this._index];
return new SourceReader(filename, this._input[filename]);
}
else {
return new SourceReader('(?)', '');
}
}
}
/* Token tags are constant symbols */
const T_EOF = Symbol.for('T_EOF'); // End of file
const T_NUM = Symbol.for('T_NUM'); // Number
const T_STRING = Symbol.for('T_STRING'); // String constant
const T_UPPERID = Symbol.for('T_UPPERID'); // Uppercase identifier
const T_LOWERID = Symbol.for('T_LOWERID'); // Lowercase identifier
/* Keywords */
const T_PROGRAM = Symbol.for('T_PROGRAM');
const T_INTERACTIVE = Symbol.for('T_INTERACTIVE');
const T_PROCEDURE = Symbol.for('T_PROCEDURE');
const T_FUNCTION = Symbol.for('T_FUNCTION');
const T_RETURN = Symbol.for('T_RETURN');
const T_IF = Symbol.for('T_IF');
const T_THEN = Symbol.for('T_THEN');
const T_ELSEIF = Symbol.for('T_ELSEIF');
const T_ELSE = Symbol.for('T_ELSE');
const T_CHOOSE = Symbol.for('T_CHOOSE');
const T_WHEN = Symbol.for('T_WHEN');
const T_OTHERWISE = Symbol.for('T_OTHERWISE');
const T_MATCHING = Symbol.for('T_MATCHING');
const T_SELECT = Symbol.for('T_SELECT');
const T_ON = Symbol.for('T_ON');
const T_REPEAT = Symbol.for('T_REPEAT');
const T_FOREACH = Symbol.for('T_FOREACH');
const T_IN = Symbol.for('T_IN');
const T_WHILE = Symbol.for('T_WHILE');
const T_SWITCH = Symbol.for('T_SWITCH');
const T_TO = Symbol.for('T_TO');
const T_LET = Symbol.for('T_LET');
const T_NOT = Symbol.for('T_NOT');
const T_DIV = Symbol.for('T_DIV');
const T_MOD = Symbol.for('T_MOD');
const T_TYPE = Symbol.for('T_TYPE');
const T_IS = Symbol.for('T_IS');
const T_RECORD = Symbol.for('T_RECORD');
const T_VARIANT = Symbol.for('T_VARIANT');
const T_CASE = Symbol.for('T_CASE');
const T_FIELD = Symbol.for('T_FIELD');
const T_UNDERSCORE = Symbol.for('T_UNDERSCORE');
const T_TIMEOUT = Symbol.for('T_TIMEOUT');
/* Symbols */
const T_LPAREN = Symbol.for('T_LPAREN');
const T_RPAREN = Symbol.for('T_RPAREN');
const T_LBRACE = Symbol.for('T_LBRACE');
const T_RBRACE = Symbol.for('T_RBRACE');
const T_LBRACK = Symbol.for('T_LBRACK');
const T_RBRACK = Symbol.for('T_RBRACK');
const T_COMMA = Symbol.for('T_COMMA');
const T_SEMICOLON = Symbol.for('T_SEMICOLON');
const T_ELLIPSIS = Symbol.for('T_ELLIPSIS');
const T_RANGE = Symbol.for('T_RANGE');
const T_GETS = Symbol.for('T_GETS');
const T_PIPE = Symbol.for('T_PIPE');
const T_ARROW = Symbol.for('T_ARROW');
const T_ASSIGN = Symbol.for('T_ASSIGN');
const T_EQ = Symbol.for('T_EQ');
const T_NE = Symbol.for('T_NE');
const T_LE = Symbol.for('T_LE');
const T_GE = Symbol.for('T_GE');
const T_LT = Symbol.for('T_LT');
const T_GT = Symbol.for('T_GT');
const T_AND = Symbol.for('T_AND');
const T_OR = Symbol.for('T_OR');
const T_CONCAT = Symbol.for('T_CONCAT');
const T_PLUS = Symbol.for('T_PLUS');
const T_MINUS = Symbol.for('T_MINUS');
const T_TIMES = Symbol.for('T_TIMES');
const T_POW = Symbol.for('T_POW');
/* A token is given by:
* - A token tag (e.g. T_LOWERID, T_NUM).
* - Possibly, a value (e.g. 'nroBolitas', 8).
* When the value is irrelevant, we provide null by convention.
* - Two positions, representing its location in the source. */
class Token {
constructor(tag, value, startPos, endPos) {
this._tag = tag;
this._value = value;
this._startPos = startPos;
this._endPos = endPos;
}
toString() {
const _tag = this._tag;
const tag = typeof _tag === 'symbol' ? Symbol.keyFor(_tag).substring(2) : _tag;
switch (tag) {
case 'NUM':
case 'STRING':
case 'UPPERID':
case 'LOWERID':
return tag + '("' + this._value.toString() + '")';
default:
return tag;
}
}
get tag() {
return this._tag;
}
get value() {
return this._value;
}
get startPos() {
return this._startPos;
}
get endPos() {
return this._endPos;
}
}
/* eslint-disable camelcase */
const N_Main = Symbol.for('N_Main');
/* Definitions */
const N_DefProgram = Symbol.for('N_DefProgram');
const N_DefInteractiveProgram = Symbol.for('N_DefInteractiveProgram');
const N_DefProcedure = Symbol.for('N_DefProcedure');
const N_DefFunction = Symbol.for('N_DefFunction');
const N_DefType = Symbol.for('N_DefType');
/* Statements */
const N_StmtBlock = Symbol.for('N_StmtBlock');
const N_StmtReturn = Symbol.for('N_StmtReturn');
const N_StmtIf = Symbol.for('N_StmtIf');
const N_StmtRepeat = Symbol.for('N_StmtRepeat');
const N_StmtForeach = Symbol.for('N_StmtForeach');
const N_StmtWhile = Symbol.for('N_StmtWhile');
const N_StmtSwitch = Symbol.for('N_StmtSwitch');
const N_StmtAssignVariable = Symbol.for('N_StmtAssignVariable');
const N_StmtAssignTuple = Symbol.for('N_StmtAssignTuple');
const N_StmtProcedureCall = Symbol.for('N_StmtProcedureCall');
/* Patterns */
const N_PatternWildcard = Symbol.for('N_PatternWildcard');
const N_PatternVariable = Symbol.for('N_PatternVariable');
const N_PatternNumber = Symbol.for('N_PatternNumber');
const N_PatternStructure = Symbol.for('N_PatternStructure');
const N_PatternTuple = Symbol.for('N_PatternTuple');
const N_PatternTimeout = Symbol.for('N_PatternTimeout');
/* Expressions */
const N_ExprVariable = Symbol.for('N_ExprVariable');
const N_ExprConstantNumber = Symbol.for('N_ExprConstantNumber');
const N_ExprConstantString = Symbol.for('N_ExprConstantString');
const N_ExprChoose = Symbol.for('N_ExprChoose');
const N_ExprMatching = Symbol.for('N_ExprMatching');
const N_ExprList = Symbol.for('N_ExprList');
const N_ExprRange = Symbol.for('N_ExprRange');
const N_ExprTuple = Symbol.for('N_ExprTuple');
const N_ExprStructure = Symbol.for('N_ExprStructure');
const N_ExprStructureUpdate = Symbol.for('N_ExprStructureUpdate');
const N_ExprFunctionCall = Symbol.for('N_ExprFunctionCall');
/* SwitchBranch: pattern -> body */
const N_SwitchBranch = Symbol.for('N_SwitchBranch');
/* MatchingBranch: pattern -> body */
const N_MatchingBranch = Symbol.for('N_MatchingBranch');
/* FieldBinding: fieldName <- value */
const N_FieldBinding = Symbol.for('N_FieldBinding');
/* ConstructorDeclaration */
const N_ConstructorDeclaration = Symbol.for('N_ConstructorDeclaration');
/* Helper functions for the ASTNode toString method */
function indent(string) {
const lines = [];
for (const line of string.split('\n')) {
lines.push(' ' + line);
}
return lines.join('\n');
}
function showAST(node) {
if (node === undefined) {
return 'null';
}
else if (node instanceof Array) {
return '[\n' + showASTs(node).join(',\n') + '\n]';
}
else if (node instanceof Token) {
return node.toString();
}
const tag = Symbol.keyFor(node.tag).substring(2);
return tag + '(\n' + showASTs(node.children).join(',\n') + '\n)';
}
function showASTs(nodes) {
const res = [];
for (const node of nodes) {
res.push(indent(showAST(node)));
}
return res;
}
/** An instance of ASTNode represents a node of the abstract syntax tree.
* - tag should be a node tag symbol.
* - children should be (recursively) a possibly empty array of ASTNode's.
* - startPos and endPos represent the starting and ending
* position of the code fragment in the source code, to aid error
* reporting.
**/
class ASTNode {
constructor(tag, children) {
this._tag = tag;
this._children = children;
this._startPos = UnknownPosition;
this._endPos = UnknownPosition;
this._attributes = {};
/* Assert this invariant to protect against common mistakes. */
if (!(children instanceof Array)) {
throw Error('The children of an ASTNode should be an array.');
}
}
toMulangLike() {
return {
tag: this._tag.toString().replace(/(^Symbol\(|\)$)/g, ''),
contents: this._children.map((node) => {
if (node === undefined) {
return 'null';
}
else if (node instanceof Array) {
return new ASTNode(Symbol('?'), node).toMulangLike().contents;
}
else if (node instanceof ASTNode) {
return node.toMulangLike();
}
else if (node instanceof Token) {
return node.toString();
}
else {
return '?';
}
})
};
}
toString() {
return showAST(this);
}
get tag() {
return this._tag;
}
get children() {
return this._children;
}
set startPos(position) {
this._startPos = position;
}
get startPos() {
return this._startPos;
}
set endPos(position) {
this._endPos = position;
}
get endPos() {
return this._endPos;
}
get attributes() {
return this._attributes;
}
set attributes(attributes) {
this._attributes = attributes;
}
}
/* Main */
class ASTMain extends ASTNode {
constructor(definitions) {
super(N_Main, definitions);
}
get definitions() {
return this.children;
}
}
/* Definitions */
class ASTDefProgram extends ASTNode {
constructor(body) {
super(N_DefProgram, [body]);
}
get body() {
return this.children[0];
}
}
class ASTNodeWithBranches extends ASTNode {
get branches() {
return this.children;
}
}
class ASTDefInteractiveProgram extends ASTNodeWithBranches {
constructor(branches) {
super(N_DefInteractiveProgram, branches);
}
get branches() {
return this.children;
}
}
class ASTDefProcedure extends ASTNode {
constructor(name, parameters, body) {
super(N_DefProcedure, [name, parameters, body]);
}
get name() {
return this.children[0];
}
get parameters() {
return this.children[1];
}
get body() {
return this.children[2];
}
}
class ASTDefFunction extends ASTNode {
constructor(name, parameters, body) {
super(N_DefFunction, [name, parameters, body]);
}
get name() {
return this.children[0];
}
get parameters() {
return this.children[1];
}
get body() {
return this.children[2];
}
}
class ASTDefType extends ASTNode {
constructor(typeName, constructorDeclarations) {
super(N_DefType, [typeName, constructorDeclarations]);
}
get typeName() {
return this.children[0];
}
get constructorDeclarations() {
return this.children[1];
}
}
/* Statements */
class ASTStmtBlock extends ASTNode {
constructor(statements) {
super(N_StmtBlock, statements);
}
get statements() {
return this.children;
}
}
class ASTStmtReturn extends ASTNode {
constructor(result) {
super(N_StmtReturn, [result]);
}
get result() {
return this.children[0];
}
}
class ASTStmtIf extends ASTNode {
// Note: elseBlock may be null
constructor(condition, thenBlock, elseBlock) {
super(N_StmtIf, [condition, thenBlock, elseBlock]);
}
get condition() {
return this.children[0];
}
get thenBlock() {
return this.children[1];
}
get elseBlock() {
return this.children[2];
}
}
class ASTStmtRepeat extends ASTNode {
constructor(times, body) {
super(N_StmtRepeat, [times, body]);
}
get times() {
return this.children[0];
}
get body() {
return this.children[1];
}
}
class ASTNodeWithPattern extends ASTNode {
get pattern() {
return this.children[0];
}
}
class ASTStmtForeach extends ASTNodeWithPattern {
constructor(pattern, range, body) {
super(N_StmtForeach, [pattern, range, body]);
}
get pattern() {
return this.children[0];
}
get range() {
return this.children[1];
}
get body() {
return this.children[2];
}
}
class ASTStmtWhile extends ASTNode {
constructor(condition, body) {
super(N_StmtWhile, [condition, body]);
}
get condition() {
return this.children[0];
}
get body() {
return this.children[1];
}
}
class ASTStmtSwitch extends ASTNodeWithBranches {
constructor(subject, branches) {
super(N_StmtSwitch, [subject, branches]);
}
get subject() {
return this.children[0];
}
get branches() {
return this.children[1];
}
}
class ASTSwitchBranch extends ASTNodeWithPattern {
constructor(pattern, body) {
super(N_SwitchBranch, [pattern, body]);
}
get pattern() {
return this.children[0];
}
get body() {
return this.children[1];
}
}
class ASTMatchingBranch extends ASTNodeWithPattern {
constructor(pattern, body) {
super(N_MatchingBranch, [pattern, body]);
}
get pattern() {
return this.children[0];
}
get body() {
return this.children[1];
}
}
class ASTStmtAssignVariable extends ASTNode {
constructor(variable, value) {
super(N_StmtAssignVariable, [variable, value]);
}
get variable() {
return this.children[0];
}
get value() {
return this.children[1];
}
}
class ASTStmtAssignTuple extends ASTNode {
constructor(variables, value) {
super(N_StmtAssignTuple, [variables, value]);
}
get variables() {
return this.children[0];
}
get value() {
return this.children[1];
}
}
class ASTStmtProcedureCall extends ASTNode {
constructor(procedureName, args) {
super(N_StmtProcedureCall, [procedureName, args]);
}
get procedureName() {
return this.children[0];
}
get args() {
return this.children[1];
}
}
class ASTPatternWildcard extends ASTNode {
constructor(statement) {
super(N_PatternWildcard, []);
}
get boundVariables() {
return [];
}
}
class ASTPatternVariable extends ASTNode {
constructor(variableName) {
super(N_PatternVariable, [variableName]);
}
get variableName() {
return this.children[0];
}
get boundVariables() {
return [this.children[0]];
}
}
class ASTPatternNumber extends ASTNode {
constructor(number) {
super(N_PatternNumber, [number]);
}
get number() {
return this.children[0];
}
get boundVariables() {
return [];
}
}
class ASTPatternStructure extends ASTNode {
constructor(constructorName, parameters) {
super(N_PatternStructure, [constructorName, parameters]);
}
get constructorName() {
return this.children[0];
}
get boundVariables() {
return this.children[1];
}
}
class ASTPatternTuple extends ASTNode {
constructor(parameters) {
super(N_PatternTuple, parameters);
}
get boundVariables() {
return this.children;
}
}
class ASTPatternTimeout extends ASTNode {
constructor(timeout) {
super(N_PatternTimeout, [timeout]);
}
get boundVariables() {
return [];
}
get timeout() {
return parseInt(this.children[0].value, 10);
}
}
class ASTExprVariable extends ASTNode {
constructor(variableName) {
super(N_ExprVariable, [variableName]);
}
get variableName() {
return this.children[0];
}
}
class ASTExprConstantNumber extends ASTNode {
constructor(number) {
super(N_ExprConstantNumber, [number]);
}
get number() {
return this.children[0];
}
}
class ASTExprConstantString extends ASTNode {
constructor(string) {
super(N_ExprConstantString, [string]);
}
get string() {
return this.children[0];
}
}
class ASTExprChoose extends ASTNode {
constructor(condition, trueExpr, falseExpr) {
super(N_ExprChoose, [condition, trueExpr, falseExpr]);
}
get condition() {
return this.children[0];
}
get trueExpr() {
return this.children[1];
}
get falseExpr() {
return this.children[2];
}
}
class ASTExprMatching extends ASTNodeWithBranches {
constructor(subject, branches) {
super(N_ExprMatching, [subject, branches]);
}
get subject() {
return this.children[0];
}
get branches() {
return this.children[1];
}
}
class ASTExprList extends ASTNode {
constructor(elements) {
super(N_ExprList, elements);
}
get elements() {
return this.children;
}
}
class ASTExprRange extends ASTNode {
// Note: second may be null
constructor(first, second, last) {
super(N_ExprRange, [first, second, last]);
}
get first() {
return this.children[0];
}
get second() {
return this.children[1];
}
get last() {
return this.children[2];
}
}
class ASTExprTuple extends ASTNode {
constructor(elements) {
super(N_ExprTuple, elements);
}
get elements() {
return this.children;
}
}
class ASTExprStructure extends ASTNode {
constructor(constructorName, fieldBindings) {
super(N_ExprStructure, [constructorName, fieldBindings]);
}
get constructorName() {
return this.children[0];
}
get fieldBindings() {
return this.children[1];
}
fieldNames() {
const names = [];
for (const fieldBinding of this.fieldBindings) {
names.push(fieldBinding.fieldName.value);
}
return names;
}
}
class ASTExprStructureUpdate extends ASTNode {
constructor(constructorName, original, fieldBindings) {
super(N_ExprStructureUpdate, [constructorName, original, fieldBindings]);
}
get constructorName() {
return this.children[0];
}
get original() {
return this.children[1];
}
get fieldBindings() {
return this.children[2];
}
fieldNames() {
const names = [];
for (const fieldBinding of this.fieldBindings) {
names.push(fieldBinding.fieldName.value);
}
return names;
}
}
class ASTExprFunctionCall extends ASTNode {
constructor(functionName, args) {
super(N_ExprFunctionCall, [functionName, args]);
}
get functionName() {
return this.children[0];
}
get args() {
return this.children[1];
}
}
class ASTFieldBinding extends ASTNode {
constructor(fieldName, value) {
super(N_FieldBinding, [fieldName, value]);
}
get fieldName() {
return this.children[0];
}
get value() {
return this.children[1];
}
}
class ASTConstructorDeclaration extends ASTNode {
constructor(constructorName, fieldNames) {
super(N_ConstructorDeclaration, [constructorName, fieldNames]);
}
get constructorName() {
return this.children[0];
}
get fieldNames() {
return this.children[1];
}
}
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable quote-props */
/* istanbul ignore file */
const keyword$1 = (palabra) => 'la palabra clave "' + palabra + '"';
function pluralize$1(n, singular, plural) {
if (n === 0) {
return 'ningún ' + singular;
}
else if (n === 1) {
return 'un ' + singular;
}
else {
return n.toString() + ' ' + plural;
}
}
// Only for typing purposes
// eslint-disable-next-line @typescript-eslint/ban-types
const toFunc$2 = (x) => x;
function ordinalNumber(n) {
const units = [
'',
'primer',
'segundo',
'tercer',
'cuarto',
'quinto',
'sexto',
'séptimo',
'octavo',
'noveno'
];
if (n >= 1 && n <= 9) {
return units[n];
}
else {
return '#' + n.toString();
}
}
function describeType(type) {
if (type.isInteger()) {
return ['m', 'número', 'números'];
}
else if (type.isBoolean()) {
return ['m', 'booleano', 'booleanos'];
}
else if (type.isColor()) {
return ['m', 'color', 'colores'];
}
else if (type.isDirection()) {
return ['f', 'dirección', 'direcciones'];
}
else if (type.isList() && type.contentType.isAny()) {
return ['f', 'lista', 'listas'];
}
else if (type.isList()) {
const description = describeType(type.contentType);
if (description === undefined) {
return undefined;
}
else {
const plural = description[2];
return ['f', 'lista de ' + plural, 'listas de ' + plural];
}
}
else {
return undefined;
}
}
function describeTypeSingular(type) {
const description = describeType(type);
if (description === undefined) {
return type.toString();
}
else {
const singular = description[1];
return singular;
}
}
function typeAsNoun(type) {
const description = describeType(type);
if (description === undefined) {
return 'un valor de tipo ' + type.toString();
}
else {
const gender = description[0];
const singular = description[1];
if (gender === 'm') {
return 'un ' + singular;
}
else {
return 'una ' + singular;
}
}
}
function typeAsQualifierSingular(type) {
const description = describeType(type);
if (description === undefined) {
return 'de tipo ' + type.toString();
}
else {
const gender = description[0];
const singular = description[1];
if (gender === 'm') {
return 'un ' + singular;
}
else {
return 'una ' + singular;
}
}
}
function typeAsQualifierPlural(type) {
const description = describeType(type);
if (description === undefined) {
return 'de tipo ' + type.toString();
}
else {
const gender = description[0];
const plural = description[2];
if (gender === 'm') {
return plural;
}
else {
return plural;
}
}
}
function listOfTypes(types) {
const typeStrings = [];
for (const type of types) {
typeStrings.push(describeTypeSingular(type));
}
return typeStrings.join(', ');
}
function openingDelimiterName(delimiter) {
if (delimiter === '(' || delimiter === ')') {
return 'un paréntesis abierto "("';
}
else if (delimiter === '[' || delimiter === ']') {
return 'un corchete abierto "["';
}
else if (delimiter === '{' || delimiter === '}') {
return 'una llave abierta "{"';
}
else {
return delimiter;
}
}
function formatTypes(string, type1, type2) {
let result = '';
for (let i = 0; i < string.length; i++) {
if (string[i] === '%' && i + 1 < string.length) {
if (string[i + 1] === '%') {
result += '%';
i++;
}
else if (string[i + 1] === '1') {
result += typeAsNoun(type1);
i++;
}
else if (string[i + 1] === '2') {
result += typeAsNoun(type2);
i++;
}
else {
result += '%';
}
}
else {
result += string[i];
}
}
return result;
}
// eslint-disable-next-line @typescript-eslint/ban-types
const LOCALE_ES = {
/* Descriptions of syntactic constructions and tokens */
definition: 'una definición (de programa, función, procedimiento, o tipo)',
pattern: 'un patrón (comodín "_", constructor aplicado a variables, o tupla)',
statement: 'un comando',
expression: 'una expresión',
'procedure call': 'una invocación a un procedimiento',
'field name': 'el nombre de un campo',
T_EOF: 'el final del archivo',
T_NUM: 'un número',
T_STRING: 'una cadena (string)',
T_UPPERID: 'un identificador con mayúsculas',
T_LOWERID: 'un identificador con minúsculas',
T_PROGRAM: keyword$1('program'),
T_INTERACTIVE: keyword$1('interactive'),
T_PROCEDURE: keyword$1('procedure'),
T_FUNCTION: keyword$1('function'),
T_RETURN: keyword$1('return'),
T_IF: keyword$1('if'),
T_THEN: keyword$1('then'),
T_ELSE: keyword$1('else'),
T_REPEAT: keyword$1('repeat'),
T_FOREACH: keyword$1('foreach'),
T_IN: keyword$1('in'),
T_WHILE: keyword$1('while'),
T_SWITCH: keyword$1('switch'),
T_TO: keyword$1('to'),
T_LET: keyword$1('let'),
T_NOT: keyword$1('not'),
T_DIV: keyword$1('div'),
T_MOD: keyword$1('mod'),
T_TYPE: keyword$1('type'),
T_IS: keyword$1('is'),
T_CHOOSE: keyword$1('choose'),
T_WHEN: keyword$1('when'),
T_OTHERWISE: keyword$1('otherwise'),
T_MATCHING: keyword$1('matching'),
T_SELECT: keyword$1('select'),
T_ON: keyword$1('on'),
T_RECORD: keyword$1('record'),
T_VARIANT: keyword$1('variant'),
T_CASE: keyword$1('case'),
T_FIELD: keyword$1('field'),
T_UNDERSCORE: 'un guión bajo ("_")',
T_LPAREN: 'un paréntesis izquierdo ("(")',
T_RPAREN: 'un paréntesis derecho (")")',
T_LBRACE: 'una llave izquierda ("{")',
T_RBRACE: 'una llave derecha ("}")',
T_LBRACK: 'un corchete izquierdo ("[")',
T_RBRACK: 'un corchete derecho ("]")',
T_COMMA: 'una coma (",")',
T_SEMICOLON: 'un punto y coma (";")',
T_RANGE: 'un separador de rango ("..")',
T_GETS: 'una flecha hacia la izquierda ("<-")',
T_PIPE: 'una barra vertical ("|")',
T_ARROW: 'una flecha ("->")',
T_ASSIGN: 'un operador de asignación (":=")',
T_EQ: 'una comparación por igualdad ("==")',
T_NE: 'una comparación por desigualdad ("/=")',
T_LE: 'un menor o igual ("<=")',
T_GE: 'un mayor o igual (">=")',
T_LT: 'un menor estricto ("<")',
T_GT: 'un mayor estricto (">")',
T_AND: 'el "y" lógico ("&&")',
T_OR: 'el "o" lógico ("||")',
T_CONCAT: 'el operador de concatenación de listas ("++")',
T_PLUS: 'el operador de suma ("+")',
T_MINUS: 'el operador de resta ("-")',
T_TIMES: 'el operador de producto ("*")',
T_POW: 'el operador de potencia ("^")',
/* Local name categories */
LocalVariable: 'variable',
LocalIndex: 'índice',
LocalParameter: 'parámetro',
/* Descriptions of value types */
V_Integer: 'un número',
V_String: 'una cadena',
V_Tuple: 'una tupla',
V_List: 'una lista',
V_Structure: 'una estructura',
/* Lexer */
'errmsg:unclosed-multiline-comment': 'El comentario se abre pero nunca se cierra.',
'errmsg:unclosed-string-constant': 'La comilla que abre no tiene una comilla que cierra correspondiente.',
// eslint-disable-next-line max-len
'errmsg:numeric-constant-should-not-have-leading-zeroes': `Las constantes numéricas no se pueden escribir con ceros a la izquierda.`,
// eslint-disable-next-line max-len
'errmsg:identifier-must-start-with-alphabetic-character': `Los identificadores deben empezar con un caracter alfabético (a...z,A...Z).`,
'errmsg:unknown-token': (symbol) => 'Símbolo desconocido en la entrada: "' + symbol + '".',
'warning:empty-pragma': 'Directiva pragma vacía.',
'warning:unknown-pragma': (pragmaName) => 'Directiva pragma desconocida: "' + pragmaName + '".',
'errmsg:unmatched-opening-delimiter': (delimiter) => 'Se encontró ' + openingDelimiterName(delimiter) + ' pero nunca se cierra.',
'errmsg:unmatched-closing-delimiter': (delimiter) => 'Se encontró un "' +
delimiter +
'" ' +
'pero no había ' +
openingDelimiterName(delimiter) +
'.',
'errmsg:unknown-language-option': (option) => 'Opción desconocida. "' + option + '".',
/* Parser */
'errmsg:empty-source': 'El programa está vacío.',
'errmsg:expected-but-found': (expected, found) => `Se esperaba ${expected}. Se encontró: ${found}.`,
'errmsg:pattern-number-cannot-be-negative-zero': 'El patrón numérico no puede ser "-0".',
'errmsg:return-tuple-cannot-be-empty': 'El return tiene que devolver algo.',
'errmsg:pattern-tuple-cannot-be-singleton': 'El patrón para una tupla no puede tener una sola componente. ' +
'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.',
'errmsg:assignment-tuple-cannot-be-singleton': 'La asignación a una tupla no puede constar de una sola componente. ' +
'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.',
'errmsg:operators-are-not-associative': (op1, op2) => 'La expresión usa ' +
op1 +
' y ' +
op2 +
', pero estos operadores no se pueden asociar. ' +
'Quizás faltan paréntesis.',
'errmsg:obsolete-tuple-assignment': 'Se esperaba un comando pero se encontró un paréntesis izquierdo. ' +
'Nota: la sintaxis de asignación de tuplas "(x1, ..., xN) := y" ' +
'está obsoleta. Usar "let (x1, ..., xN) := y".',
/* Linter */
'errmsg:program-already-defined': (pos1, pos2) => 'Ya había un programa definido en ' +
pos1 +
'.\n' +
'No se puede definir un programa en ' +
pos2 +
'.',
'errmsg:procedure-already-defined': (name, pos1, pos2) => 'El procedimiento "' +
name +
'" está definido dos veces: ' +
'en ' +
pos1 +
' y en ' +
pos2 +
'.',
'errmsg:function-already-defined': (name, pos1, pos2) => 'La función "' +
name +
'" está definida dos veces: ' +
'en ' +
pos1 +
' y en ' +
pos2 +
'.',
'errmsg:type-already-defined': (name, pos1, pos2) => `El tipo "${name}" está definido dos veces: en ${pos1} y en ${pos2}.`,
'errmsg:constructor-already-defined': (name, pos1, pos2) => 'El constructor "' +
name +
'" está definido dos veces: ' +
'en ' +
pos1 +
' y en ' +
pos2 +
'.',
'errmsg:repeated-field-name': (constructorName, fieldName) => 'El campo "' +
fieldName +
'" no puede estar repetido ' +
'para el constructor "' +
constructorName +
'".',
'errmsg:function-and-field-cannot-have-the-same-name': (name, posFunction, posField) => 'El nombre "' +
name +
'" se usa ' +
'para una función en ' +
posFunction +
' y ' +
'para un campo en ' +
posField +
'.',
'errmsg:source-should-have-a-program-definition':
/* Note: the code may actually be completely empty, but
* we avoid this technicality since the message could be
* confusing. */
'El código debe tener una definición de "program { ... }".',
'errmsg:procedure-should-not-have-return': (name) => `El procedimiento "${name}" no debería tener un comando "return".`,
'errmsg:function-should-have-return': (name) => 'La función "' + name + '" debería tener un comando "return".',
'errmsg:return-statement-not-allowed-here': 'El comando "return" solo puede aparecer como el último comando ' +
'de una función o como el último comando del programa.',
'errmsg:local-name-conflict': (name, oldCat, oldPos, newCat, newPos) => 'Conflicto de nombres: "' +
name +
'" se usa dos veces: ' +
'como ' +
oldCat +
' en ' +
oldPos +
', y ' +
'como ' +
newCat +
' en ' +
newPos +
'.',
'errmsg:repeated-variable-in-tuple-assignment': (name) => `La variable "${name}" está repetida en la asignación de tuplas.`,
'errmsg:constructor-used-as-procedure': (name, type) => 'El procedimiento "' +
name +
'" no está definido. ' +
'El nombre "' +
name +
'" es el nombre de un constructor ' +
'del tipo "' +
type +
'".',
'errmsg:undefined-procedure': (name) => 'El procedimiento "' + name + '" no está definido.',
'errmsg:undefined-function': (name) => 'La función "' + name + '" no está definida.',
'errmsg:procedure-arity-mismatch': (name, expected, received) => '"El procedimiento "' +
name +
'" espera recibir ' +
toFunc$2(LOCALE_ES['<n>-parameters'])(expected) +
' pero se lo invoca con ' +
toFunc$2(LOCALE_ES['<n>-arguments'])(received) +
'.',
'errmsg:function-arity-mismatch': (name, expected, received) => 'La función "' +
name +
'" espera recibir ' +
toFunc$2(LOCALE_ES['<n>-parameters'])(expected) +
' pero se la invoca con ' +
toFunc$2(LOCALE_ES['<n>-arguments'])(received) +
'.',
'errmsg:structure-pattern-arity-mismatch': (name, expected, received) => 'El constructor "' +
name +
'" tiene ' +
toFunc$2(LOCALE_ES['<n>-fields'])(expected) +
' pero el patrón tiene ' +
toFunc$2(LOCALE_ES['<n>-parameters'])(received) +
'.',
'errmsg:type-used-as-constructor'(name, constructorNames) {
let msg;
if (constructorNames.length === 0) {
msg = '(no tiene constructores).';
}
else if (constructorNames.length === 1) {
msg = '(tiene un constructor: ' + constructorNames[0] + ').';
}
else {
msg = '(sus constructores son: ' + constructorNames.join(', ') + ').';
}
return ('El constructor "' +
name +
'" no está definido. ' +
'El nombre "' +
name +
'" es el nombre de un tipo ' +
msg);
},
'errmsg:procedure-used-as-constructor': (name) => 'El constructor "' +
name +
'" no está definido. ' +
'El nombre "' +
name +
'" es el nombre de un procedimiento.',
'errmsg:undeclared-constructor': (name) => 'El constructor "' + name + '" no está definido.',
'errmsg:wildcard-pattern-should-be-last': 'El comodín "_" debe estar en la última rama.',
'errmsg:variable-pattern-should-be-last': (name) => 'El patrón variable "' + name + '" tiene debe estar en la última rama.',
'errmsg:numeric-pattern-repeats-number': (number) => 'Hay dos ramas distintas para el número "' + number + '".',
'errmsg:structure-pattern-repeats-constructor': (name) => 'Hay dos ramas distintas para el constructor "' + name + '".',
'errmsg:structure-pattern-repeats-tuple-arity': (arity) => 'Hay dos ramas distintas para las tuplas de ' + arity.toString() + ' componentes.',
'errmsg:structure-pattern-repeats-timeout': 'Hay dos ramas distintas para el TIMEOUT.',
'errmsg:pattern-does-not-match-type': (expectedType, patternType) => 'Los patrones tienen que ser todos del mismo tipo. ' +
'El patrón debería ser de tipo ' +
expectedType +
'pero es de tipo ' +
patternType +
'.',
'errmsg:patterns-in-interactive-program-must-be-events': 'Los patrones de un "interactive program" deben ser eventos.',
'errmsg:patterns-in-interactive-program-cannot-be-variables': 'El patrón no puede ser una variable.',
'errmsg:patterns-in-switch-must-not-be-events': 'El patrón no puede ser un evento.',
'errmsg:structure-construction-repeated-field': (constructorName, fieldName) => 'El campo "' +
fieldName +
'" está repetido en ' +
'la instanciación del constructor "' +
constructorName +
'".',
'errmsg:structure-construction-invalid-field': (constructorName, fieldName) => 'El campo "' +
fieldName +
'" no es un campo válido ' +
'para el constructor "' +
constructorName +
'".',
'errmsg:structure-construction-missing-field': (constructorName, fieldName) => 'Falta darle valor al campo "' +
fieldName +
'" ' +
'del constructor "' +
constructorName +
'".',
'errmsg:structure-construction-cannot-be-an-event': (constructorName) => 'El constructor "' +
constructorName +
'" corresponde a un ' +
'evento, y solamente se puede manejar implícitamente ' +
'en un programa interactivo (el usuario no puede construir ' +
'instancias).',
'errmsg:forbidden-extension-destructuring-foreach': 'El índice de la repetición indexada debe ser un identificador.',
['errmsg:forbidden-extension-allow-recursion']: (cycle) => {
const msg = [];
for (const call of cycle) {
msg.push(' ' +
call.caller +
' llama a ' +
call.callee +
' (' +
call.location.startPos.filename.toString() +
':' +
call.location.startPos.line.toString() +
':' +
call.location.startPos.column.toString() +
')');
}
return ('La recursión está deshabilitada. ' +
'Hay un ciclo en las invocaciones:\n' +
msg.join('\n'));
},
'errmsg:patterns-in-foreach-must-not-be-events': 'El patrón de un foreach no puede ser un evento.',
/* Runtime errors (virtual machine) */
'errmsg:ellipsis': 'El programa todavía no está completo.',
'errmsg:undefined-variable': (variableName) => 'La variable "' + variableName + '" no está definida.',
'errmsg:too-few-arguments': (routineName) => 'Faltan argumentos para "' + routineName + '".',
'errmsg:expected-structure-but-got': (constructorName, valueTag) => 'Se esperaba una estructura construida ' +
'con el constructor "' +
constructorName +
'", ' +
'pero se recibió ' +
valueTag +
'.',
'errmsg:expected-constructor-but-got': (constructorNameExpected, constructorNameReceived) => 'Se esperaba una estructura construida ' +
'con el constructor "' +
constructorNameExpected +
'", ' +
'pero el constructor recibido es ' +
constructorNameReceived +
'".',
'errmsg:incompatible-types-on-assignment': (variableName, oldType, newType) => 'La variable "' +
variableName +
'" ' +
'contenía ' +
typeAsNoun(oldType) +
', ' +
'no se le puede asignar ' +
typeAsNoun(newType) +
'".',
'errmsg:incompatible-types-on-list-creation': (index, oldType, newType) => 'Todos los elementos de una lista deben ser del mismo tipo. ' +
'Los elementos son ' +
typeAsQualifierPlural(oldType) +
', ' +
'pero el elemento en la posición ' +
index.toString() +
' ' +
'es ' +
typeAsQualifierSingular(newType) +
'.',
'errmsg:incompatible-types-on-structure-update': (fieldName, oldType, newType) => 'El campo "' +
fieldName +
'" es ' +
typeAsQualifierSingular(oldType) +
'. ' +
'No se lo puede actualizar con ' +
typeAsNoun(newType) +
'.',
'errmsg:expected-tuple-value-but-got': (receivedType) => 'Se esperaba una tupla pero se recibió ' + typeAsNoun(receivedType) + '.',
'errmsg:tuple-component-out-of-bounds': (size, index) => 'Índice fuera de rango. ' +
'La tupla es de tamaño ' +
size.toString() +
' y ' +
'el índice es ' +
index.toString() +
'.',
'errmsg:expected-structure-value-but-got': (receivedType) => 'Se esperaba una estructura pero se recibió ' + typeAsNoun(receivedType) + '.',
'errmsg:structure-field-not-present': (fieldNames, missingFieldName) => 'La estructura no tiene un campo "' +
missingFieldName +
'". ' +
'Los campos son: [' +
fieldNames.join(', ') +
'].',
'errmsg:primitive-does-not-exist': (primitiveName) => `La operación primitiva "${primitiveName}" no existe o no está disponible.`,
'errmsg:primitive-arity-mismatch': (name, expected, received) => 'La operación "' +
name +
'" espera recibir ' +
toFunc$2(LOCALE_ES['<n>-parameters'])(expected) +
' pero se la invoca con ' +
toFunc$2(LOCALE_ES['<n>-arguments'])(received) +
'.',
'errmsg:primitive-argument-type-mismatch'(name, parameterIndex, numArgs, expectedType, receivedType) {
let msg = 'El ';
if (numArgs > 1) {
msg += ordinalNumber(parameterIndex) + ' ';
}
msg += 'parámetro ';
msg += 'de "' + name + '" ';
msg += 'debería ser ' + typeAsQualifierSingular(expectedType) + ' ';
msg += 'pero es ' + typeAsQualifierSingular(receivedType) + '.';
return msg;
},
'errmsg:expected-value-of-type-but-got': (expectedType, receivedType) => 'Se esperaba ' +
typeAsNoun(expectedType) +
' ' +
'pero se recibió ' +
typeAsNoun(receivedType) +
'.',
'errmsg:expected-value-of-some-type-but-got': (expectedTypes, receivedType) => 'Se esperaba un valor de alguno de los siguientes tipos: ' +
listOfTypes(expectedTypes) +
'. ' +
'Pero se recibió ' +
typeAsNoun(receivedType) +
'.',
'errmsg:expected-values-to-have-compatible-types': (type1, type2) => 'Los tipos de las expresiones no coinciden: ' +
'la primera es ' +
typeAsQualifierSingular(type1) +
' ' +
'y la segunda es ' +
typeAsQualifierSingular(type2) +
'.',
'errmsg:switch-does-not-match': 'El valor analizado no coincide con ninguna de las ramas del switch.',
'errmsg:foreach-pattern-does-not-match': 'El elemento no coincide con el patrón esperado por el foreach.',
'errmsg:cannot-divide-by-zero': 'No se puede dividir por cero.',
'errmsg:negative-exponent': 'El exponente de la potencia no puede ser negativo.',
'errmsg:list-cannot-be-empty': 'La lista no puede ser vacía.',
'errmsg:timeout': (millisecs) => 'La ejecución del programa demoró más de ' + millisecs.toString() + 'ms.',
/* Typecheck */
'errmsg:typecheck-failed': (errorMessage, type1, type2) => formatTypes(errorMessage, type1, type2),
/* Board operations */
'errmsg:cannot-move-to': (dirName) => 'No se puede mover hacia la dirección ' + dirName + ': cae afuera del tablero.',
'errmsg:cannot-remove-stone': (dirName) => 'No se puede sacar una bolita de color ' + dirName + ': no hay bolitas de ese color.',
/* Runtime */
'TYPE:Integer': 'Number',
'TYPE:String': 'String',
'TYPE:Tuple': '',
'TYPE:List': 'List',
'TYPE:Event': 'Event',
'CONS:INIT': 'INIT',
'CONS:TIMEOUT': 'TIMEOUT',
'TYPE:Bool': 'Bool',
'CONS:False': 'False',
'CONS:True': 'True',
'TYPE:Color': 'Color',
'CONS:Color0': 'Azul',
'CONS:Color1': 'Negro',
'CONS:Color2': 'Rojo',
'CONS:Color3': 'Verde',
'TYPE:Dir': 'Dir',
'CONS:Dir0': 'Norte',
'CONS:Dir1': 'Este',
'CONS:Dir2': 'Sur',
'CONS:Dir3': 'Oeste',
'PRIM:TypeCheck': 'TypeCheck',
'PRIM:BOOM': 'BOOM',
'PRIM:boom': 'boom',
'PRIM:PutStone': 'Poner',
'PRIM:RemoveStone': 'Sacar',
'PRIM:Move': 'Mover',
'PRIM:GoToEdge': 'IrAlBorde',
'PRIM:EmptyBoardContents': 'VaciarTablero',
'PRIM:numStones': 'nroBolitas',
'PRIM:anyStones': 'hayBolitas',
'PRIM:canMove': 'puedeMover',
'PRIM:next': 'siguiente',
'PRIM:prev': 'previo',
'PRIM:opposite': 'opuesto',
'PRIM:minBool': 'minBool',
'PRIM:maxBool': 'maxBool',
'PRIM:minColor': 'minColor',
'PRIM:maxColor': 'maxColor',
'PRIM:minDir': 'minDir',
'PRIM:maxDir': 'maxDir',
'PRIM:isEmpty': 'esVacía',
'PRIM:head': 'primero',
'PRIM:tail': 'sinElPrimero',
'PRIM:oldTail': 'resto',
'PRIM:init': 'comienzo',
'PRIM:last': 'último',
/* Helpers */
'<alternative>': (strings) => 'alguna de las siguientes alternativas:\n' +
strings.map((s) => ' ' + s).join('\n'),
'<position>': (filename, line, column) => filename + ':' + line.toString() + ':' + column.toString(),
'<n>-parameters': (n) => pluralize$1(n, 'parámetro', 'parámetros'),
'<n>-arguments': (n) => pluralize$1(n, 'argumento', 'argumentos'),
'<n>-fields': (n) => pluralize$1(n, 'campo', 'campos'),
'<pattern-type>'(patternType) {
if (patternType === 'Event') {
return 'evento del programa interactivo';
}
else if (patternType.substring(0, 7) === '_TUPLE_') {
return 'tupla de ' + patternType.substring(7) + ' componentes';
}
else {
return patternType;
}
}
};
const LOCALE_EN = {};
for (const key in LOCALE_ES) {
LOCALE_EN[key] = LOCALE_ES[key];
}
LOCALE_EN['TYPE:Color'] = 'Color';
LOCALE_EN['CONS:Color0'] = 'Blue';
LOCALE_EN['CONS:Color1'] = 'Black';
LOCALE_EN['CONS:Color2'] = 'Red';
LOCALE_EN['CONS:Color3'] = 'Green';
LOCALE_EN['TYPE:Dir'] = 'Dir';
LOCALE_EN['CONS:Dir0'] = 'North';
LOCALE_EN['CONS:Dir1'] = 'East';
LOCALE_EN['CONS:Dir2'] = 'South';
LOCALE_EN['CONS:Dir3'] = 'West';
LOCALE_EN['PRIM:PutStone'] = 'Drop';
LOCALE_EN['PRIM:RemoveStone'] = 'Grab';
LOCALE_EN['PRIM:Move'] = 'Move';
LOCALE_EN['PRIM:GoToEdge'] = 'GoToEdge';
LOCALE_EN['P