/** remove any space that isn't in one of the following spots:
)_(, x_(, )_x, x_x, x_\, )_\ */functionremove_extra_spaces(str){returnstr.trim().replace(/\s+([^\(\wλ])/g,"$1").replace(/([^\)\w])\s+/g,"$1");}// all spaces are lambda application => whitespace matters
functionlex(str){lettokens=remove_extra_spaces(str).split(/(\)|\(|λ|\.|\w+|\s+)/).filter((t)=>t!="");returntokens;}
1
2
lex(" ( λ a. λ b. a ) a b ");// ["(", "λ", "a", ".", "λ", "b", ".", "a", ")", " ", "a", " ", "b"]
// shunting yard algorithm
functionparse(tokens){letoutput=[];letstack=[];while(tokens.length>0){letcurrent=tokens.shift();if(current.match(/\w+/)){output.push(newVar(current));}elseif(current.match(/\s+/)){// swap if both o1 and o2 are application
stack_to_output(stack,output,()=>stack[stack.length-1].match(/\s+/));stack.push(current);}elseif(current=="("||current=="."){stack.push(current);}elseif(current==")"){stack_to_output(stack,output,()=>stack[stack.length-1]!="(");if(stack.length==0){console.log("mismatched parenthesis");}stack.pop();// pop off left paren
}}if(stack.indexOf("(")!=-1||stack.indexOf(")")!=-1){console.log("mismatched parenthesis");}else{stack_to_output(stack,output,()=>true);}returnoutput.pop();}
// substitute e for x (variable) in expr
functionsubstitute(e,x,expr){switch(expr.type){case"var":returnexpr.id==x.id?e:expr;case"app":returnnewApp(substitute(e,x,expr.func),substitute(e,x,expr.arg));case"abs":if(expr.var.id==x.id){returnexpr;}elseif(!e.free_vars.has(expr.var.id)){returnnewAbs(expr.var,substitute(e,x,expr.expr));}else{do{varz=rename(expr.var.id);}while(e.free_vars.has(z)||variables(expr.expr).has(z));returnnewAbs(newVar(z),substitute(e,x,substitute(newVar(z),expr.var,expr.expr)));}}}
// unnecessary parenthesises uses in some abstractions
functionast_to_expr(expr){switch(expr.type){case"var":returnexpr.id;case"abs":return`(λ${expr.var.id}.${ast_to_expr(expr.expr)})`;case"app":return(ast_to_expr(expr.func)+" "+(expr.arg.type=="app"?"("+ast_to_expr(expr.arg)+")":ast_to_expr(expr.arg)));}}