diff --git a/js/mpl.js b/js/mpl.js index 7e564b3..03936d9 100644 --- a/js/mpl.js +++ b/js/mpl.js @@ -13,19 +13,29 @@ const R={add:(a,b)=>rat(a.n*b.d+b.n*a.d,a.d*b.d),sub:(a,b)=>rat(a.n*b.d-b.n*a.d, const cmpStr=(a,b)=>{const A=[...a],B=[...b],m=Math.min(A.length,B.length); for(let i=0;itoks.push({t,v,line:l,col:c});const err=(key,l,c)=>{const e=new Error(key);e.key=key;e.line=l;e.col=c;throw e}; while(i{for(let k=0;ktoks[p],at=t=>toks[p].t===t; const err=(key,tok)=>{const e=new Error(key);e.key=key;e.line=tok.line;e.col=tok.col;throw e}; const eat=t=>{if(!at(t))err('err_expect',peek());return toks[p++]}; -function program(){const s=seq();eat('eof');return s} +function program(){const s=at('eof')?{k:'seq',es:[]}:seq();eat('eof');return s} function seq(){const es=[expr()];while(at('semi')){p++;if(at('eof')||at('rc')||at('rp')||at('rb'))break;es.push(expr())}return es.length===1?es[0]:{k:'seq',es}} function expr(){return parallel()} function parallel(){let l=def();while(at('par')){p++;l={k:'seq',es:[l,def()]}}return l} @@ -51,16 +61,20 @@ function add(){let l=mul();while(at('plus')||at('minus')){const o=toks[p++].t;l= function mul(){let l=unary();while(at('mul')||at('divi')){const o=toks[p++].t;l={k:'bin',o,l,r:unary(),line:toks[p-1].line,col:toks[p-1].col}}return l} function unary(){if(at('trace')){const tk=toks[p++];return{k:'trace',e:unary(),line:tk.line,col:tk.col}}if(at('minus')){const tk=toks[p++];return{k:'neg',e:unary(),line:tk.line,col:tk.col}}return postfix()} function postfix(){let e=atom();for(;;){if(at('lp')){const tk=toks[p++];const args=[];if(!at('rp')){args.push(expr());while(at('comma')){p++;args.push(expr())}}eat('rp');e={k:'call',f:e,args,line:tk.line,col:tk.col};continue}break}return e} -function pattern(){const names=[];if(at('lp')){p++;names.push(eat('id').v);while(at('comma')){p++;names.push(eat('id').v)}eat('rp')}else{names.push(eat('id').v);while(at('comma')){p++;names.push(eat('id').v)}}return names} +function pattern(){const names=[eat('id').v];while(at('comma')){p++;names.push(eat('id').v)}return names} function atom(){const tk=peek(); if(at('num')||at('str')||at('bool')){p++;return{k:'lit',v:tk.v}} if(at('bot')){p++;return{k:'lit',v:BOT}} if(at('id')){p++;return{k:'id',v:tk.v,line:tk.line,col:tk.col}} -if(at('lambda')){p++;const ps=pattern();if(at('in')){p++;cond()}eat('colon');return{k:'lam',ps,body:expr()}} +if(at('lambda')){p++;let ps=[];if(!at('colon')){ps=pattern();if(at('in')){p++;cond()}}eat('colon');return{k:'lam',ps,body:expr()}} if(at('forall')){p++;const v=eat('id').v;eat('in');const it=cond();eat('colon');return{k:'forall',v,it,body:expr(),line:tk.line,col:tk.col}} if(at('lb')){p++;const es=[];if(!at('rb')){es.push(expr());while(at('comma')){p++;es.push(expr())}}eat('rb');return{k:'list',es}} -if(at('lp')){p++;const e=expr();eat('rp');return e} -if(at('lc')){p++;if(at('rc')){p++;return{k:'lit',v:BOT}}const s=seq();eat('rc');return s} +if(at('lp')){p++;const e=seq();eat('rp');return e} +if(at('lc')){p++;if(at('rc')){p++;return{k:'lit',v:BOT}} +if(at('id')&&toks[p+1].t==='colon'){const fields=[];for(;;){const key=eat('id').v;eat('colon');fields.push([key,expr()]);if(at('comma')){p++;continue}break}eat('rc');return{k:'record',fields,line:tk.line,col:tk.col}} +const first=expr(); +if(at('comma')){const es=[first];while(at('comma')){p++;es.push(expr())}eat('rc');return{k:'setlit',es,line:tk.line,col:tk.col}} +const es=[first];while(at('semi')){p++;if(at('eof')||at('rc')||at('rp')||at('rb'))break;es.push(expr())}eat('rc');return es.length===1?first:{k:'seq',es}} err('err_unexpected',tk)} return program()} function runMPL(src,print){const ast=parse(lex(src));const global={vars:new Map(),parent:null}; @@ -145,7 +159,7 @@ case 'forall':{if(f.st===0){f.st=1;push(n.it,sc)} else{ret=f.last;K.pop()}}break} case 'list':{if(f.st===0){f.es=[];f.st=1;if(!n.es.length){ret=[];K.pop();break}push(n.es[0],sc)} else{f.es.push(strip(ret));if(f.es.length