UNPKG

redc

Version:

Compiles RED lang into Minecraft schematics

404 lines (403 loc) 14.5 kB
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; }