diff --git a/ast.json b/ast.json index 4d319a8..ec9f41a 100644 --- a/ast.json +++ b/ast.json @@ -11,44 +11,39 @@ }, { "type": "Group", - "value": "{", - "tokens": [ - { - "type": "Left Delimiter", - "value": "{", - "layer": 0 - } - ] - }, - { - "type": "Group", - "value": "a", + "value": "(", "tokens": [ { "type": "Variable", "value": "a", - "layer": 2 - }, - { - "type": "Operator", - "value": "++", - "layer": 2 + "layer": 1 } ] }, { "type": "Group", - "value": "asdf", + "value": "{", "tokens": [ { - "type": "Variable", - "value": "asdf", - "layer": 0 + "type": "Function Call", + "value": "return", + "layer": 1 }, { - "type": "Operator", - "value": "=", - "layer": 0 + "type": "Group", + "value": "(", + "tokens": [ + { + "type": "Variable", + "value": "a", + "layer": 2 + }, + { + "type": "Operator", + "value": "++", + "layer": 2 + } + ] } ] } diff --git a/classes.js b/classes.js index e5df2f0..4d59265 100644 --- a/classes.js +++ b/classes.js @@ -1,20 +1,21 @@ // This is the most basic type of token. var token = function (type, value) { - this.type = type; - this.value = value; + this.type = type; + this.value = value; }; // This is a group of tokens. -var group = function (type, tokens) { - this.type = "Group"; - this.value = type; - this.tokens; - if(typeof tokens != "undefined") { - this.tokens = tokens; - } else { - this.tokens = []; - } +var group = function (type, tokens, index) { + this.type = "Group"; + this.value = type; + this.index = index; + this.tokens; + if (typeof tokens != "undefined") { + this.tokens = tokens; + } else { + this.tokens = []; + } } module.exports = { - token: token, - group: group -} + token: token, + group: group +} \ No newline at end of file diff --git a/parser.js b/parser.js index e163023..50cba2f 100644 --- a/parser.js +++ b/parser.js @@ -5,55 +5,130 @@ const group = require("./classes.js").group; // parse() takes an array of tokens in, and outputs an // Abstract Syntax Tree (a structured array of tokens). module.exports = tokens => { - // Variables for later. - var layer = 0; - var delimiterCount = 0; - var deepestLayer = 0; - // Give each token a layer number based on delimiters. - for (var i = 0; i < tokens.length; i++) { - if (tokens[i].type == "Left Delimiter") { - layer++; - if(layer > deepestLayer) { - deepestLayer = layer; - } - delimiterCount++; - } - tokens[i].layer = layer; - if (tokens[i].type == "Right Delimiter") { - layer--; - } + // Variables for later. + var layer = 0; + var delimiterCount = 0; + var deepestLayer = 0; + // Give each token a layer number based on delimiters. + for (var i = 0; i < tokens.length; i++) { + if (tokens[i].type == "Left Delimiter") { + layer++; + if (layer > deepestLayer) { + deepestLayer = layer; + } + delimiterCount++; } - // Lower the layer of delimiters. - for (var i = 0; i < tokens.length; i++) { - if ((tokens[i].type == "Left Delimiter") || (tokens[i].type == "Right Delimiter")) { - tokens[i].layer--; - } - } - if (layer > 0) { // Unclosed delimiter. - } else if (layer < 0) { // Overclosed delimiter. - } - // Reset layer for structuring. - layer = 0; - /* - - - DO NOT TOUCH THIS - - - */ - for(var i=deepestLayer;i>=0;i--) { - var temp = []; - var firstIndex; - for(var j=0;j 0) { - var g = new group(tokens[firstIndex].value,temp); - tokens.splice(firstIndex-1,temp.length+2,g); - temp = []; - } - } + tokens[i].layer = layer; + if (tokens[i].type == "Right Delimiter") { + layer--; + } + } + // Lower the layer of delimiters. + for (var i = 0; i < tokens.length; i++) { + if ((tokens[i].type == "Left Delimiter") || (tokens[i].type == "Right Delimiter")) { + tokens[i].layer--; + } + } + if (layer > 0) { // Unclosed delimiter. + } else if (layer < 0) { // Overclosed delimiter. + } + // Give each token an index. + for (let i = 0; i < tokens.length; i++) { + tokens[i].index = i; + } + // Structure the layers. + // Count the rising edges of the layers to determine how many groups should exist. + let structure = function () { + let layer = 0; + let risingFalling = []; // Create an array to store indices of rising/falling edges. + for (let i = 0; i < tokens.length; i++) { + // Add a rising and a falling tag to each token. + tokens[i].rising = false; + tokens[i].falling = false; + if (tokens[i].layer > layer) { // If the token moves up a layer. + // Create a new rising index in risingFalling. + risingFalling.push({ + type: 'rising', + index: i + }); + tokens[i].rising = true; // Note that the token is a rising edge. + layer++; + } else if (tokens[i].layer < layer) { + // Create a new falling index in risingFalling. + risingFalling.push({ + type: 'falling', + index: i + }); + tokens[i].falling = true; // Note that the token is a falling edge. + layer--; + } + } + // Loop through the list of rising/falling edges. + for (let i = 0; i < risingFalling.length; i++) { + if (i != risingFalling.length - 1) { // If not the last edge. + let item = risingFalling[i]; + let nextItem = risingFalling[i + 1]; + // If a falling edge follows a rising edge, classifiy it as a group. + if ((item.type == 'rising') && (nextItem.type == 'falling')) { + // Get the group together as one item. + let selectedItems = tokens.slice(item.index, nextItem.index); + tokens.splice(item.index, selectedItems.length, new group(tokens[item.index - 1].value, selectedItems, item.index)); } + } } - /* - - - OK YOU CAN TOUCH AGAIN - - - */ - // Return the structured tokens. - return tokens; -}; + risingFalling = []; // Reset the list of edges. + // Count the edges again. + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].layer > layer) { + risingFalling.push({ + type: 'rising', + index: i + }); + layer++; + } else if (tokens[i].layer < layer) { + risingFalling.push({ + type: 'falling', + index: i + }); + layer--; + } + } + // If there are still edges, run again. + if (risingFalling.length) { + structure(); + } + }; + // Start the recursion. + structure(); + let trimDelimiters = function (thing) { + // Loop through the tokens of thing. + for (let i = 0; i < thing.length; i++) { + // Delete unnecessary keys. + if (typeof thing[i].rising != 'undefined') { + delete thing[i].rising; + } + if (typeof thing[i].falling != 'undefined') { + delete thing[i].falling; + } + if (typeof thing[i].index != 'undefined') { + delete thing[i].index; + } + if (typeof thing[i].index != 'undefined') { + delete thing[i].index; + } + // Remove delimiters. + if ((thing[i].type == 'Left Delimiter') || (thing[i].type == 'Right Delimiter')) { + thing.splice(i, 1); + i--; + } + // If a token is a group, look at the group's tokens. + if (thing[i].type == 'Group') { + trimDelimiters(thing[i].tokens); + } + } + }; + // Start the recursion. + trimDelimiters(tokens); + // Return the structured tokens. + return tokens; +}; \ No newline at end of file diff --git a/pivot.js b/pivot.js index e9d92cc..2b75014 100644 --- a/pivot.js +++ b/pivot.js @@ -10,4 +10,4 @@ var ast = parse(tokenize(code)); // Write the AST to ast.json. var fs = require("fs"); -fs.writeFileSync("ast.json", JSON.stringify(ast, null, 4)); +fs.writeFileSync("ast.json", JSON.stringify(ast, null, 4)); \ No newline at end of file diff --git a/tokenizer.js b/tokenizer.js index b1ba7f9..f8dab7d 100644 --- a/tokenizer.js +++ b/tokenizer.js @@ -3,168 +3,168 @@ const token = require("./classes.js").token; // Create the tokenizer function. // tokenize() takes Pivot code in, and outputs an array of tokens. -module.exports = exp => { - // Check functions for different token types. - var isDigit = char => { - return /\d/.test(char); - }; - var isLetter = char => { - return /[a-z]/i.test(char); - }; - var isOperator = char => { - return /\+|-|\*|\/|\^|=/.test(char); - }; - var isLeftDelimiter = char => { - return (/\(|\[|\{|"|'|`/.test(char)); - }; - var isRightDelimiter = char => { - return (/\)|\]|\}/.test(char)); - }; - var isComma = char => { - return (char === ","); - }; - var isPeriod = char => { - return (char === "."); - }; - var result = []; // The final array of tokens. - var nb = []; // Number buffer. Allows for multiple digits to be one number. - var lb = []; // Letter buffer. Allows for multiple letters to be one variable / function. - var ob = []; // Operator buffer. Allows for multi-character operators. E.g. ++ or ==. - var sb = []; // String buffer. Allows for multi-character strings. - var inString = false; // Keep track of whether in string or not. - var stringType; // Keep track of what type of string. E.g. "" or ''. - exp = exp.split(""); // Split the expression into an array of characters. - /* - - - DO NOT TOUCH THIS - - - */ - for (var i = 0; i < exp.length; i++) { // Loop through all of the characters. - var char = exp[i]; // Create a quick reference to the current char. - if (i >= 1) { - if (exp[i - 1] == "\\") { - exp.splice(i - 1, 2, `\\${char}`); - i--; - continue; - } - if (exp[i - 1] == "$" && char == "{") { - exp.splice(i - 1, 2, `\${`); - i--; - continue; - } - } - } - /* - - - OK YOU CAN TOUCH AGAIN - - - */ - // Nevermind, just don't mess with any of this file. - for (var i = 0; i < exp.length; i++) { - var char = exp[i]; - if (inString) { - if (char == `'` || char == `"` || char == "`") { - var exitString = () => { - inString = false; - if (sb.length == 0) { - result.push(new token("String", null)); - } else { - var string = sb.join(""); - result.push(new token("String", string)); - } - sb = []; - }; - if (char == `'` && stringType == "single") { - exitString(); - } else if (char == `"` && stringType == "double") { - exitString(); - } else if (char == "`" && stringType == "backtick") { - exitString(); - } else { - if (char == `'`) { - sb.push(`\'`); - } - if (char == `"`) { - sb.push(`\"`); - } - if (char == "`") { - sb.push("\`"); - } - } - } else { - sb.push(char); - } - } else { - if (isDigit(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - nb.push(char); - } else if (isLetter(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - lb.push(char); - } else if (isOperator(char)) { - result.push(new token("Number", nb.join(""))); - nb = []; - result.push(new token("Variable", lb.join(""))); - lb = []; - ob.push(char); - } else if (isLeftDelimiter(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - result.push(new token("Function Call", lb.join(""))); - lb = []; - if (char == `'` || char == `"` || char == "`") { - inString = true; - if (char == `'`) { - stringType = "single"; - } else if (char == `"`) { - stringType = "double"; - } else if (char == "`") { - stringType = "backtick"; - } - } else { - result.push(new token("Left Delimiter", char)); - } - } else if (isRightDelimiter(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - result.push(new token("Number", nb.join(""))); - nb = []; - result.push(new token("Variable", lb.join(""))); - lb = []; - result.push(new token("Right Delimiter", char)); - } else if (isComma(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - result.push(new token("Number", nb.join(""))); - nb = []; - result.push(new token("Variable", lb.join(""))); - lb = []; - result.push(new token("Comma", char)); - } else if (isPeriod(char)) { - result.push(new token("Operator", ob.join(""))); - ob = []; - nb.push(char); - } - } - } - result.push(new token("Operator", ob.join(""))); - ob = []; - result.push(new token("Number", nb.join(""))); - nb = []; - lb.forEach(item => { - result.push(new token("Variable", item)); - }); - lb = []; - for (var i = 0; i < 3; i++) { - result.forEach((item, index) => { - if (item.value == "") { - result.splice(index, 1); - } - }); - } - result.forEach((item, index) => { - if (item.value == "-" && index != 0) { - if (result[index - 1].type != "Variable" && result[index - 1].type != "Number") { - if (result[index + 1].type == "Number") { - result[index + 1].value = "-" + result[index + 1].value; - result.splice(index, 1); - } - } - } - }); - return result; -}; +module.exports = exp => { + // Check functions for different token types. + var isDigit = char => { + return /\d/.test(char); + }; + var isLetter = char => { + return /[a-z]/i.test(char); + }; + var isOperator = char => { + return /\+|-|\*|\/|\^|=/.test(char); + }; + var isLeftDelimiter = char => { + return (/\(|\[|\{|"|'|`/.test(char)); + }; + var isRightDelimiter = char => { + return (/\)|\]|\}/.test(char)); + }; + var isComma = char => { + return (char === ","); + }; + var isPeriod = char => { + return (char === "."); + }; + var result = []; // The final array of tokens. + var nb = []; // Number buffer. Allows for multiple digits to be one number. + var lb = []; // Letter buffer. Allows for multiple letters to be one variable / function. + var ob = []; // Operator buffer. Allows for multi-character operators. E.g. ++ or ==. + var sb = []; // String buffer. Allows for multi-character strings. + var inString = false; // Keep track of whether in string or not. + var stringType; // Keep track of what type of string. E.g. "" or ''. + exp = exp.split(""); // Split the expression into an array of characters. + /* - - - DO NOT TOUCH THIS - - - */ + for (var i = 0; i < exp.length; i++) { // Loop through all of the characters. + var char = exp[i]; // Create a quick reference to the current char. + if (i >= 1) { + if (exp[i - 1] == "\\") { + exp.splice(i - 1, 2, `\\${char}`); + i--; + continue; + } + if (exp[i - 1] == "$" && char == "{") { + exp.splice(i - 1, 2, `\${`); + i--; + continue; + } + } + } + /* - - - OK YOU CAN TOUCH AGAIN - - - */ + // Nevermind, just don't mess with any of this file. + for (var i = 0; i < exp.length; i++) { + var char = exp[i]; + if (inString) { + if (char == `'` || char == `"` || char == "`") { + var exitString = () => { + inString = false; + if (sb.length == 0) { + result.push(new token("String", null)); + } else { + var string = sb.join(""); + result.push(new token("String", string)); + } + sb = []; + }; + if (char == `'` && stringType == "single") { + exitString(); + } else if (char == `"` && stringType == "double") { + exitString(); + } else if (char == "`" && stringType == "backtick") { + exitString(); + } else { + if (char == `'`) { + sb.push(`\'`); + } + if (char == `"`) { + sb.push(`\"`); + } + if (char == "`") { + sb.push("\`"); + } + } + } else { + sb.push(char); + } + } else { + if (isDigit(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + nb.push(char); + } else if (isLetter(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + lb.push(char); + } else if (isOperator(char)) { + result.push(new token("Number", nb.join(""))); + nb = []; + result.push(new token("Variable", lb.join(""))); + lb = []; + ob.push(char); + } else if (isLeftDelimiter(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + result.push(new token("Function Call", lb.join(""))); + lb = []; + if (char == `'` || char == `"` || char == "`") { + inString = true; + if (char == `'`) { + stringType = "single"; + } else if (char == `"`) { + stringType = "double"; + } else if (char == "`") { + stringType = "backtick"; + } + } else { + result.push(new token("Left Delimiter", char)); + } + } else if (isRightDelimiter(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + result.push(new token("Number", nb.join(""))); + nb = []; + result.push(new token("Variable", lb.join(""))); + lb = []; + result.push(new token("Right Delimiter", char)); + } else if (isComma(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + result.push(new token("Number", nb.join(""))); + nb = []; + result.push(new token("Variable", lb.join(""))); + lb = []; + result.push(new token("Comma", char)); + } else if (isPeriod(char)) { + result.push(new token("Operator", ob.join(""))); + ob = []; + nb.push(char); + } + } + } + result.push(new token("Operator", ob.join(""))); + ob = []; + result.push(new token("Number", nb.join(""))); + nb = []; + lb.forEach(item => { + result.push(new token("Variable", item)); + }); + lb = []; + for (var i = 0; i < 3; i++) { + result.forEach((item, index) => { + if (item.value == "") { + result.splice(index, 1); + } + }); + } + result.forEach((item, index) => { + if (item.value == "-" && index != 0) { + if (result[index - 1].type != "Variable" && result[index - 1].type != "Number") { + if (result[index + 1].type == "Number") { + result[index + 1].value = "-" + result[index + 1].value; + result.splice(index, 1); + } + } + } + }); + return result; +}; \ No newline at end of file