Files
2025-02-07 12:24:13 -06:00

813 lines
25 KiB
JavaScript

this.Parser = (function() {
class Parser {
constructor(input, filename = "") {
this.input = input;
this.filename = filename;
if (/^\s*\/\/\s*javascript\s*\n/.test(this.input)) {
this.input = 'system.javascript("""\n\n' + this.input.replace(/\\/g, "\\\\") + '\n\n""")';
}
this.tokenizer = new Tokenizer(this.input, this.filename);
this.program = new Program();
this.current_block = [];
this.current = {
line: 1,
column: 1
};
this.verbose = false;
this.nesting = 0;
this.object_nesting = 0;
this.not_terminated = [];
this.api_reserved = {
screen: true,
audio: true,
keyboard: true,
gamepad: true,
sprites: true,
sounds: true,
music: true,
assets: true,
asset_manager: true,
maps: true,
touch: true,
mouse: true,
fonts: true,
Sound: true,
Image: true,
Sprite: true,
Map: true,
system: true,
storage: true,
print: true,
random: true,
Function: true,
List: true,
Object: true,
String: true,
Number: true
};
}
nextToken() {
var token;
token = this.tokenizer.next();
if (token == null) {
this.unexpected_eof = true;
throw "Unexpected end of file";
}
return this.current = token;
}
nextTokenOptional() {
var token;
token = this.tokenizer.next();
if (token != null) {
this.current = token;
}
return token;
}
parse() {
var err, expression, nt, token;
try {
this.warnings = [];
while (true) {
expression = this.parseLine();
if ((expression == null) && !this.tokenizer.finished()) {
token = this.tokenizer.next();
if ((token != null) && token.reserved_keyword) {
if (token.value === "end") {
this.error("Too many 'end'");
} else {
this.error(`Misuse of reserved keyword: '${token.value}'`);
}
} else {
this.error("Unexpected data");
}
}
if (expression === null) {
break;
}
this.current_block.push(expression);
this.program.add(expression);
if (this.verbose) {
console.info(expression);
}
}
return this;
} catch (error1) {
err = error1;
//console.info "Error at line: #{@current.line} column: #{@current.column}"
if (this.not_terminated.length > 0 && err === "Unexpected end of file") {
nt = this.not_terminated[this.not_terminated.length - 1];
return this.error_info = {
error: `Unterminated '${nt.value}' ; no matching 'end' found`,
line: nt.line,
column: nt.column
};
} else {
return this.error_info = {
error: err,
line: this.current.line,
column: this.current.column
};
}
}
}
//console.error err
parseLine() {
var token;
token = this.nextTokenOptional();
if (token == null) {
return null;
}
switch (token.type) {
case Token.TYPE_RETURN:
return new Program.Return(token, this.parseExpression());
case Token.TYPE_BREAK:
return new Program.Break(token);
case Token.TYPE_CONTINUE:
return new Program.Continue(token);
case Token.TYPE_LOCAL:
return this.parseLocalAssignment(token);
default:
this.tokenizer.pushBack(token);
return this.parseExpression();
}
}
parseExpression(filter, first_function_call = false) {
var access, expression;
expression = this.parseExpressionStart();
if (expression == null) {
return null;
}
while (true) {
access = this.parseExpressionSuffix(expression, filter);
if (access == null) {
return expression;
}
if (first_function_call && access instanceof Program.FunctionCall) {
return access;
}
expression = access;
}
}
assertExpression(filter, first_function_call = false) {
var exp;
exp = this.parseExpression(filter, first_function_call);
if (exp == null) {
throw "Expression expected";
}
return exp;
}
parseExpressionSuffix(expression, filter) {
var field, identifier, token;
token = this.nextTokenOptional();
if (token == null) {
return (filter === "self" ? expression : null);
}
switch (token.type) {
case Token.TYPE_DOT:
if (expression instanceof Program.Value && expression.type === Program.Value.TYPE_NUMBER) {
this.tokenizer.pushBack(token);
return null;
} else {
this.tokenizer.changeNumberToIdentifier();
identifier = this.assertBroadIdentifier("Expected identifier");
return Program.CreateFieldAccess(token, expression, new Program.Value(identifier, Program.Value.TYPE_STRING, identifier.value));
}
break;
case Token.TYPE_OPEN_BRACKET:
field = this.assertExpression();
this.assert(Token.TYPE_CLOSED_BRACKET, "Expected ']'");
return Program.CreateFieldAccess(token, expression, field);
case Token.TYPE_OPEN_BRACE:
return this.parseFunctionCall(token, expression);
case Token.TYPE_EQUALS:
return this.parseAssignment(token, expression);
case Token.TYPE_PLUS_EQUALS:
return this.parseSelfAssignment(token, expression, token.type);
case Token.TYPE_MINUS_EQUALS:
return this.parseSelfAssignment(token, expression, token.type);
case Token.TYPE_MULTIPLY_EQUALS:
return this.parseSelfAssignment(token, expression, token.type);
case Token.TYPE_DIVIDE_EQUALS:
return this.parseSelfAssignment(token, expression, token.type);
case Token.TYPE_MODULO_EQUALS:
case Token.TYPE_AND_EQUALS:
case Token.TYPE_OR_EQUALS:
return this.parseSelfAssignment(token, expression, token.type);
default:
if (filter === "self") {
this.tokenizer.pushBack(token);
return expression;
} else if (token.is_binary_operator && filter !== "noop") {
return this.parseBinaryOperation(token, expression);
} else {
this.tokenizer.pushBack(token);
return null;
}
}
}
parseExpressionStart() {
var next, token;
token = this.nextTokenOptional();
if (token == null) {
return null;
}
switch (token.type) {
case Token.TYPE_IDENTIFIER: // variable name
return new Program.Variable(token, token.value);
case Token.TYPE_NUMBER:
return this.parseNumberExpression(token);
case Token.TYPE_PLUS:
return this.assertExpression();
case Token.TYPE_MINUS:
return this.parseExpressionSuffix(new Program.Negate(token, this.assertExpression("noop")), "self");
case Token.TYPE_NOT:
return this.parseExpressionSuffix(new Program.Not(token, this.assertExpression("noop")), "self");
case Token.TYPE_STRING:
return this.parseStringExpression(token);
case Token.TYPE_IF:
return this.parseIf(token);
case Token.TYPE_FOR:
return this.parseFor(token);
case Token.TYPE_WHILE:
return this.parseWhile(token);
case Token.TYPE_OPEN_BRACE:
return this.parseBracedExpression(token);
case Token.TYPE_OPEN_BRACKET:
return this.parseArray(token);
case Token.TYPE_FUNCTION:
return this.parseFunction(token);
case Token.TYPE_OBJECT:
return this.parseObject(token);
case Token.TYPE_CLASS:
return this.parseClass(token);
case Token.TYPE_NEW:
return this.parseNew(token);
case Token.TYPE_DOT:
next = this.assert(Token.TYPE_NUMBER, "malformed number");
if (!Number.isInteger(next.value)) {
throw "malformed number";
}
return new Program.Value(token, Program.Value.TYPE_NUMBER, Number.parseFloat(`.${next.string_value}`));
case Token.TYPE_AFTER:
return this.parseAfter(token);
case Token.TYPE_EVERY:
return this.parseEvery(token);
case Token.TYPE_DO:
return this.parseDo(token);
case Token.TYPE_SLEEP:
return this.parseSleep(token);
case Token.TYPE_DELETE:
return this.parseDelete(token);
default:
this.tokenizer.pushBack(token);
return null;
}
}
parseNumberExpression(number) {
return new Program.Value(number, Program.Value.TYPE_NUMBER, number.value);
}
parseStringExpression(string) {
var token;
token = this.nextTokenOptional();
if (token == null) {
return new Program.Value(string, Program.Value.TYPE_STRING, string.value);
} else {
this.tokenizer.pushBack(token);
return new Program.Value(string, Program.Value.TYPE_STRING, string.value);
}
}
parseArray(bracket) {
var res, token;
res = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_CLOSED_BRACKET) {
return new Program.Value(bracket, Program.Value.TYPE_ARRAY, res);
} else if (token.type === Token.TYPE_COMMA) {
continue;
} else {
this.tokenizer.pushBack(token);
res.push(this.assertExpression());
}
}
}
parseBinaryOperation(operation, term1) {
var ops, terms, token;
ops = [new Program.Operation(operation, operation.value)];
terms = [term1];
terms.push(this.assertExpression("noop"));
while (true) {
token = this.nextTokenOptional();
if (token == null) {
break;
}
if (!token.is_binary_operator) {
this.tokenizer.pushBack(token);
break;
}
ops.push(new Program.Operation(token, token.value));
terms.push(this.assertExpression("noop"));
}
return Program.BuildOperations(ops, terms);
}
parseAssignment(token, expression) {
var res;
if (!(expression instanceof Program.Variable) && !(expression instanceof Program.Field)) {
throw "Expected variable identifier or property";
}
if (this.object_nesting === 0 && expression instanceof Program.Variable && this.api_reserved[expression.identifier]) {
this.warnings.push({
type: "assigning_api_variable",
identifier: expression.identifier,
line: token.line,
column: token.column
});
}
if (expression instanceof Program.Field) {
this.object_nesting += 1;
res = new Program.Assignment(token, expression, this.assertExpression());
this.object_nesting -= 1;
} else {
res = new Program.Assignment(token, expression, this.assertExpression());
}
return res;
}
parseSelfAssignment(token, expression, operation) {
if (!(expression instanceof Program.Variable) && !(expression instanceof Program.Field)) {
throw "Expected variable identifier or property";
}
return new Program.SelfAssignment(token, expression, operation, this.assertExpression());
}
parseLocalAssignment(local) {
var identifier;
identifier = this.assert(Token.TYPE_IDENTIFIER, "Expected identifier");
this.assert(Token.TYPE_EQUALS, "Expected '='");
return new Program.Assignment(local, new Program.Variable(identifier, identifier.value), this.assertExpression(), true);
}
parseBracedExpression(open) {
var expression, token;
expression = this.assertExpression();
token = this.nextToken();
if (token.type === Token.TYPE_CLOSED_BRACE) {
return new Program.Braced(open, expression);
} else {
return this.error("missing closing parenthese");
}
}
parseFunctionCall(brace_token, expression) {
var args, start, token;
args = [];
this.last_function_call = new Program.FunctionCall(brace_token, expression, args);
this.last_function_call.argslimits = [];
while (true) {
token = this.nextTokenOptional();
if (token == null) {
return this.error("missing closing parenthese");
} else if (token.type === Token.TYPE_CLOSED_BRACE) {
return new Program.FunctionCall(token, expression, args);
} else if (token.type === Token.TYPE_COMMA) {
continue;
} else {
this.tokenizer.pushBack(token);
start = token.start;
args.push(this.assertExpression());
this.last_function_call.argslimits.push({
start: start,
end: this.tokenizer.index - 1
});
}
}
}
addTerminable(token) {
return this.not_terminated.push(token);
}
endTerminable() {
if (this.not_terminated.length > 0) {
this.not_terminated.splice(this.not_terminated.length - 1, 1);
}
}
parseFunction(funk) {
var args, line, sequence, token;
this.nesting += 1;
this.addTerminable(funk);
args = this.parseFunctionArgs();
sequence = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.endTerminable();
return new Program.Function(funk, args, sequence, token);
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line != null) {
sequence.push(line);
} else {
this.error("Unexpected data while parsing function");
}
}
}
}
parseFunctionArgs() {
var args, exp, last, token;
token = this.nextToken();
args = [];
last = null;
if (token.type !== Token.TYPE_OPEN_BRACE) {
return this.error("Expected opening parenthese");
}
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_CLOSED_BRACE) {
return args;
} else if (token.type === Token.TYPE_COMMA) {
last = null;
continue;
} else if (token.type === Token.TYPE_EQUALS && last === "argument") {
exp = this.assertExpression();
args[args.length - 1].default = exp;
} else if (token.type === Token.TYPE_IDENTIFIER) {
last = "argument";
args.push({
name: token.value
});
} else {
return this.error("Unexpected token");
}
}
}
warningAssignmentCondition(expression) {
if (expression instanceof Program.Assignment) {
return this.warnings.push({
type: "assignment_as_condition",
line: expression.token.line,
column: expression.token.column
});
}
}
parseIf(iftoken) {
var chain, current, line, token;
this.addTerminable(iftoken);
current = {
condition: this.assertExpression(),
sequence: []
};
this.warningAssignmentCondition(current.condition);
chain = [];
token = this.nextToken();
if (token.type !== Token.TYPE_THEN) {
return this.error("Expected 'then'");
}
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_ELSIF) {
chain.push(current);
current = {
condition: this.assertExpression(),
sequence: []
};
this.warningAssignmentCondition(current.condition);
this.assert(Token.TYPE_THEN, "Expected 'then'");
} else if (token.type === Token.TYPE_ELSE) {
current.else = [];
} else if (token.type === Token.TYPE_END) {
chain.push(current);
this.endTerminable();
return new Program.Condition(iftoken, chain);
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line == null) {
throw Error("Unexpected data while parsing if");
}
if (current.else != null) {
current.else.push(line);
} else {
current.sequence.push(line);
}
}
}
}
assert(type, error) {
var token;
token = this.nextToken();
if (token.type !== type) {
throw error;
}
return token;
}
assertBroadIdentifier(error) {
var token;
token = this.nextToken();
if (token.type !== Token.TYPE_IDENTIFIER && token.reserved_keyword) {
token.type = Token.TYPE_IDENTIFIER;
}
if (token.type !== Token.TYPE_IDENTIFIER) {
throw error;
}
return token;
}
error(text) {
throw text;
}
parseFor(fortoken) {
var iterator, list, range_by, range_from, range_to, token;
iterator = this.assertExpression();
if (iterator instanceof Program.Assignment) {
range_from = iterator.expression;
iterator = iterator.field;
token = this.nextToken();
if (token.type !== Token.TYPE_TO) {
return this.error("Expected 'to'");
}
range_to = this.assertExpression();
token = this.nextToken();
if (token.type === Token.TYPE_BY) {
range_by = this.assertExpression();
} else {
range_by = 0;
this.tokenizer.pushBack(token);
}
return new Program.For(fortoken, iterator.identifier, range_from, range_to, range_by, this.parseSequence(fortoken));
} else if (iterator instanceof Program.Variable) {
this.assert(Token.TYPE_IN, "Error expected keyword 'in'");
list = this.assertExpression();
return new Program.ForIn(fortoken, iterator.identifier, list, this.parseSequence(fortoken));
} else {
return this.error("Malformed for loop");
}
}
parseWhile(whiletoken) {
var condition;
condition = this.assertExpression();
return new Program.While(whiletoken, condition, this.parseSequence(whiletoken));
}
parseSequence(start_token) {
var line, sequence, token;
if (start_token != null) {
this.addTerminable(start_token);
}
this.nesting += 1;
sequence = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
if (start_token != null) {
this.endTerminable();
}
this.nesting -= 1;
return sequence;
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line == null) {
this.error("Unexpected data");
}
sequence.push(line);
}
}
return sequence;
}
parseObject(object) {
var exp, fields, token;
this.nesting += 1;
this.object_nesting += 1;
this.addTerminable(object);
fields = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.object_nesting -= 1;
this.endTerminable();
return new Program.CreateObject(object, fields);
} else {
if (token.type !== Token.TYPE_IDENTIFIER && token.reserved_keyword) {
token.type = Token.TYPE_IDENTIFIER;
}
if (token.type === Token.TYPE_STRING) {
token.type = Token.TYPE_IDENTIFIER;
}
if (token.type === Token.TYPE_IDENTIFIER) {
this.assert(Token.TYPE_EQUALS, "Expected '='");
exp = this.assertExpression();
fields.push({
field: token.value,
value: exp
});
} else {
return this.error("Malformed object");
}
}
}
}
parseClass(object) {
var exp, ext, fields, token;
this.nesting += 1;
this.object_nesting += 1;
this.addTerminable(object);
fields = [];
token = this.nextToken();
if (token.type === Token.TYPE_EXTENDS) {
ext = this.assertExpression();
token = this.nextToken();
}
while (true) {
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.object_nesting -= 1;
this.endTerminable();
return new Program.CreateClass(object, ext, fields);
} else {
if (token.type !== Token.TYPE_IDENTIFIER && token.reserved_keyword) {
token.type = Token.TYPE_IDENTIFIER;
}
if (token.type === Token.TYPE_STRING) {
token.type = Token.TYPE_IDENTIFIER;
}
if (token.type === Token.TYPE_IDENTIFIER) {
this.assert(Token.TYPE_EQUALS, "Expected '='");
exp = this.assertExpression();
fields.push({
field: token.value,
value: exp
});
} else {
return this.error("Malformed object");
}
}
token = this.nextToken();
}
}
parseNew(token) {
var exp;
exp = this.assertExpression(null, true);
return new Program.NewCall(token, exp);
}
parseAfter(after) {
var delay, line, multiplier, sequence, token;
this.nesting += 1;
this.addTerminable(after);
delay = this.assertExpression();
token = this.nextToken();
multiplier = null;
if (token.type === Token.TYPE_IDENTIFIER && this.multipliers[token.value]) {
multiplier = this.multipliers[token.value];
token = this.nextToken();
}
if ((token == null) || token.type !== Token.TYPE_DO) {
this.error("Expected keyword 'do'");
}
sequence = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.endTerminable();
return new Program.After(after, delay, sequence, token, multiplier);
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line != null) {
sequence.push(line);
} else {
this.error("Unexpected data while parsing after");
}
}
}
}
parseEvery(every) {
var delay, line, multiplier, sequence, token;
this.nesting += 1;
this.addTerminable(every);
delay = this.assertExpression();
token = this.nextToken();
multiplier = null;
if (token.type === Token.TYPE_IDENTIFIER && this.multipliers[token.value]) {
multiplier = this.multipliers[token.value];
token = this.nextToken();
}
if ((token == null) || token.type !== Token.TYPE_DO) {
this.error("Expected keyword 'do'");
}
sequence = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.endTerminable();
return new Program.Every(every, delay, sequence, token, multiplier);
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line != null) {
sequence.push(line);
} else {
this.error("Unexpected data while parsing after");
}
}
}
}
parseDo(do_token) {
var line, sequence, token;
this.nesting += 1;
this.addTerminable(do_token);
sequence = [];
while (true) {
token = this.nextToken();
if (token.type === Token.TYPE_END) {
this.nesting -= 1;
this.endTerminable();
return new Program.Do(do_token, sequence, token);
} else {
this.tokenizer.pushBack(token);
line = this.parseLine();
if (line != null) {
sequence.push(line);
} else {
this.error("Unexpected data while parsing after");
}
}
}
}
parseSleep(sleep) {
var delay, multiplier, token;
delay = this.assertExpression();
token = this.nextToken();
multiplier = null;
if (token != null) {
if (token.type === Token.TYPE_IDENTIFIER && this.multipliers[token.value]) {
multiplier = this.multipliers[token.value];
} else {
this.tokenizer.pushBack(token);
}
}
return new Program.Sleep(sleep, delay, multiplier);
}
parseDelete(del) {
var v;
v = this.parseExpression();
if ((v == null) || (!(v instanceof Program.Variable) && !(v instanceof Program.Field))) {
return this.error("expecting variable name or property access after keyword `delete`");
} else {
return new Program.Delete(del, v);
}
}
};
Parser.prototype.multipliers = {
millisecond: 1,
milliseconds: 1,
second: 1000,
seconds: 1000,
minute: 60000,
minutes: 60000,
hour: 60000 * 60,
hours: 60000 * 60,
day: 60000 * 60 * 24,
days: 60000 * 60 * 24
};
return Parser;
}).call(this);