redc
Version:
Compiles RED lang into Minecraft schematics
404 lines (403 loc) • 14.5 kB
JavaScript
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import chalk from 'chalk';
import { MEMORY_SIZE, REDCError } from './meta.js';
function evaluate(value) {
var asInt = parseInt(value);
if (isNaN(asInt)) {
return value;
}
else
return asInt;
}
function ramAddressOnly(value, context) {
var d = variableExists(value, context);
if (d)
return d;
if (context.parseData.variables[value].source !== 'ram') {
return "Cannot allocate to \"".concat(value, "\": Only ram allocated variables are allowed for this operation.");
}
}
var variableExists = function (value, context) {
if (isNaN(parseInt(value)) && !(value in context.parseData.variables)) {
return "Variable \"".concat(value, "\" is not allocated");
}
};
var commonTests = [
{
basic: /^#/,
name: 'comment',
syntax: '# <anything>',
test: /^#/,
vars: {}
},
{
basic: /^@/,
name: 'tag',
syntax: '@<tag>',
test: /^@(?<tag>[a-z]+)$/,
vars: {
tag: {
onMissing: function () { return 'A tag name must be in all lowercase'; },
check: function (value) {
if (value !== 'entry')
return "Unknown tag ".concat(value);
}
}
},
onMatch: function (context, _a) {
var tag = _a.tag;
context.tags.push(tag);
}
},
{
basic: /^\[[\w\W]*\]$/,
name: 'routine',
syntax: '[<routine name>]',
test: /^\[(?<name>[A-Z][A-Za-z._]*)\]$/,
vars: {
name: {
onMissing: function () { return 'A routine name must start with an uppercase letter'; },
check: function (value, context) {
if (context.tags.indexOf('entry') !== -1 && context.parseData.entry) {
return 'Multiple routines cannot be tagged as @entry.';
}
}
}
},
onMatch: function (context, _a) {
var name = _a.name;
context.scope = 'routine';
context.currentRoutine = name;
context.parseData.routines[name] = [];
for (var _i = 0, _b = context.tags; _i < _b.length; _i++) {
var tag = _b[_i];
if (tag === 'entry') {
context.parseData.entry = name;
}
}
context.tags = [];
}
},
{
basic: /^$/,
name: 'empty line',
syntax: '',
test: /^$/,
vars: {}
}
];
var mainScopeTests = __spreadArray(__spreadArray([], commonTests, true), [
{
basic: /^use /,
name: 'memory allocation',
syntax: 'use <variable name> = <ram|device|input>:<address>',
test: /^use (?<name>[a-z][a-zA-Z._]*) = (?<source>[a-z]+):(?<address>\d+)$/,
vars: {
name: {
onMissing: function () { return 'A variable name must be provided when allocating memory'; }
},
source: {
check: function (value) {
if (value !== 'ram' && value !== 'input' && value !== 'device') {
return 'Memory source must be one of "ram", "input" or "device"';
}
},
onMissing: function () { return 'A memory source must be provided when allocating memory'; }
},
address: {
onMissing: function () { return 'A memory address must be provided when allocating memory'; },
check: function (str) {
var asInt = parseInt(str);
if (isNaN(asInt)) {
return 'A memory address must be a positive integer';
}
if (asInt >= MEMORY_SIZE) {
return "Cannot allocate memory in an address greater than ".concat(MEMORY_SIZE - 1);
}
}
}
},
onMatch: function (context, _a) {
var name = _a.name, source = _a.source, address = _a.address;
context.parseData.variables[name] = {
source: source,
address: parseInt(address)
};
}
}
], false);
var binaryOperators = [
{ text: 'and' },
{ text: 'or' },
{ text: 'xor' },
{ text: 'plus' }
];
var unaryOperators = [
{ text: 'not' },
{ text: 'shift' }
];
var routineTests = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], commonTests, true), [
{
basic: null,
name: 'conditional jump',
syntax: 'goto <routine> [if <variable>]',
test: /^goto (?<routine>[A-Z][a-zA-Z._]*) if (?<name>[a-z][a-zA-Z._]*)$/,
vars: {
routine: {
onMissing: function () { return 'A jump must provide a routine. The name of a routine must start with a capital letter'; }
},
name: {
onMissing: function () { return 'A conditional jump must provide a variable'; },
check: variableExists
}
},
onMatch: function (_a, _b) {
var parseData = _a.parseData, currentRoutine = _a.currentRoutine, line = _a.line;
var routine = _b.routine, name = _b.name;
parseData.routines[currentRoutine].push({
type: 'jump',
to: routine,
"if": name,
line: line
});
}
},
{
basic: /^goto/,
name: 'jump',
syntax: 'goto <routine> [if <variable>]',
test: /^goto (?<routine>[A-Z][a-zA-Z._]*)$/,
vars: {
routine: {
onMissing: function () { return 'A jump must provide a routine. The name of a routine must start with a capital letter'; }
}
},
onMatch: function (_a, _b) {
var parseData = _a.parseData, currentRoutine = _a.currentRoutine, line = _a.line;
var routine = _b.routine;
parseData.routines[currentRoutine].push({
type: 'jump',
to: routine,
line: line
});
}
},
{
basic: null,
name: 'exit',
syntax: 'exit',
test: /^exit$/,
vars: {},
onMatch: function (_a) {
var parseData = _a.parseData, currentRoutine = _a.currentRoutine, line = _a.line;
parseData.routines[currentRoutine].push({
type: 'exit',
line: line
});
}
},
{
basic: null,
name: 'assignment',
syntax: '<variable> = <variable | number>',
test: /^(?<name>[a-z][a-zA-Z._]*) = (?<operand>[a-z][a-zA-Z._]*|\d+)$/,
vars: {
name: {
onMissing: function () { return 'Assignment operation is missing variable'; },
check: variableExists
},
operand: {
onMissing: function () { return 'Assignment operation is missing assigned value'; },
check: variableExists
}
},
onMatch: function (_a, _b) {
var parseData = _a.parseData, currentRoutine = _a.currentRoutine, line = _a.line;
var name = _b.name, operand = _b.operand;
parseData.routines[currentRoutine].push({
type: 'assignment',
assignedTo: name,
value: evaluate(operand),
line: line
});
}
},
{
basic: /^[a-z]+:[a-z][a-zA-Z._]*/,
name: 'pointer assignment',
syntax: '<ram|device>:<ram variable> = <variable | number>',
test: /^(?<source>[a-z]+):(?<addresser>[a-z][a-zA-Z._]*) = (?<value>[a-z][a-zA-Z._]*|\d+)$/,
vars: {
source: {
onMissing: function () { return 'Pointer assignment missing source'; },
check: function (v) {
if (v !== 'ram' && v !== 'device' && v !== 'input') {
return 'Pointer assignment source must be "ram", "device" or "input"';
}
}
},
addresser: {
onMissing: function () { return 'Pointer assignment missing addresser'; },
check: ramAddressOnly
},
value: {
onMissing: function () { return 'Pointer assignment missing assignment value'; }
}
},
onMatch: function (_a, _b) {
var parseData = _a.parseData, currentRoutine = _a.currentRoutine, line = _a.line;
var source = _b.source, addresser = _b.addresser, value = _b.value;
parseData.routines[currentRoutine].push({
type: 'pointer',
source: source,
at: addresser,
to: value,
line: line
});
}
},
{
basic: /^[a-z][a-zA-Z._]* = [a-z]+:[a-zA-Z_.]*$/,
name: 'copying from pointer',
syntax: '<variable> = <ram|input>:<ram variable>',
test: /^(?<variable>[a-z][a-zA-Z._]*) = (?<source>[a-z]+):(?<addresser>[a-z][a-zA-Z._]*)$/,
vars: {}
}
], false), binaryOperators.map(function (operator) {
var _a;
var name = (_a = operator['name']) !== null && _a !== void 0 ? _a : operator.text;
return {
basic: null,
name: name,
syntax: "<variable> = <variable | number> ".concat(operator.text, " <variable | number>"),
test: new RegExp("^(?<variable>[a-z][a-zA-Z._]*) = (?<operand1>[a-z][a-zA-Z._]*|\\d+) ".concat(operator.text, " (?<operand2>[a-z][a-zA-Z._]*|\\d+)$")),
vars: {
variable: {
onMissing: function () { return ''; },
check: ramAddressOnly
},
operand1: {
onMissing: function () { return ''; },
check: variableExists
},
operand2: {
onMissing: function () { return ''; },
check: variableExists
}
},
onMatch: function (_a, _b) {
var currentRoutine = _a.currentRoutine, parseData = _a.parseData, line = _a.line;
var variable = _b.variable, operand1 = _b.operand1, operand2 = _b.operand2;
parseData.routines[currentRoutine].push({
type: name,
assignedTo: variable,
operand1: evaluate(operand1),
operand2: evaluate(operand2),
line: line
});
}
};
}), true), unaryOperators.map(function (operator) {
var _a;
var name = (_a = operator['name']) !== null && _a !== void 0 ? _a : operator.text;
return {
basic: null,
name: name,
syntax: "<variable> = ".concat(operator.text, " <variable | number>"),
test: new RegExp("^(?<variable>[a-z][a-zA-Z._]*) = ".concat(operator.text, " (?<operand>[a-z][a-zA-Z._]*|\\d+)$")),
vars: {
variable: {
onMissing: function () { return ''; },
check: ramAddressOnly
},
operand: {
onMissing: function () { return ''; },
check: variableExists
}
},
onMatch: function (_a, _b) {
var currentRoutine = _a.currentRoutine, parseData = _a.parseData, line = _a.line;
var variable = _b.variable, operand = _b.operand;
parseData.routines[currentRoutine].push({
type: name,
assignedTo: variable,
operand: evaluate(operand),
line: line
});
}
};
}), true);
export function parseLine(line, context) {
var tests = [];
tests = context.scope === 'main' ? mainScopeTests : routineTests;
var found = false;
for (var _i = 0, tests_1 = tests; _i < tests_1.length; _i++) {
var _a = tests_1[_i], basic = _a.basic, syntax = _a.syntax, name_1 = _a.name, vars = _a.vars, test = _a.test, onMatch = _a.onMatch;
if (basic && !basic.test(line))
continue;
var match = line.match(test);
if (match) {
found = true;
var values = {};
for (var key in vars) {
var _b = vars[key], check = _b.check, onMissing = _b.onMissing;
if (key in match.groups) {
var value = match.groups[key];
if (check) {
var errorMsg = check(value, context);
if (errorMsg)
return errorMsg;
values[key] = value;
}
else {
values[key] = value;
}
}
else {
return onMissing();
}
}
onMatch === null || onMatch === void 0 ? void 0 : onMatch(context, values);
return;
}
else if (basic) {
return "Invalid syntax for ".concat(name_1, ". Syntax: ").concat(chalk.green(syntax));
}
}
if (!found) {
return "Unknown syntax \"".concat(line, "\"");
}
}
export function parse(lines) {
var context = {
scope: 'main',
currentRoutine: null,
parseData: {
entry: null,
routines: {},
variables: {}
},
tags: [],
line: 0
};
for (var i = 0; i < lines.length; i++) {
context.line = i;
var errorMsg = parseLine(lines[i].trim(), context);
if (errorMsg) {
var message = "Parsing error encountered at line ".concat(i + 1, "\nError: ").concat(errorMsg, "\nOriginal line: \"").concat(lines[i], "\"");
throw new REDCError(message);
}
}
if (!context.parseData.entry) {
throw new REDCError('No entry routine was provided. Tag a routine with @entry to create one.');
}
return context.parseData;
}