Created
September 29, 2018 19:47
-
-
Save Xananax/383ef7a9d1fbfaa534a952870f53ce28 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const out = thing => console.log(JSON.stringify(thing,true,2)) | |
/** | |
* checks if a string can be cast to integer | |
* @param {string} n | |
*/ | |
const isInt = n => !isNaN(parseInt(n)) && isFinite(n); | |
/** | |
* if passed string is an integer, will cast it to an AST number | |
* @param {string} n | |
*/ | |
const castToIntIfRelevant = n => isInt(n) ? {type:'INTEGER', value:parseInt(n)} : false | |
/** | |
* if the passed string begins with `#`, will cast it to an AST identifier | |
* @param {string} n | |
*/ | |
const castToIdentifierIfRelevant = n => /^#/.test(n) ? {type:'IDENTIFIER', value:n.replace(/^#/,'')} : false | |
/** | |
* if passed string is a point, will be cast to an AST point, otherwise | |
* @param {string} n | |
*/ | |
const splitPointIfNecessary = n => /.+,.+/.test(n) ? {type:'POINT', value:n.replace(/\s+/,'').split(',').map(x => castToIntIfRelevant(x) || castToIdentifierIfRelevant(x) || x)} : false | |
/** | |
* parses a string into AST values | |
* @param {string} n | |
*/ | |
const detectValueType = n => castToIdentifierIfRelevant(n) || castToIntIfRelevant(n) || splitPointIfNecessary(n) || n | |
/** | |
* Operates a regex match on the passed string. If there is a match, returns the elements of the match, | |
* but not before parsing integers and numbers | |
* @param {RegExp} regexp | |
*/ | |
const match = regexp => str => { | |
const matched = str.match(regexp) | |
if(matched){ return matched.slice(1).filter(Boolean).map(detectValueType)} | |
return false | |
} | |
const compose = (...fns ) => arg => { | |
fns.reverse() | |
let i = fns.length; | |
while (i--){ arg = fns[i].call ? fns[i](arg) : arg;} | |
return arg; | |
}; | |
const regexOptions = (...items) => items.map(str=>`(?:${str})`).join('|') | |
const IDENTIFIER = `#[a-zA-Z_]+` | |
const INTEGER = `[0-9]+` | |
const _ = `\\s*?` | |
const POINT = `${INTEGER},${_}${INTEGER}` | |
const VALUE = regexOptions(INTEGER,IDENTIFIER,POINT) | |
const IS = `(${VALUE})${_}(IS)${_}(${VALUE})` | |
const IS_NOT = `(${VALUE})${_}IS (NOT)${_}(${VALUE})` | |
const IS_LOWER = `(${VALUE})${_}IS (LOWER) THAN${_}(${VALUE})` | |
const IS_GREATER = `(${VALUE})${_}IS (GREATER) THAN${_}(${VALUE})` | |
const CONDITION = regexOptions(IS,IS_NOT,IS_GREATER,IS_LOWER) | |
const FROM = `(FROM)${_}(${POINT}|${IDENTIFIER})` | |
const DROP = `(DROP)` | |
const RAISE = `(RAISE)` | |
const TURN = `(TURN)${_}(${INTEGER}|${IDENTIFIER})` | |
const LINE = `(LINE)${_}(${INTEGER}|${IDENTIFIER})` | |
const CURVE = `(CURVE) TO${_}(${INTEGER}|${IDENTIFIER})` | |
const NAME = `(NAME)${_}(${VALUE})${_}AS${_}(${IDENTIFIER})` | |
const COMMENT = `(\\(.*?\\))` | |
const REPEAT = `(REPEAT)${_}(${INTEGER}|${IDENTIFIER})` | |
const END_REPEAT = `(END REPEAT)` | |
const PROCEDURE = `(PROCEDURE)${_}(${IDENTIFIER})` | |
const USE = `(USE)${_}(${INTEGER})${_}AS${_}(${IDENTIFIER})` | |
const DO = `(DO)${_}(${IDENTIFIER})${_}WITH${_}(${VALUE})+` | |
const END_PROCEDURE = `(END PROCEDURE)` | |
const IF = `(IF)${_}(${CONDITION})${_}THEN` | |
const ELSE = `(ELSE)` | |
const END_IF = `(END IF)` | |
const expressionOptions = [COMMENT,FROM,DROP,RAISE,TURN,LINE,CURVE,NAME,REPEAT,END_REPEAT,PROCEDURE,END_PROCEDURE,USE,DO,END_PROCEDURE,IF,ELSE,END_IF] | |
const EXPRESSION = `(?:`+regexOptions(...expressionOptions)+`)` | |
const parse_expression = match(EXPRESSION) | |
const tokenize = text => text | |
.trim() // remove first and last space | |
.replace(/ +/g,' ') // replace many spaces with one space | |
.replace(/(\n|\r|\r\n|\n\r)+/g,'\n') // replace many line returns with one line return | |
.split(/\n|;/) // split on line return or ; | |
.map(parse_expression) | |
.filter(Boolean) | |
.map(([type,...value]) =>({type,value}) ) | |
const nest = tokens => { | |
console.log('>>>>>>>>>>>>>>>') | |
const max = tokens.length | |
let i = 0 | |
const buffer = [] | |
while(i<max){ | |
const token = tokens[i] | |
const { type } = token | |
console.log(type) | |
if(/END/.test(type)){ | |
return {i:i+1,buffer} | |
} | |
if(/REPEAT|PROCEDURE|IF|ELSE/.test(type)){ | |
const {i:additional,buffer:children} = nest(tokens.slice(++i)) | |
i+=additional | |
token.children = children | |
console.log('<<<<<<<<<<<<<<<') | |
}else{ | |
i++ | |
} | |
buffer.push(token) | |
} | |
return buffer | |
} | |
const parse = compose(tokenize,nest) | |
const text = ` | |
PROCEDURE #house | |
USE 1 AS #start | |
FROM #start | |
DROP | |
TURN 90 | |
LINE 5 | |
TURN -90 | |
LINE 5 | |
TURN -90 | |
LINE 5 | |
TURN -90 | |
LINE 5 | |
TURN 45 | |
LINE 3 | |
TURN 45 | |
END PROCEDURE | |
PROCEDURE #sun | |
FROM 10,0 | |
DROP | |
CURVE TO 8,2 | |
CURVE TO 10,4 | |
CURVE TO 12,2 | |
CURVE TO 10,0 | |
RAISE | |
END PROCEDURE | |
DO #house | |
DO #sun | |
` | |
out(parse(text)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment