UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

473 lines 21.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transform = exports.collect = exports.match = exports.macro_transform = exports.Transformer = void 0; const base_1 = require("../alt-langs/scheme/scm-slang/src/stdlib/base"); const core_math_1 = require("../alt-langs/scheme/scm-slang/src/stdlib/core-math"); const macro_utils_1 = require("./macro-utils"); // a single pattern stored within the patterns component // may have several transformers attributed to it. class Transformer { constructor(literals, pattern, template) { this.literals = literals; this.pattern = pattern; this.template = template; } } exports.Transformer = Transformer; // given a matching transformer, // the macro_transform() function will transform a list // into the template of the transformer. function macro_transform(input, transformer) { const collected = collect(input, transformer.pattern, transformer.literals); return transform(transformer.template, collected); } exports.macro_transform = macro_transform; // we use the match() function to match a list against a pattern and literals // and verify if it is a match. function match(input, pattern, literals) { // we should compare the input and pattern based on the possible forms of pattern: // 1. an identifier // 2. a literal such as null, a number, a string, or a boolean // 3. (<pattern>+) // 4. (<pattern>+ . <pattern>) // 5. (<pattern>+ ... <pattern>+) // 6. (<pattern>+ ... <pattern>+ . <pattern>) // case 1 if (pattern instanceof base_1._Symbol && literals.includes(pattern.sym)) { return input instanceof base_1._Symbol && input.sym === pattern.sym; } if (pattern instanceof base_1._Symbol) { return !(input instanceof base_1._Symbol && literals.includes(input.sym)); } // case 2 if (pattern === null) { return input === null; } if ((0, core_math_1.is_number)(pattern)) { return (0, core_math_1.is_number)(input) && (0, core_math_1.atomic_equals)(input, pattern); } if (typeof pattern === 'string' || typeof pattern === 'boolean' || typeof pattern === 'number') { return input === pattern; } // case 3 and 5 if ((0, macro_utils_1.isList)(pattern)) { if (!(0, macro_utils_1.isList)(input)) { return false; } const inputList = (0, macro_utils_1.flattenList)(input); const patternList = (0, macro_utils_1.flattenList)(pattern); // there can be a single ellepsis in the pattern, but it must be behind some element. // scan the pattern for the ... symbol. // we will need the position of the ... symbol to compare the front and back of the list. const ellipsisIndex = patternList.findIndex(elem => elem instanceof base_1._Symbol && elem.sym === '...'); // case 5 if (ellipsisIndex !== -1) { // if the input is shorter than the pattern (minus the ... and matching pattern), it can't match. if (inputList.length < patternList.length - 2) { return false; } const frontPatternLength = ellipsisIndex - 1; const ellipsisPattern = patternList[ellipsisIndex - 1]; const backPatternLength = patternList.length - ellipsisIndex - 1; // compare the front of the list with the front of the pattern as per normal for (let i = 0; i < frontPatternLength; i++) { if (!match(inputList[i], patternList[i], literals)) { return false; } } // compare the items that should be captured by the ellipsis for (let i = frontPatternLength; i < inputList.length - backPatternLength; i++) { if (!match(inputList[i], ellipsisPattern, literals)) { return false; } } // now we can compare the back of the list with the rest of the patterns for (let i = inputList.length - backPatternLength; i < inputList.length; i++) { if (!match(inputList[i], patternList[i - (inputList.length - patternList.length)], literals)) { return false; } } // else all is good and return true return true; } // case 3 if (inputList.length !== patternList.length) { return false; } for (let i = 0; i < inputList.length; i++) { if (!match(inputList[i], patternList[i], literals)) { return false; } } return true; } // case 4 and 6 if ((0, macro_utils_1.isImproperList)(pattern)) { // if the input is not a pair, it can't match. if (!(0, macro_utils_1.isPair)(input)) { return false; } let currEllipsisPattern; let currentPattern = pattern; let currentInput = input; let ellipsisFound = false; // iterate through currentPattern while it is a pair while ((0, macro_utils_1.isPair)(currentPattern)) { if (!(0, macro_utils_1.isPair)(currentInput)) { return false; } const [headPattern, tailPattern] = currentPattern; const [headInput, tailInput] = currentInput; // we can lookahead to see if the ellipsis symbol is the next pattern element. if ((0, macro_utils_1.isPair)(tailPattern) && tailPattern[0] instanceof base_1._Symbol && tailPattern[0].sym === '...') { ellipsisFound = true; currEllipsisPattern = headPattern; // skip ahead to the (cddr pattern) for the next iteration // the cddr is what "remains" of the pattern after the ellipsis. currentPattern = tailPattern[1]; continue; } // if the ellipis is found, continue to match the pattern until the ellipsis is exhausted. // (this is done by comparing the length of the input to the length of the remaining pattern) if (ellipsisFound && (0, macro_utils_1.improperListLength)(currentInput) > (0, macro_utils_1.improperListLength)(currentPattern)) { // match the headInput with the currEllipsisPattern if (!match(headInput, currEllipsisPattern, literals)) { return false; } currentInput = tailInput; // move to the next input continue; } // if the ellipsis symbol is not found, or we have already matched the ellipsis pattern, // match the headInput with the headPattern if (!match(headInput, headPattern, literals)) { return false; } currEllipsisPattern = headPattern; currentPattern = tailPattern; currentInput = tailInput; } // now we can compare the last item in the pattern with the rest of the input return match(currentInput, currentPattern, literals); } return false; } exports.match = match; // once a pattern is matched, we need to collect all of the matched variables. // ONLY called on matching patterns. function collect(input, pattern, literals) { const collected = new Map(); // we should compare the input and pattern based on the possible forms of pattern: // 1. an identifier // 2. a literal such as null, a number, a string, or a boolean // 3. (<pattern>+) // 4. (<pattern>+ . <pattern>) // 5. (<pattern>+ ... <pattern>+) // 6. (<pattern>+ ... <pattern>+ . <pattern>) // case 1 if (pattern instanceof base_1._Symbol && !literals.includes(pattern.sym)) { if (!collected.has(pattern.sym)) { collected.set(pattern.sym, []); } collected.get(pattern.sym)?.push(input); return collected; } // case 2 if (pattern === null) { return collected; } if ((0, core_math_1.is_number)(pattern)) { return collected; } if (typeof pattern === 'string' || typeof pattern === 'boolean' || typeof pattern === 'number') { return collected; } // cases 3 and 5 if ((0, macro_utils_1.isList)(pattern)) { const inputList = (0, macro_utils_1.flattenList)(input); const patternList = (0, macro_utils_1.flattenList)(pattern); const ellipsisIndex = patternList.findIndex(elem => elem instanceof base_1._Symbol && elem.sym === '...'); // case 5 if (ellipsisIndex !== -1) { const frontPatternLength = ellipsisIndex - 1; const ellipsisPattern = patternList[ellipsisIndex - 1]; const backPatternLength = patternList.length - ellipsisIndex - 1; for (let i = 0; i < frontPatternLength; i++) { const val = collect(inputList[i], patternList[i], literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } } for (let i = frontPatternLength; i < inputList.length - backPatternLength; i++) { const val = collect(inputList[i], ellipsisPattern, literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } } for (let i = inputList.length - backPatternLength; i < inputList.length; i++) { const val = collect(inputList[i], patternList[i - (inputList.length - patternList.length)], literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } } return collected; } // case 3 for (let i = 0; i < inputList.length; i++) { const val = collect(inputList[i], patternList[i], literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } } return collected; } // case 4 and 6 if ((0, macro_utils_1.isImproperList)(pattern)) { let currEllipsisPattern; let currentPattern = pattern; let currentInput = input; let ellipsisFound = false; // iterate through currentPattern while it is a pair while ((0, macro_utils_1.isPair)(currentPattern)) { const [headPattern, tailPattern] = currentPattern; const [headInput, tailInput] = currentInput; // we can lookahead to see if the ellipsis symbol is the next pattern element. if ((0, macro_utils_1.isPair)(tailPattern) && tailPattern[0] instanceof base_1._Symbol && tailPattern[0].sym === '...') { ellipsisFound = true; currEllipsisPattern = headPattern; // skip ahead to the (cddr pattern) for the next iteration // the cddr is what "remains" of the pattern after the ellipsis. currentPattern = tailPattern[1]; continue; } // if the ellipis is found, continue to match the pattern until the ellipsis is exhausted. // (this is done by comparing the length of the input to the length of the remaining pattern) // it may be the case that the ellipsis pattern is not matched at all. if (ellipsisFound && (0, macro_utils_1.improperListLength)(currentInput) > (0, macro_utils_1.improperListLength)(currentPattern)) { const val = collect(headInput, currEllipsisPattern, literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } currentInput = tailInput; // move to the next input continue; } // if the ellipsis symbol is not found, or we have already matched the ellipsis pattern, // match the headInput with the headPattern const val = collect(headInput, headPattern, literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } currEllipsisPattern = headPattern; currentPattern = tailPattern; currentInput = tailInput; } // now we can compare the last item in the pattern with the rest of the input const val = collect(currentInput, currentPattern, literals); for (let [key, value] of val) { if (!collected.has(key)) { collected.set(key, []); } collected.get(key)?.push(...value); } return collected; } return collected; } exports.collect = collect; // when matched against a pattern, we use the transform() function // to transform the list into the template. // returns a list, a pair, or any value, as determined by the template. function transform(template, collected, indexToCollect = 0) { // there are 5 possible forms of the template: // 1. an identifier // 2. a literal such as null, a number, a string, or a boolean // 3. (... <template>) // 4. (<element>+) // 5. (<element>+ . <template>) // where <element> is <template> | <template> ... // case 1 if (template instanceof base_1._Symbol) { if (collected.has(template.sym)) { // get the item from the collected list, // remove it from the collected list, // and return it. const item = collected.get(template.sym)[indexToCollect]; return item; } return template; } // case 2 if (template === null) { return null; } if (typeof template === 'string' || typeof template === 'boolean' || typeof template === 'number') { return template; } if ((0, core_math_1.is_number)(template)) { return template; } // case 3 if ((0, macro_utils_1.isList)(template) && template !== null && template[0] instanceof base_1._Symbol && template[0].sym === '...') { // parser should ensure that the ellipsis is followed by a single value. // this value is the template to be used. // get the cadr of the template and return it. return template[1][0]; } // collects all items in an ellipsis template to be used in the final list. // helper function for dealing with ellipsis templates. function deepFlatten(pair) { const items = []; function flattenHelper(item) { if (item instanceof base_1._Symbol && item.sym !== '...') { items.push(item); } else if (item === null) { return; } else if (item instanceof Array && item.length === 2) { // based on the usage of (... <value>), // and our previous discussion on the viability // of ... within the ellipsis template // we can assume that any ellipsis used is used to halt macro expansion of <value>. if (item[0] instanceof base_1._Symbol && item[0].sym === '...') { // do not collect any items here, this halts the collection return; } // if its a pair, traverse both car and cdr flattenHelper(item[0]); flattenHelper(item[1]); } } flattenHelper(pair); return items; } // case 4 if ((0, macro_utils_1.isList)(template)) { const templateList = (0, macro_utils_1.flattenList)(template); const transformedList = []; for (let i = 0; i < templateList.length; i++) { if (templateList[i] instanceof base_1._Symbol && templateList[i].sym === '...') { // if the ellipsis symbol is found, we can skip ahead to the next template // as the ellipsis symbol is not used in the transformation. continue; } // lookahead for the ellipsis symbol if (i + 1 < templateList.length && templateList[i + 1] instanceof base_1._Symbol && templateList[i + 1].sym === '...') { const currEllipsisPattern = templateList[i]; const items = deepFlatten(currEllipsisPattern); // add items to the transformed list // until the templates are exhausted. let currIndex = 0; while (true) { // check if all items are exhausted let itemsAreExhausted = false; for (let j = 0; j < items.length; j++) { if (!collected.has(items[j].sym)) { itemsAreExhausted = true; break; } if (collected.has(items[j].sym) && currIndex >= collected.get(items[j].sym).length) { itemsAreExhausted = true; break; } } if (itemsAreExhausted) { break; } // apply the last ellipsis template again transformedList.push(transform(currEllipsisPattern, collected, currIndex)); currIndex++; } continue; } transformedList.push(transform(templateList[i], collected)); } return (0, macro_utils_1.arrayToList)(transformedList); } // case 5 if ((0, macro_utils_1.isImproperList)(template)) { let currEllipsisPattern = undefined; let currentPattern = template; let collectedItems = []; // iterate through currentPattern while it is a pair while ((0, macro_utils_1.isPair)(currentPattern)) { const [headPattern, tailPattern] = currentPattern; // we can lookahead to see if the ellipsis symbol is the next pattern element. if ((0, macro_utils_1.isPair)(tailPattern) && tailPattern[0] instanceof base_1._Symbol && tailPattern[0].sym === '...') { currEllipsisPattern = headPattern; // skip ahead to the (cddr pattern) for the next iteration // the cddr is what "remains" of the pattern after the ellipsis. currentPattern = tailPattern[1]; continue; } // if a current ellipsis pattern exists, we continue collecting items from it until it is exhausted. // then we undefine the current ellipsis pattern. if (currEllipsisPattern !== undefined) { const items = deepFlatten(currEllipsisPattern); let currIndex = 0; while (true) { // check if all items are exhausted let itemsAreExhausted = false; for (let i = 0; i < items.length; i++) { if (!collected.has(items[i].sym)) { itemsAreExhausted = true; break; } if (collected.has(items[i].sym) && currIndex >= collected.get(items[i].sym).length) { itemsAreExhausted = true; break; } } if (itemsAreExhausted) { break; } // add the ellipsis pattern to the collected items collectedItems.push(transform(currEllipsisPattern, collected, currIndex)); currIndex++; } currEllipsisPattern = undefined; continue; } collectedItems.push(transform(headPattern, collected)); currentPattern = tailPattern; } // now we can compare the last item in the pattern with the rest of the input const val = transform(currentPattern, collected); return (0, macro_utils_1.arrayToImproperList)(collectedItems, val); } return template; } exports.transform = transform; //# sourceMappingURL=patterns.js.map