UNPKG

ladder-logic

Version:

Ladder Logic Compiler for node.js and the web

416 lines (340 loc) 12 kB
/* * Copyright (c) 2014 Christopher M. Baker * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * */ (function(exports) { function preprocess(schematic) { var rungs = []; var rung = []; var lines = schematic.toString().trim().split("\n"); for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (line.substr(0, 2) == "||" && line.substr(-2) == "||") { line = line.substr(2, line.length - 4); if (line[0] == "-") { if (rung.length > 0) { rungs.push(rung); } rung = [line]; } else { rung.push(line); } } }; if (rung.length > 0) { rungs.push(rung); } return rungs; } function scanInNot(rung, instructions, row, column, count, not) { var end = rung[row].indexOf("]", column); var name = rung[row].substr(column, end - column); instructions.push([ "in", name ]); if (not) { instructions.push([ "not" ]); } scanAnd(rung, instructions, row, end + 1, count + 1); } function scanIn(rung, instructions, row, column, count) { if (rung[row][column] == "/") { scanInNot(rung, instructions, row, column + 1, count, true); } else { scanInNot(rung, instructions, row, column, count, false); } } function scanOutNot(rung, instructions, row, column, count, not) { var end = rung[row].indexOf(")", column); var name = rung[row].substr(column, end - column); if (not) { instructions.push([ "not" ]); } instructions.push([ "out", name ]); scan(rung, instructions, row, end + 1); } function scanOut(rung, instructions, row, column, count) { if (rung[row][column] == "/") { scanOutNot(rung, instructions, row, column + 1, count, true); } else { scanOutNot(rung, instructions, row, column, count, false); } } function scanSystem(rung, instructions, row, column, count) { var end = rung[row].indexOf("}", column); var name = rung[row].substr(column, end - column); instructions.push(name.split(" ")); scanAnd(rung, instructions, row, end + 1, count + 1); } function scanOrBlock(rung, instructions, row, column, end, count) { if (rung[row][column] == "+") { var line = rung[row].substr(column + 1, end - column - 1); scan([line], instructions, 0, 0, 0); if (count > 0) { instructions.push([ "or" ]); } scanOrBlock(rung, instructions, row + 1, column, end, count + 1); } else if (rung[row][column] == "|") { scanOrBlock(rung, instructions, row + 1, column, end, count); } } function scanOr(rung, instructions, row, column, count) { var end = rung[row].indexOf("+", column + 1); scanOrBlock(rung, instructions, row, column, end, 0); scanAnd(rung, instructions, row, end + 1, count + 1); } function scanAnd(rung, instructions, row, column, count) { if (count > 1) { instructions.push([ "and" ]); } scan(rung, instructions, row, column, count); } function scan(rung, instructions, row, column, count) { while (rung[row][column] == "-") { column++; } if (column == rung[row].length) { /* end of rung */ } else if (rung[row][column] == "[") { scanIn(rung, instructions, row, column + 1, count); } else if (rung[row][column] == "(") { scanOut(rung, instructions, row, column + 1, count); } else if (rung[row][column] == "{") { scanSystem(rung, instructions, row, column + 1, count); } else if (rung[row][column] == "+") { scanOr(rung, instructions, row, column, count); } else if (rung[row][column] == " ") { /* an empty rung used as a separation */ } else { throw new Error(rung[row].substr(column)); } } function compile(schematic) { var instructions = []; var rungs = preprocess(schematic); for (var r = 0; r < rungs.length; r++) { scan(rungs[r], instructions, 0, 0, 0); } return instructions; } function NotVisitor(parent) { var pending; this.visit = function(instruction) { if (instruction[0] == "not" && pending[0] == "in") { parent.visit([ "in", "/" + pending[1] ]); pending = null; } else if (instruction[0] == "out" && pending) { if (pending[0] == "not") { parent.visit([ "out", "/" + instruction[1] ]); pending = null; } else { parent.visit(pending); parent.visit(instruction); pending = null; } } else { if (pending) { parent.visit(pending); } pending = instruction; } }; } function TreeVisitor(parent) { var stack = []; this.visit = function(instruction) { this[instruction[0]].apply(this, instruction.slice(1)); }; this.not = function() { stack.push([ "not", stack.pop() ]); }; this.or = function() { var a = stack.pop(); var b = stack.pop(); stack.push([ "or", b, a ]); }; this.and = function() { var a = stack.pop(); var b = stack.pop(); stack.push([ "and", b, a ]); }; this.in = function(name) { stack.push([ "in", name ]); }; this.out = function(name) { parent.visit([ "out", name, stack.pop() ]); }; } function Canvas() { var row; var column; var lines; this.left = function() { if (column-- == 0) throw new Error(); } this.right = function() { if (++column == lines[0].length) { for (var i = 0; i < lines.length; i++) { lines[i] += lines[i].substr(-1); } } } this.up = function() { if (row-- == 0) throw new Error(); } this.down = function() { if (++row == lines.length) { lines.push(new Array(lines[0].length + 1).join(" ")); } } this.draw = function(text) { lines[row] = lines[row].substr(0, column) + text[0] + lines[row].substr(column + 1); for (var i = 1; i < text.length; i++) { this.right(); lines[row] = lines[row].substr(0, column) + text[i] + lines[row].substr(column + 1); } } this.getMarker = function() { return [row, column]; }; this.setMarker = function(marker) { row = marker[0]; column = marker[1]; }; this.fill = function(c) { var text = new Array(lines[row].length - column + 1).join(c); lines[row] = lines[row].substr(0, column) + text; column = lines[row].length - 1; }; this.bottom = function() { row = lines.length - 1; }; this.end = function() { column = lines[row].length - 1; }; this.replaceUp = function(marker, replacements) { while (row > marker[0]) { var start = this.getMarker(); this.draw(replacements[lines[row][column]]); this.setMarker(start); this.up(); } }; this.crlf = function() { column = 0; this.down(); }; this.clear = function() { row = 0; column = 0; lines = [""]; }; this.getLines = function() { return lines.slice(-1).concat(lines.slice(0, lines.length - 1)); }; this.clear(); } function Schematic(program) { var self = this; var canvas = new Canvas(); this.visit = function(instruction) { this[instruction[0]].apply(this, instruction.slice(1)); }; function orRecursive(a, b) { if (a[0] == "or") { orRecursive(a[1], a[2]); } else { var topLeft = canvas.getMarker(); canvas.draw("+"); canvas.right(); self.visit(a); canvas.setMarker(topLeft); } canvas.down(); canvas.draw("| "); canvas.left(); canvas.down(); var bottomLeft = canvas.getMarker(); canvas.draw("+"); canvas.right(); self.visit(b); canvas.fill("-"); canvas.setMarker(bottomLeft); } this.or = function(a, b) { canvas.draw("--"); canvas.right(); var topLeft = canvas.getMarker(); orRecursive(a, b); canvas.end(); canvas.replaceUp(topLeft, { " ": "| ", "-": "+ " }); canvas.draw("+--"); canvas.right(); }; this.and = function(a, b) { this.visit(a); this.visit(b); }; this.in = function(name) { canvas.draw("--[" + name + "]--"); canvas.right(); }; this.out = function(name, value) { var marker = canvas.getMarker(); canvas.fill("-"); canvas.setMarker(marker); this.visit(value); canvas.draw("--(" + name + ")--"); canvas.bottom(); canvas.down(); canvas.crlf(); }; this.toString = function() { canvas.clear(); program.visit(new NotVisitor(new TreeVisitor(this))); return "||" + canvas.getLines().join("||\n||") + "||"; }; } function decompile(program) { program.visit = function(visitor) { for (var i = 0; i < program.length; i++) { visitor.visit(program[i]); } }; return new Schematic(program).toString(); } exports.compile = compile; exports.decompile = decompile; })((typeof exports === "undefined") ? (this.ll = { }) : module.exports);