Skip to content

Instantly share code, notes, and snippets.

@Xananax
Created September 29, 2018 19:47
Show Gist options
  • Save Xananax/383ef7a9d1fbfaa534a952870f53ce28 to your computer and use it in GitHub Desktop.
Save Xananax/383ef7a9d1fbfaa534a952870f53ce28 to your computer and use it in GitHub Desktop.
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