From ddb4430ed2845a9bd826eefa2e39bf6b37720534 Mon Sep 17 00:00:00 2001 From: David Evans Date: Sun, 5 Nov 2017 17:10:06 +0000 Subject: [PATCH] Add support for aliases, and fix various issues when using the same agent multiple times in a statement [#19] --- README.md | 13 ++ screenshots/AgentAliases.png | Bin 0 -> 22099 bytes scripts/sequence/CodeMirrorMode.js | 316 +++++++++++++++------------- scripts/sequence/Generator.js | 111 +++++++--- scripts/sequence/Generator_spec.js | 106 ++++++++-- scripts/sequence/Parser.js | 44 +++- scripts/sequence/Parser_spec.js | 62 ++++-- scripts/sequence/components/Note.js | 21 +- scripts/tester/jshintRunner.js | 2 +- styles/main.css | 1 + 10 files changed, 431 insertions(+), 245 deletions(-) create mode 100644 screenshots/AgentAliases.png diff --git a/README.md b/README.md index bbc6b43..4ef3f9a 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,19 @@ terminators bar # (options are: box, bar, cross, none) ``` +### Agent Aliases + +Agent Aliases preview + +``` +define My complicated agent name as A +define "Another agent name, +and this one's multi-line!" as B + +A -> B: this is much easier +A <- B: than writing the whole name +``` + ### Alternative Agent Ordering Alternative Agent Ordering preview diff --git a/screenshots/AgentAliases.png b/screenshots/AgentAliases.png new file mode 100644 index 0000000000000000000000000000000000000000..0724670ebcd07cd05461102aa5089fc71292caa0 GIT binary patch literal 22099 zcmce8cRZW#`>#*;LYLO|X;GVsy<3XfBZ#eN%%U2hF%Z zRh5v~u@xt@pYQLC@9&)Rd%e!<_~W_r+|T`7_js=Ry07uRL>U`uGoE2T!@$76sB`y@ zDFefCI0M69O{b30Ypz^&R%c+aVAr{G+wAf1Jk}UH(Kwn@Lf_rC3(p~>6d^M+y40*QmwL5-N%+;`T1RcPvyqw2EF<4DfjxYSnc&l zlVcYgZ&|Z;F^*;a41Rs(Z9;_i_Kv;Zd|6G|K-s5-F><%7oglV+9c4|gD6!b3)amlm zhx7mAFKNHrFG4-fSSxsoS)9nga5EDpdgg^X+wyVn2?mB_IQ@~a3j@O~xX|S@2USmc zD)9StUB-f!aW6&op8}3DyuWMe!tf;FB<>sogP;@x!~aeBg#O+^yMI^a;(yow@5=)G z;mZv4RtH4>U3oZ~11blj`p--H(}RZxt^Tv}f11;=)iS*?$^c{85r)G>f(gpoFQ}UL z`mM!xHa@k+ zGa=P2>I$Mm3a88k=6)o5LVKErQUEwu_$SNxcqLIs@mZ!Z_G>olhHo`%D0kr6=c=^K zPca{i_Xz;>0wxI-cPF13dgqc1JElHHUmFI7DMPrDd4@5m-NfY<+=NbvA-MtHe%evo zJsg#Xe*!pcBv{3_Hal*t$L#({u#>~sW$9+5rAsm!1H-*wAxLsUgMCP#>Mvr2`q246 z0)nQ97EwrB>@}GmkevT^J0!>XSa3LsY)tA<%)6wR6(mwvu85{>qn4fvra@J&EyD&B z?H->EA{1rcX@32{oAR!U8XhXxo0fJ+!E&tQ#FC6J5mF~$dj>wA$J;&7LDc450_!HYZM9h2@jEb6&l(6 z`yTUFjoEK&AEh=|$8}hVeY=Qt3#PrwzWhW-m2h3G=Q@%(Zc*5iLx5tQg*4seis3?s zS+__VO+@Ie&p^gpgV%u}-`$T+*EZg6sj2;OZP&A3b@~?kF9rs1S0|%EwK|VC8(X^C zQbm<=GC?)#46&}GfIDGu_A%jYi@s^3%$bl9*M@;NfNtX)Yvqnm%Fr|C?I_n+5NF;A zll;bVOqqK`HI&ppodXL+7U~R7vP@XTO_xxDH*Mdq?SyPMlGBPy#VY+WDqY96u@G!y z?1eGe?f^0(%1WziQ7qeH$cDp@%~A=I!~FGJPFAPS4sxQ3q2U1E!R2Q_{)RK%Cry?}N{-h>@FOl1|p+ z6N+4NPFR{S?JC>O+?5^wMdasWSSr_av-c@%d4=IGU0C<9Q_@g!#p(Varw&j)o-q%F z(jWThWnS7c^VrKg%QZ`aFuB{TNG?Zr7khsSH<3r2Jrp(&Z_!4u+v+^m5oNRynr-DCNS!&t*2(2|MXXM@9Qm*UCj2UCseco*}mxX!_4Dws_31A zkJeTO&}M^E)oYr(UV@w{f{;Q{1I1zyOg*0@gNrmACJr;H*@hG=)7XQj?np!zTtFE)WBfvcrwL629=we`C~kcN`ahoxS38g))ipntw{6L*&%`~EMW3lBY{NRE zY^n^l4M8WqN&t>19?DV^6u$Tt_uFITKg~|2B}((z(_S6>WQM7D7|&$%%h%uDCE(bD#=P%! zTnQtW{Jxs-Kr-vjcMb@8B_LW0mVX>atoe8sD6}Ca8?Kf=GpNdY^79F!TwOcS z%hyo%KHqrk-tk%}EfB>_gil`XV2rgKLT_z+)axNsjHVGYrDBvBnsIif}(gQJ;B0}X`bCu3w^2j0VtrqdV0A}q8g{_I0n)b3dNR=7hyX6&i{!O{F)(82}i zY}BTz1TLCd5m{cy#08IDf>OAz%7Sm$z>`e6RFCf{LJCJ}=1CJ}N>Q6vJb!B?fmcV zOh|64bGO@PW{$R%gm5EQPpg|+$->&J#(wxBo&bn2lBb>r{*m))rM6vMFH$V-0*=Jl zn;f5ksn8fM+*mESNqvqCHgWrM4js3kH)kd7Z(XwJH`d@Ir;dzM=NXn-e{eH-D4;J{ zTd9iL9co9j{4jra0nLVo+>*?}e>Mz22rd21df=DwpWz}h9 zQgO`Ec8WqvxXfi-=g7=p1cYT^O$S>r@mN4WG6gNxe)n;ZQ}^)MdAKJw4@jD25rph* zOT`n9x$fwX#QogcF6r0Far=F8UcvFhNVD&7yof1jR-+W3qU^5*j*WB? zatLAqk1L^)8!UIhJQB%lhs>ZLCw5%=@4eoUdqOlRXJfcr@h^3a7q})*P9aFow#K10 zaSL9qm9tm%a+ zOMPOwaiet(krK_ZeqeF?uxAK-6~J@-s^ZH76)uJg>@BCoxqtDMvv?Xgrr}$vg#kQ( z9Jq^yoKGGKPVfxN8!j`0``9Es?C#6EXssz39hy?OS zR@zDI%FZLFpnPSHBrIErb`Ah!8b+`nsqO-P)g~P>&tp>;nj>w&Rvjz>vV|)FT&kto z4d)YjJ%nl7oN{MeYfNmaH63O?K1qRLA*!EVWbe_PVC0~laz)bNzIy|%ZkG%rGoEgK*Tnl57A7E zpDqZbXS*y2vK@AW#(SCyxGc6fIIKtn`;oL;Sj2KYQ|4p(SL1REgwAlg+&hexUi@}>(3U0ZQT zwo(9*IRS7L(c&RO)8yrvG9jm;J6RdE={!TXDsIEI^8gMwvnMZ^3S4BF@4`3OC#uEF z!(42fVV&bJg7DY(Z`;0g0;6P5>;)9P^79={s^Cz~S^feo{Wa8#Z zg^nJRs&QT7aWDfg8oVdRVBQv_*@o-qT4r~Kc4Iq%ed0aS)} z5#1aa5Dp-tF>Q zKnD8SIJzU-CSmhzhRIFS^*+vI*r}8)wd&V&!%Fa|@RSC;^ZWCSN22@1&OhdKlZUt~ z#oePVD7owIb@sKSYcXoW`{jj+E*=ScA{9kSD!5VC5FcbVQ5Nc!V_!!hHXG1)(N*>d zd6TvD%mSBay0E08hjEcq!s-+a7_773z| zDyKbY!E_hid2+tlG>3J=&~R={@o#W#`|+`9p6p#A)s(NO{t0-Yn$^BNCn`V|+||IY zGC!0pJ_Uc$*gvae2N2&!5 zjBqy>z8THt-ZjEy8}zZDuU`SWNIg>Y>AcS+u_0#2zXoam)D_)b2toi~*YT$c+&oH` zdAH!&NWjEE^~XL~Q`TeO3VvS5Lx?;#T`ie=AbLh7F|H4sHJgQKxcbp^kN69x!1<1Z0b;AB8VNVMc06h z>E_~z*saOZ4H;qQ=Y5$M*Ta7gR|dCywi1~h(Ox+8QBY#HbsGOww%7O9YIYIhN4txxE+oikn zIsy<;5B^xsf>O8O)M4}$F#KRmeq*UJ5= zq${Vt-#E|<{&OM+6c362tI@wB_8&bQjO5@A)=Q(NX&6G0ec2IX(goc*?@}8(^UY!^ z1=TC}7k$G$$NjQx5H>f}>7KTB#nu>Nz5A}8ywjj6P};Xioti2R?MBEB5-r$|=Hhu1 z#cm@B)*ytg8)7Ddn}@vHiFn&SCWYF*t>$?~d_qlLO2*O?u}qwD91E3-+1cOx+xs}q zR)%D`ZBxP{MV#pq^~{S#71yqb@oN8)u=P1Din@h}S9(V;1>PncrC|(OVhcT(A z6n_>#wB)ck3g)&2bH|-bsQT!vbh+oVAY^}2J8Q~`Nsu;U<`Ndf{RR+Ed>>OjxjD0G z>aveKf{Ovf)9v;%oilw$1!=Esg(=Tk#-+|ld}f?~$%LD0d2h+@%!%Yar-d2Wq{)TT63?(Ag5Gx`5ws zRREa#Os$)iOIys|_NJlSnFkxtwHb`bv!jiI>y+?jMvu+NQO_~6w7@f7P}qpXOq2NK z?1;P;^DHC8#>nL7;8}+5loi==*4FK+_|P1PNOVmMT}P*3j30BfB>^1HqWA???VgGl zH?LoH*uw-?SlF)$Py2MEd@yA`Q1udJFQJJmIVKs3{dquhH;t4H9?fnw1PB!F+?P~< z_CM)j+jF4?p`q>9e6M3EC;qCRJvj@6v9}ZNPd|~5H|hF0#(N9iQL~~kIq-VSITv4` z3#ay9X`WQ>Zd`45e7`G~ei=wQdfVw;HJ_CORRZ48Iy=x9wK*FJ6)ZAA6R?%jB_2sM z)6oLCwfssI*R|@(0lV$NdM^1QHJg#;i8!zN(ksY*UF13Y{9mRUY@YoyEOwnCh(WgC zzLG96HO`JwyIwmFLl~6yBs-4agv{>W8#HyTxuv(7Ux|P9VOckODM)%^yDboj!&An} zB4eR09$0V2TkxQ28I!a72#_DZ8EF)|E@{$zvLmPWiWl(;*T#4?wxSWa9SS68>T$;u z&5XEsfn!P3CsP%VMQwkmihYIuK#zuYoPHxdYwpQWLV1?#Y+A9WTC+~UjGseaX#`ECwh95x6)O6b&+OCU3{p=(-YJI;c`3u{&F@k(XjZZ zfOwn<$#Y=8@#ROrf(dAF6^9$W%)O+fywMO6aVzvSWM`e7tICesZ+vP&e+Js1e@6V| zx@9>0lsnuRshE}4U&7zD7_8`jEkrFxAu9i(O2wqK19S3&S4AjeRKVpAdCM*D8&S z$9)g7n~?96A`O(Z1l2m?a(Mid@*}2AJeku&Rc-w~h&%8xHYpc2J?~}Ca%ODzv*=Y; zL0V}HShY~%)BePq?Yiyemg!mq#YFW*r!9M=O4{D=+XM$(4ODFWRo>18^X(OW8JJOS zk;iHUBtB5OdnZ;TbH%1a0-H9TXpbwxH-na%ncFwUrfUZ4!V*jbNXOACX@IcQZLhw} z_}tpZX=nnC#a8%=%N!61sqm`_*xFf3lv|`$UT&C63*A@_Hf}T>ut~IRt_Nt(*AZ9= zdfte24SHjJ94<{O6ZhmWV0=?x*us73*eATZhrUh$-KmmqGTItJLIe`#1NeQ zy4PCQn&cj~zP40GPR+IDmf-9BEo^3o`W>vl4=yM~QGviVgW zuP;9#6!m{@TX6BjY$T48unG;Sz&ATS0B@MKWa4M#BQOb;Am<4a^iVpbp73?z+W2%x z*4kqi>Psqkf4NDjDx2x?Fia?bZ!7kk3GT5Z54-aNvRB) z1xY^E%t{^FH6ZP^=gVIkCrc;nYY=u)A1#}Xdh~^6ZG2gzDVE!>l>#{`jAI)tuJ(Wl zD|}wP8yM8txRdqB7c%);<)3N?kQQT$=0_Tz!SoxU_9fL4zK+!?L*wfwp?6g%=4t*m z)FC?{aCp`%D=|lA#Oo@D3n}gp4ML^75Ir_Xv9>% z$A0kxN06Up+FFrDm&a=IssWqPPAmlIdug%1yiw;@OHnG4tveh=&yT358iB0LWr(oXzh%+Hsz_ez9 zRM%Ui&uW&q8x~4hL}36teYb1asu){Jm}(GW%6l3}JaDz4YxbnwfOW_1fzRR4=ndBO zMMANbasi%K7tTAo3#j~QrFYQ|Hww;F|EjHS@?Fa=Zo{Cno_t!7iz92hXgOcQ^eE-x z7OyzFjf?6+tpv<}mDt?689pS2oxA*$WZ87jM}OEOE6X+%JU-2ZNNonFDy0v$u(xMX zwIl9N_}4~n56--V!Lwh#ObO~LuyG;p**?GrdmP6FD_?QIb<7+$Du`%GwwMRbu?<1AUnMUM@;W2JPbk8Ce-rN2?@SgKibSXdTvzs5+ifs0JAEiCw>; z8ukJ9%v@)8i0g}-(8c7r_hk95`!rIy>f6c=z*8S z&~gJ|@UeDPYl)q}#}2yreN~A04vE;5b)K#6mb6$1Kg_Jt1SbKkeXLC}r+rn|E+1pp zScv6{R|0nZs*TAjcq&q*meXr2Obd$g*~UbxXie`t129(s^dKscolG4|LNsxoXE&h| zMxmd#+6qwBJ!y|3F*j*Pw!){L=D@!0{5^dmuPZ^L6>8+>e_}+ct!qNK0$&o_LF?)7 zA-;$@JDi5lR?7iwur1zfbk^q`i+)!e3}05X(RKuf3RJv!)gTc~lln2|0JUdG3*x>g z5MQ!2Q!~VNbU~5KWM%?SD;hv5SlH<7!fkf%d87sXJ+15k^{Yuz*@D`yDQ|Kb;W)wf zEsdWZtVlQOq>X!vnH|{wqyavlS!?|Didi2a!aFiy z&E#TCe-Vlk7=5m3|4Z1OmjUd9M6-ppL&JFe@{HfO_gb2AH3!!8QB$ZPZf$7(FJ}X| z4KS#CW3c;VeAZ4)#FDq_NC{R=Zh3r6lf#gcaY}}I( zHfACUWWh>_I~*^-{fYt`l{SVox5T!s$`}g*&r=6OV;WNT;(s z%~k}>hn)v=?h9PigloSBWL#s;iZCAKP|9rXsvlG6@`#fmePwC*Tz{VA2&7C^-oMn6 zEdP|-kc&KQWeMM(9*VfznZLFoM4P`!sk;%--1)P<1I*QX16!i@!b);Qg!ahUuT+5Z z^G&@v-=7w(_i+&M-W zK#!aCxY0MuuylVHV$RSF+jvHw!e-2y(FyS>EsB!eM*lqaxcyab9^7bwAs2zEW=6rY z?71=}!CzT+T3#)^9EERwAh?wRgI#ACKRdjTCG2$bVDsd#jrzZbn9oXPf7m3u&*|PT zbN{Bxg?7Wtt@nR;lbXz{_AYrS4Zpgu6p?=syuN13DAeLm(f+p1=}G~~-%uLS0+zv!|}6U)mY9bqgz~eA<(b-=a#C zAJv(Nt2XHjZs;!GQN+&xNrp%Q&&5kFAR~Cjqx;Wy>nXT74r~V+A7X(qk_uXLsIGcn zPyH<=#`U{ksFzAuNId)Z$|;ZZXg5IZMT@yB1wZQ%liej;?oFI#}!&$TDOfyXPx zB9NfCWf2GyvMD&pxh$eheSXU4s0C?+MWo}K2&8hmgL2E5^Z`hsZaaTq7areBhX^`r z6H*!mw$+{X{9y~UtkCiXuY0csL=Lu7x&=IN?qmKn5K6`Tes}@ zO=(REQLhu|vFAQWd-=RxW^v!@%e`VoMU=OuFI6+;pZHsYHeBL`^=l(|ngFS+jF@Cy z?4?-3J;vnFCsd3V6T$_9NPBx)MzQH8q#tW?k8!pIkpSvCvCGVT>NXsGBm201k4B(L z&P;CT>yPlBvJ(8>wP>_<1z;l*6^v?EUa5cdW;l zIcvft5FcPFQK-K)itwKU&jlqnm=jKdxy`_F@vhf@`x3Abj|Af&<$4-T?duYW2BxK|IL$*QJ73&%(xF!Xf7WBR_1TM8L zn%7km=bjdkzyB~Rtj9b!Tt?ePW~Mh3n`%t{`~uahQGoBzHcxE$g|l^!H(78b(;YiN zctJIw22+%yV&VHkjyFn6uEfUwvr&dN$$C>!vkJGoy4BBw!xw#JFGT_^P5yJ_|y z>@?5?lvp6IRAa(XBUxXgkPU#k`hsNz(guy60(O98x_c-dNM45U^cleCKgnGBfr_bK zrFSNB5I%$Wy?A5W!?AY|A-eGO1;R*+{vBRvHvrPw?E-8rJ*gut_>>KW)l0ej0<6uI z8}8U=UzAk(h2!jWR~Vp2pSVAT##PeMWFfewj#HiTI-qcwziP3V>v>F__I>2yVX>mbB$Q`Tt=p?w)_7y> z+CO1b8^;d4phia<`mCl*M^#hysR>6uJVte0L zc@}6A=_5;!#dXep&;s&_OiB{0%$A)p7p20k{wj3lb6iRuk}Jl1 zOZZC)4Uj9*w-SXXbi`Ip6Tzczegf&i)k`loE*aFVcs(p!==c))=+jFgz5owtx)j%@ zZNkCDd#5?FT0)8PBULhO`YC=HVSdysJkOM*OQFZp9@Ji%=mhug;*l#dKGlK{zIX9~ z-U$NcI&h`fv~$d^cL`riNOGSR8&a=*wEA%RSzlOvcZ&?XPk0O`x#J+6vfUe#js10T ze@U=P%hL}XBnbHydH#GsdbA2M*v!lHJvA)*;Pzh10f#WY{c&(vv1!X3Jg~8gCP8G0Cly z;c%Pv+n_0FW?O1v3kD=4;^E}TofF^%272qig&>d!Qo!ZMkuEp4+q>;Mre1l=Wu-)BQWd3^)Mssn({8-pQCUXMXu03S~u_(D5eOcIG!Mz7CJJ#o}{&Y z7R36MvwI%LSYY1Y=2XeV${a8+z6YC1yJGDeZS!#vhsJldSvIOHEE$NBXPvqSwRB=9 zEk^svl^fLgb>;5OpFk>6+#1-*$^G`?n-9G0Cr#iY<#ZWU1=}(2g;?xRYrBXn<}IYy zY~uccbX#aAtr#fLhU!W?Ik>mIn$xxs;w0A6&${0Rtzkg^HMp@EoEQY8-sH3 zp1-%s(`?_{U$*01`K3Izn_C|X^_Gka)t;*|G!RK{%%kt66BgshzBYdqtH)=uWu5Pl zw_k!txWJfD3ebC)ZgZgV{^12bRZLO=696sFSJM~$k4q*?>-fxwcu6nx{@BRks{BRYqa-9U57~{Y`k@JhJ&FpArlgU^N0NXmP6=u9m4avv1Xxcf=`6aHC{}-ZTvJ+@m}++W z^bc)nXlEBEPg!9Hs`>yB5@MlLL7)v6GtKu=2i6UEp2x5k2z(t?FONv~Gv29b^BqrK z&6V|p=31uIs0FdVY+Q}-FEj8&T3FpN{x#na$R=f7A!d(yrikuS%)3&)VU_9`R6Y^5 z8T2&`IgyT*fEq`xCYU>0Nq$)`6lC_SGNi_IQ70@Mq^^MsY4mUFX>l;>-%KGxRxtqg~wd5^-^_isU>`^&8!h+f_?0tth994MY zzV~7=rxe}`UqdEMxc(U|3v9=+3GeuKy$5XFB%NZavhl zhlO19MRonFnBLW8LKiP=An2?KdhwG3J)Asqc5Q$l>|IV!dKA)iica}IFDOe$8`=i%(!Oom`W5AO&If3b5BT(dxMOc5qz7AsOCmb-ItT2DZvb@$( zUUT%i{cFf|W;i5%Inez*$B_hZK-k7OffxY#r!-Difg)Wz(lNE4taQug*vIHh_ z?S|9RThN1r&b!g6KAF=(UCu3J@=f@3q+t7X>`YXNKOD+V|XAOt^{iEAx7Dzp9N zLQZYlR(_wY&zN2Ym%4qybXW47Ag*_cLip1jaZRe8D)#B=D>v8EwV~`E+c$MEYufGX z=u$c5Gcq3CW8xMHHkvStk3>^@(pDNvjb*P{NTE&JvR|yw>MT#TZy@nAlAl*w-_Mzl zEpGv3i36bVo4+P48%6BHASx60_q%%gd)^*3G(bSI(PGbJCBD0vy3CqB=x+%|P3VtC zr)tARe8PGa#unwT_&j9pD;wv z1qUQvfd8yFh02p_3>tsHqp6C#(Q96^g~#8PB5;@vdoNHf3jAM33z{@^>5B~5%_6QKo&187H)<~E(xn_X8ULQ(XJ zK$RlK7rjhvTPB{LFd@3-PA%i88zK2&fs`UW0Y7^I=*Rjo_b;r#deqvs#@iOD)#nzZ zP#yFs6~+7z#XQ+8?w_#b6Ljoc5eR9WJ4^2XYBx(U+~XmO!6idRe1i+70*AE3*!jOLf1_ zf%kh*pm32N(!17ZkD%kkRu$`tp)%qaMlZlUnuPcYf9@w=Uus}oR^-tOv6g$Hf1c=f z_axNT>lvV@{rgSn19o;vR>0&mI$L-3JuL<59~JZLN4Ao8OC-A-khn>Bb0jp zWTIan1b3SL%O`+O55FZU{UJ5T8Q7 zD2FFE_+Ku1ejDB{n}v`Oet6`U@tet=S;15ZeK z7NniEMPK4gBJ+=5D#nbp?W8@CI)cR4wwQlSwB0;w;B=P7RpezY!K~rDjT*C#YJHI! zy$K-yKO;EzCyC}aH0R*vH*)4+Ee?=f2QZq0w3EY|gUS6)%K4}MpNI7OKX3k7a~g?w;_Wngk+qD{rX#W#cqqF2L3X00gUbV#RR zGTS|A<4d+PXcHC&29|d>>l0Pg&41@99V7_-PVJ$iVh>m6zgqsY%D)%*f2H~l_5XAE zXE}d=GyQ)vz<*ET+0%u5;}aJ0uYMLZ4V zWoDPXn1UuN7>%9F0Z9F;-$Fbiiy+M)tH(XWq4FuCUr6#0mwSkH3E?*ajV>a-3(|T` z^698HHmToB=12$Iyfk!srzeDL z<2H7$xWEv+PK?cgc=TQuGcDLd=ymaty z-Q5@f&w!_OuI6@Q?QHv?t1BUo7b7h&FS4^cEw zd+eMLKPxcsj^BU~aNG$lVzcj~m}~8MHq5Lf)FY((m+`foD!W$a5Sxn%H^pc725a&C zU_E#rwJq()0(`KptB)|~bt^b3kpgYA%r^=%5?v1UXp4E3e!*rB>ioQkb2r#3j7in& z&LQybK&JVDQ8LLx<}NQVVAm%31zU``>*Z25v7TS)-yhBG1#tY*Rpo&f#Ki0d$K~8 z7M3Hr|74&I(b@WOjw&d_o_RmyeO^;Y;NP38URk~1nuvnxR}dk{G<74*b=BY|g%h#= zD5R(%Z($r~MtbD;2qYh<3`8m-*Cq`<_dW;gP}*cZDl1sJM5}@#2O^v)Ng5$z7lc>R zwhc>aR5`^YCM@U9hHY?!l=KBkjgGuB1gmm*TRpRe8ZSPVJ9_pUIGZ(CTj!FDmCe8Q z*U)EWGBu1pWz~JNZvKNqszUOHM{Y{7_pUjAil6r1<+C{3t!;jTZ_BMODYj$h_SQp} z($+_DP0AN?zaiZtMoEYaKAy-r29wNX$cG78OKrz=Z`P@B}b-rB_jc# z=1?7^mhD28#ifA$gG#fh^G9nC@F!HkVI zB}jb7n1btmd2k5Ot7DQYHfn2WMFe7#3om!NFrh{;Gx(OB37s<32lYCVRrOC@uT`qC zX>^u2CSI-e{%S;JM_p_%&zPgi5N5G|AQk+07fqn(;f7{6pQM( z#H=c|{F&Cn+uUGOa7fT-t>b%KlZ54u`&#y-RZLWs>R_FZpO(PKNA@CFBM`fiH6Nr! zK`2WF$bux(rWbd14Sd}o3yC*6h6`4oux?Q#vNnhlyS)!#pmb;_J-D)MdE%uDnw};k zh3{ANG1goga7f4Sd`^KiK%xo(B}9YqIqsrCv>l-wGg5iS3f@uKX#y`L$DSLg1$67| z5@rVkAtL|CDZ-nZN(S&E;D@@w(-XBn%}7tt4qRaI$e>YS`5gzDWChsBLsxw`zq`4p z*gYM771rk^gSgSx+vOXU1@r@uepJyV=eehVjrLy)-<&)%s|OUq#_k~<-~b6DLE3Z6 zJHB*WyoJGbtBG~LS5v8_X>9g4T`!{0+VMQrWrrR56D0rpvrRZ7D4 zl#RKMxz@@b;vV9oy+o{H5r!x*YyaYJ$nea0796%(ajIP&3c;JyR-M8Rhj3vY^|9(>WSuxy0> zCbDw2dZ*#?gAN-(+FcV;aPMzr>Lwi|IxI`nE6Q%?~U$H8z}TZHS@K6W@N_7O*cEHvGZs40h-iR_ct63qVN zMIaE-Nvmbze)D=EU?fvYXY>|vLkObqCXbLFF(Mw{y1t&w8*5X|pCVM{hu=ab#a@_d zh~o<~irC+;C{!JzeytpJ)@So>ZrX%Hwzeiu1;P@S=LKnW!l}}~^{I1pfQ^}eR6szX zKGB3p%*}+ZADmSi%P{^ZR&k=bfR(4}Oojcl~ ze8p?d{QWA%7h=#7KR-#c`1vR&J9d|HT8PspOjh1BO#jr9p5rKV|4rrYM~BEz_KqXi zVfGg}$&w@2aLV^_nc{!g=@Sy4=x0>|RZWe}W5o_5uV9fao>Aj6q(Y9eH5x=qsan&< zPZ6gu{kH-KEcydjKhWF2hAICKUwlGONU76B10&GC)XH6=)e%klv)@J8pRZ^#ffo)( zcrWu%CpQ)r>Hhuw4mA4S4#h)Qelb1qDY^xJFra%)g0$G)eR@95 z*IgX_E{^|C4#K@rVcMLn-a*f| za;`F%9X&iD4|6IGFLZK$av^??>v!eh^`Em8eByttgjEYc;>QVqko;y74`~(a5~wwD z;fxxO@;ZelP|r028(IblR$7g+@SH0368Lif(&+za;kPYO(C_xHHf(dVtm2NmIN!PBVxy&O`Y`ReM zUPo+k$!c<{TZXs7fLcY}nt)1C$!f8GS^M@JhK`%3B?k7F6UUr%mEAW#2WkVS)pAMo|y_72NP9B z?A!HSyOCArvJdp}Dy}}0!Hqj;kmR@FOKz7Hnom|+R;|4bMY^6_GXj$KRBQ^bu04$d z50i_QHtqVz9{ty`rD2NT=KaA{A1dgBZ_LZMNl7eH)4DKavd)&^YMrm?(GThhQ7*Y5 z)_Yxsl7l(Oq&W0yWd0;R9Z2b@HAYbUbgPiv9t3Y>qsmJ{tZr=jMK+dHk9ZGc5%+SO z-k#WIR{5mE`U$Eus~(bmUJz!rb)?KpbsED#zjV`4_9DE`J@@BgE=-4I^%hiO$2j`7 z*apaAfe!pB)q@QJUX>H`-6A~_a38wo%ZS__en-45w7igrh5E~sj=9ZYj5dyYa_Pl9 zmb)t7Oq3R>Wl8em(}3GJdFodV+%@HSjtXYAS^V_oyxRGlrb2;OzNgd9kH!7Z+3;c0 zF!3sZH`cMVn1O$#L5b>|78-VJ{eD9)OA=GpQHA%EEP9ku#C>(>YA|=KYctocX(s33 zhuONq?oas;x9KKD)lx=DIq{70(xQVEd^bqx1M9rD0+S_rW#7h<;csisqmtLZTp?IJ3sEWrfkhFDP{DdkaBVP6vjfdm0DVo(SHX%X2JP-F`N z${MyLBD+Gf*aRg(HUk(m2ud)lvPPCbzX*2qRCU$NR8P(1pI5Ky-FMG<@7GW(Y$SN5|> z9FLOc4D7SA=v)eAEBI6)dcxCnM^`gv%lgGv@i@n0UHz*_j{yCS89(6^)#vc4-@-jF zqLeW$yfNXvAEN*pvgwt@yK)&A!9&^yb@c9s%l@FYME!{4zp9Cx z=eUkFxe*echu&#+&)T(CwZVIU*Q;rfSUa2M8B)YDkXdPkVl*Mf6;wfgtPk1!!9s5| zr^)ROQx5DWO4VCYM6vEB?mna&a}I|gIvu`EBU!yMDeDBvpL5DCCu(Zj4n^7>ZWSL= z;;9res9TgKc2B;9UWPq9umj5SeLj#nn=;XX3wd@^H{MIeNjhZO30AMSS58xVPmuqk zDGjxxCFG&Pqgr~r1#Gv!FGIS-8;hr1t#5W5U!dm(YuD%!ZvQbOSP(Q2KPx3DT|J9u z?8Tdu^k&l+jizK}+Cr4YGI>jlbQjwn3h{5#G5E_0JH4zx^sD8fJYN>`CP3pQg zW1HOs7JhdLSkao|EBlyph|@dHAPE{rhKDC@gV}qlwIhx1p2(&}XDS3MB)+Uu&k6xZ{gf)mko8-Tb@7sC8ik>csBF`iV^R@b(>D{FOOBcZNVRj3w>N?@ub;X=Qp56P4i zI0P^5|HJ6kiLl9?C>GgOvs|LTt?o6*gv25N)o|cR2cq_C@wy|`$9FJPVPsi3rgIFW zb*nJF-f!{K*sO4JEjS;_4pKXtXD)k1J-Sp+>=Hx&=FBosQChPQO?3mTyV7wWm0Q@Q zjUGS2X{*!rC{FfBAEi5Xr#*o=0TTad{ntNrKAE<&M7tW6F(V)vdu`cOZogF^qMH4? zD~HyV66%xel~C}30d&X24GSP?R3k6;t!@^O&DGDt-w5dYly}M3`g7Qb)xhoA1PbO{ z! z4#f6*g5Kck^uT2%`owz5dL@G5$36zA#BGChU1hAsWL!8|itM|&I-@nOO15Pb*h<}e z(cS)PwQ;K*+Z>>>G4@$^Zc&k!e%R+h0^CLI*LiT?Y-0W9#u|lNcb|^;=>hMY!TUv zFrZVbbL~R zO6eoA5!*?Rqgy0LQSg#`r1*=?Jau=A{6tTGF_Y_Fst}cTy%x1ic+1eymJqsD?FS#_ec{KtnchCgExi0HhtQ~tQ+xm%H1YwFj` zCc0LhOmv)tCJkcg2OUqUdcei5%!Ynhio8ro5j7y>h9Z6FDN*X?a z8T&j~*HQ5WE0;QLpsN>ypJ6AY7d=jk^wK2ae-8Or4pVcJhs!QEk z)YtHiW(4pk%PSNMl6W`}(p0h?8K^49n2>s*u)wLwzo7K0$c()WOU}1~J5+b>4tm@= z<0mVZo5Vg5FvWZ3qx6mwl-!j;q#!WG_D9WkLc3MG%?lu|B3CJ{J`h^cc*}xptHMmQ7`e12)=!?t%a6Epdb@PSH8%t}DmfQOIuoyTWP}+t zfz>(Fz$p`LEKcm3SEwgdS6B}~0%~_m`gECw7G|Em3ri6F^a$>`>ym<3C6rcSsKmU# z$8d9(0W31DH5+o%dQT0hhv+Q+AU-!k-+im(N!C{}$5*#1M~7vs(5g=axKS}WXx>%7 zLn2!?9H5dR4IhKv2&05rNWXp_ zN@y}lc*@uhXMMNX0zv4B_(PgXa{*a2 z@j=%w=YSaGd(=9G%48f2bmD8cJMyUOpeR+FA0>oa8~~C03z$NW3kduglH4j_kPgv@ z2EqW$!Dj|}AS{S@lAEN(-(to+NtQoUzVK1vs${Z|fZJ&*5Ulmjr{gx^&vybq|KDI6 z&=VH*DsdC$2z)hrlT85%lW6)UMzTpJ0o8;p-lS-N)XX^k|F~SH8+fUF-#0k*MpTr4 z&JSV!r^mk^quq$<{ud1TiwN;Q-SwNh|MK{aXZ?NOzR|FaC;Jx$^1JwVLfe0Kgg+qE rPJbuxi)nK0x>hXz4*$zvW6kLLb{NWKJ$B?Wjo-1!8Mrao&@KFT?Xg4J literal 0 HcmV?d00001 diff --git a/scripts/sequence/CodeMirrorMode.js b/scripts/sequence/CodeMirrorMode.js index 1ed55d4..258fbb4 100644 --- a/scripts/sequence/CodeMirrorMode.js +++ b/scripts/sequence/CodeMirrorMode.js @@ -1,183 +1,195 @@ define(['core/ArrayUtilities'], (array) => { 'use strict'; - const CM_END = {type: '', suggest: '\n', then: {}}; - const CM_HIDDEN_END = {type: '', then: {}}; const CM_ERROR = {type: 'error line-error', then: {'': 0}}; - function makeCMCommaBlock(type, suggest, exits = {}) { - return {type, suggest, then: Object.assign({ + const CM_COMMANDS = ((() => { + const end = {type: '', suggest: '\n', then: {}}; + const hiddenEnd = {type: '', then: {}}; + + const ARROWS = [ + '->', '-->', + '<-', '<--', + '<->', '<-->', + ]; + + const textToEnd = {type: 'string', then: {'': 0, '\n': end}}; + const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: { '': 0, - ',': {type: 'operator', suggest: true, then: { - '': 1, + 'as': {type: 'keyword', suggest: true, then: { + '': {type: 'variable', suggest: 'Agent', then: { + '': 0, + ',': {type: 'operator', suggest: true, then: {'': 3}}, + '\n': end, + }}, }}, - }, exits)}; - } - - const CM_TEXT_TO_END = {type: 'string', then: {'': 0, '\n': CM_END}}; - const CM_AGENT_LIST_TO_END = makeCMCommaBlock('variable', 'Agent', { - '\n': CM_END, - }); - const CM_AGENT_LIST_TO_TEXT = makeCMCommaBlock('variable', 'Agent', { - ':': {type: 'operator', suggest: true, then: {'': CM_TEXT_TO_END}}, - }); - const CM_AGENT_TO_OPTTEXT = {type: 'variable', suggest: 'Agent', then: { - '': 0, - ':': {type: 'operator', suggest: true, then: { - '': CM_TEXT_TO_END, - '\n': CM_HIDDEN_END, - }}, - '\n': CM_END, - }}; - - function makeCMSideNote(side) { - return { - type: 'keyword', - suggest: [side + ' of ', side + ': '], - then: { - 'of': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_TEXT, - }}, - ':': {type: 'operator', suggest: true, then: { - '': CM_TEXT_TO_END, - }}, - '': CM_AGENT_LIST_TO_TEXT, - }, - }; - } - - function makeCMOperatorBlock(exit) { - const op = {type: 'operator', suggest: true, then: { - '+': CM_ERROR, - '-': CM_ERROR, - '*': CM_ERROR, - '!': CM_ERROR, - '': exit, + ',': {type: 'operator', suggest: true, then: {'': 1}}, + '\n': end, }}; - return { - '+': {type: 'operator', suggest: true, then: { + const agentListToEnd = {type: 'variable', suggest: 'Agent', then: { + '': 0, + ',': {type: 'operator', suggest: true, then: {'': 1}}, + ':': {type: 'operator', suggest: true, then: {'': textToEnd}}, + }}; + const agentToOptText = {type: 'variable', suggest: 'Agent', then: { + '': 0, + ':': {type: 'operator', suggest: true, then: { + '': textToEnd, + '\n': hiddenEnd, + }}, + '\n': end, + }}; + + function makeSideNote(side) { + return { + type: 'keyword', + suggest: [side + ' of ', side + ': '], + then: { + 'of': {type: 'keyword', suggest: true, then: { + '': agentListToEnd, + }}, + ':': {type: 'operator', suggest: true, then: { + '': textToEnd, + }}, + '': agentListToEnd, + }, + }; + } + + function makeOpBlock(exit) { + const op = {type: 'operator', suggest: true, then: { '+': CM_ERROR, '-': CM_ERROR, - '*': op, + '*': CM_ERROR, '!': CM_ERROR, '': exit, - }}, - '-': {type: 'operator', suggest: true, then: { - '+': CM_ERROR, - '-': CM_ERROR, - '*': op, - '!': {type: 'operator', then: { + }}; + return { + '+': {type: 'operator', suggest: true, then: { '+': CM_ERROR, '-': CM_ERROR, + '*': op, + '!': CM_ERROR, + '': exit, + }}, + '-': {type: 'operator', suggest: true, then: { + '+': CM_ERROR, + '-': CM_ERROR, + '*': op, + '!': {type: 'operator', then: { + '+': CM_ERROR, + '-': CM_ERROR, + '*': CM_ERROR, + '!': CM_ERROR, + '': exit, + }}, + '': exit, + }}, + '*': {type: 'operator', suggest: true, then: { + '+': op, + '-': op, '*': CM_ERROR, '!': CM_ERROR, '': exit, }}, + '!': op, '': exit, - }}, - '*': {type: 'operator', suggest: true, then: { - '+': op, - '-': op, - '*': CM_ERROR, - '!': CM_ERROR, - '': exit, - }}, - '!': op, - '': exit, - }; - } + }; + } - function makeCMConnect() { - const connect = { - type: 'keyword', - suggest: true, - then: makeCMOperatorBlock(CM_AGENT_TO_OPTTEXT), - }; + function makeCMConnect() { + const connect = { + type: 'keyword', + suggest: true, + then: makeOpBlock(agentToOptText), + }; - return makeCMOperatorBlock({type: 'variable', suggest: 'Agent', then: { - '->': connect, - '-->': connect, - '<-': connect, - '<--': connect, - '<->': connect, - '<-->': connect, - ':': {type: 'operator', suggest: true, override: 'Label', then: {}}, - '': 0, - }}); - } + const then = { + ':': { + type: 'operator', + suggest: true, + override: 'Label', + then: {}, + }, + '': 0, + }; + ARROWS.forEach((arrow) => (then[arrow] = connect)); + return makeOpBlock({type: 'variable', suggest: 'Agent', then}); + } - const CM_COMMANDS = {type: 'error line-error', then: Object.assign({ - 'title': {type: 'keyword', suggest: true, then: { - '': CM_TEXT_TO_END, - }}, - 'terminators': {type: 'keyword', suggest: true, then: { - 'none': {type: 'keyword', suggest: true, then: {}}, - 'cross': {type: 'keyword', suggest: true, then: {}}, - 'box': {type: 'keyword', suggest: true, then: {}}, - 'bar': {type: 'keyword', suggest: true, then: {}}, - }}, - 'define': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_END, - }}, - 'begin': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_END, - }}, - 'end': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_END, - '\n': CM_END, - }}, - 'if': {type: 'keyword', suggest: true, then: { - '': CM_TEXT_TO_END, - ':': {type: 'operator', suggest: true, then: { - '': CM_TEXT_TO_END, + return {type: 'error line-error', then: Object.assign({ + 'title': {type: 'keyword', suggest: true, then: { + '': textToEnd, }}, - '\n': CM_END, - }}, - 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { - 'if': {type: 'keyword', suggest: 'if: ', then: { - '': CM_TEXT_TO_END, + 'terminators': {type: 'keyword', suggest: true, then: { + 'none': {type: 'keyword', suggest: true, then: {}}, + 'cross': {type: 'keyword', suggest: true, then: {}}, + 'box': {type: 'keyword', suggest: true, then: {}}, + 'bar': {type: 'keyword', suggest: true, then: {}}, + }}, + 'define': {type: 'keyword', suggest: true, then: { + '': aliasListToEnd, + }}, + 'begin': {type: 'keyword', suggest: true, then: { + '': aliasListToEnd, + }}, + 'end': {type: 'keyword', suggest: true, then: { + '': aliasListToEnd, + '\n': end, + }}, + 'if': {type: 'keyword', suggest: true, then: { + '': textToEnd, ':': {type: 'operator', suggest: true, then: { - '': CM_TEXT_TO_END, + '': textToEnd, + }}, + '\n': end, + }}, + 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { + 'if': {type: 'keyword', suggest: 'if: ', then: { + '': textToEnd, + ':': {type: 'operator', suggest: true, then: { + '': textToEnd, + }}, + }}, + '\n': end, + }}, + 'repeat': {type: 'keyword', suggest: true, then: { + '': textToEnd, + ':': {type: 'operator', suggest: true, then: { + '': textToEnd, + }}, + '\n': end, + }}, + 'note': {type: 'keyword', suggest: true, then: { + 'over': {type: 'keyword', suggest: true, then: { + '': agentListToEnd, + }}, + 'left': makeSideNote('left'), + 'right': makeSideNote('right'), + 'between': {type: 'keyword', suggest: true, then: { + '': agentListToEnd, }}, }}, - '\n': CM_END, - }}, - 'repeat': {type: 'keyword', suggest: true, then: { - '': CM_TEXT_TO_END, - ':': {type: 'operator', suggest: true, then: { - '': CM_TEXT_TO_END, - }}, - '\n': CM_END, - }}, - 'note': {type: 'keyword', suggest: true, then: { - 'over': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_TEXT, - }}, - 'left': makeCMSideNote('left'), - 'right': makeCMSideNote('right'), - 'between': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_TEXT, - }}, - }}, - 'state': {type: 'keyword', suggest: 'state over ', then: { - 'over': {type: 'keyword', suggest: true, then: { - '': CM_AGENT_LIST_TO_TEXT, - }}, - }}, - 'text': {type: 'keyword', suggest: true, then: { - 'left': makeCMSideNote('left'), - 'right': makeCMSideNote('right'), - }}, - 'simultaneously': {type: 'keyword', suggest: true, then: { - ':': {type: 'operator', suggest: true, then: {}}, - 'with': {type: 'keyword', suggest: true, then: { - '': {type: 'variable', suggest: 'Label', then: { - '': 0, - ':': {type: 'operator', suggest: true, then: {}}, + 'state': {type: 'keyword', suggest: 'state over ', then: { + 'over': {type: 'keyword', suggest: true, then: { + '': agentListToEnd, }}, }}, - }}, - }, makeCMConnect())}; + 'text': {type: 'keyword', suggest: true, then: { + 'left': makeSideNote('left'), + 'right': makeSideNote('right'), + }}, + 'simultaneously': {type: 'keyword', suggest: true, then: { + ':': {type: 'operator', suggest: true, then: {}}, + 'with': {type: 'keyword', suggest: true, then: { + '': {type: 'variable', suggest: 'Label', then: { + '': 0, + ':': {type: 'operator', suggest: true, then: {}}, + }}, + }}, + }}, + }, makeCMConnect())}; + })()); function cmCappedToken(token, current) { if(Object.keys(current.then).length > 0) { diff --git a/scripts/sequence/Generator.js b/scripts/sequence/Generator.js index 791c301..b1bea09 100644 --- a/scripts/sequence/Generator.js +++ b/scripts/sequence/Generator.js @@ -17,10 +17,6 @@ define(['core/ArrayUtilities'], (array) => { return {name, anchorRight}; } - function convertAgent(agent) { - return makeAgent(agent.name); - } - function getAgentName(agent) { return agent.name; } @@ -181,6 +177,7 @@ define(['core/ArrayUtilities'], (array) => { return class Generator { constructor() { this.agentStates = new Map(); + this.agentAliases = new Map(); this.agents = []; this.blockCount = 0; this.nesting = []; @@ -204,6 +201,29 @@ define(['core/ArrayUtilities'], (array) => { 'block end': this.handleBlockEnd.bind(this), }; this.handleStage = this.handleStage.bind(this); + this.convertAgent = this.convertAgent.bind(this); + } + + convertAgent({alias, name}) { + if(alias) { + if(this.agentAliases.has(name)) { + throw new Error( + 'Cannot alias ' + name + '; it is already an alias' + ); + } + const old = this.agentAliases.get(alias); + if( + (old && old !== alias) || + this.agents.some((agent) => (agent.name === alias)) + ) { + throw new Error( + 'Cannot use ' + alias + + ' as an alias; it is already in use' + ); + } + this.agentAliases.set(alias, name); + } + return makeAgent(this.agentAliases.get(name) || name); } addStage(stage, isVisible = true) { @@ -230,13 +250,18 @@ define(['core/ArrayUtilities'], (array) => { }); } - defineAgents(agents) { - array.mergeSets(this.currentNest.agents, agents, agentEqCheck); - array.mergeSets(this.agents, agents, agentEqCheck); + defineAgents(colAgents) { + array.mergeSets(this.currentNest.agents, colAgents, agentEqCheck); + array.mergeSets(this.agents, colAgents, agentEqCheck); } - setAgentVisRaw(agents, visible, mode, checked = false) { - const filteredAgents = agents.filter((agent) => { + setAgentVis(colAgents, visible, mode, checked = false) { + const seen = new Set(); + const filteredAgents = colAgents.filter((agent) => { + if(seen.has(agent.name)) { + return false; + } + seen.add(agent.name); const state = this.agentStates.get(agent.name) || DEFAULT_AGENT; if(state.locked) { if(checked) { @@ -269,17 +294,8 @@ define(['core/ArrayUtilities'], (array) => { }; } - setAgentVis(agents, visible, mode, checked = false) { - return this.setAgentVisRaw( - agents.map(convertAgent), - visible, - mode, - checked - ); - } - - setAgentHighlight(agents, highlighted, checked = false) { - const filteredAgents = agents.filter((agent) => { + setAgentHighlight(colAgents, highlighted, checked = false) { + const filteredAgents = colAgents.filter((agent) => { const state = this.agentStates.get(agent.name) || DEFAULT_AGENT; if(state.locked) { if(checked) { @@ -349,27 +365,44 @@ define(['core/ArrayUtilities'], (array) => { } handleConnect({agents, label, options}) { - const beginAgents = agents.filter(agentHasFlag('begin')); - const endAgents = agents.filter(agentHasFlag('end')); + const beginAgents = (agents + .filter(agentHasFlag('begin')) + .map(this.convertAgent) + ); + const endAgents = (agents + .filter(agentHasFlag('end')) + .map(this.convertAgent) + ); if(array.hasIntersection(beginAgents, endAgents, agentEqCheck)) { throw new Error('Cannot set agent visibility multiple times'); } - const startAgents = agents.filter(agentHasFlag('start')); - const stopAgents = agents.filter(agentHasFlag('stop')); + const startAgents = (agents + .filter(agentHasFlag('start')) + .map(this.convertAgent) + ); + const stopAgents = (agents + .filter(agentHasFlag('stop')) + .map(this.convertAgent) + ); array.mergeSets(stopAgents, endAgents); if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) { throw new Error('Cannot set agent highlighting multiple times'); } - this.defineAgents(agents.map(convertAgent)); + const colAgents = agents.map(this.convertAgent); + const agentNames = colAgents.map(getAgentName); + this.defineAgents(colAgents); - const implicitBegin = agents.filter(agentHasFlag('begin', false)); + const implicitBegin = (agents + .filter(agentHasFlag('begin', false)) + .map(this.convertAgent) + ); this.addStage(this.setAgentVis(implicitBegin, true, 'box')); const connectStage = { type: 'connect', - agentNames: agents.map(getAgentName), + agentNames, label, options, }; @@ -388,32 +421,39 @@ define(['core/ArrayUtilities'], (array) => { if(agents.length === 0) { colAgents = NOTE_DEFAULT_AGENTS[type] || []; } else { - colAgents = agents.map(convertAgent); + colAgents = agents.map(this.convertAgent); + } + const agentNames = colAgents.map(getAgentName); + const uniqueAgents = new Set(agentNames).size; + if(type === 'note between' && uniqueAgents < 2) { + throw new Error('note between requires at least 2 agents'); } - this.addStage(this.setAgentVisRaw(colAgents, true, 'box')); + this.addStage(this.setAgentVis(colAgents, true, 'box')); this.defineAgents(colAgents); this.addStage({ type, - agentNames: colAgents.map(getAgentName), + agentNames, mode, label, }); } handleAgentDefine({agents}) { - this.defineAgents(agents.map(convertAgent)); + this.defineAgents(agents.map(this.convertAgent)); } handleAgentBegin({agents, mode}) { - this.addStage(this.setAgentVis(agents, true, mode, true)); + const colAgents = agents.map(this.convertAgent); + this.addStage(this.setAgentVis(colAgents, true, mode, true)); } handleAgentEnd({agents, mode}) { + const colAgents = agents.map(this.convertAgent); this.addParallelStages([ - this.setAgentHighlight(agents, false), - this.setAgentVis(agents, false, mode, true), + this.setAgentHighlight(colAgents, false), + this.setAgentVis(colAgents, false, mode, true), ]); } @@ -467,6 +507,7 @@ define(['core/ArrayUtilities'], (array) => { generate({stages, meta = {}}) { this.agentStates.clear(); this.markers.clear(); + this.agentAliases.clear(); this.agents.length = 0; this.blockCount = 0; this.nesting.length = 0; @@ -485,7 +526,7 @@ define(['core/ArrayUtilities'], (array) => { this.addParallelStages([ this.setAgentHighlight(this.agents, false), - this.setAgentVisRaw(this.agents, false, terminators), + this.setAgentVis(this.agents, false, terminators), ]); addBounds( diff --git a/scripts/sequence/Generator_spec.js b/scripts/sequence/Generator_spec.js index 4f4ed59..b5701f4 100644 --- a/scripts/sequence/Generator_spec.js +++ b/scripts/sequence/Generator_spec.js @@ -8,7 +8,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { if(typeof item === 'object') { return item; } else { - return {name: item, flags: []}; + return {name: item, alias: '', flags: []}; } }); } @@ -231,6 +231,33 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { ]); }); + it('converts aliases', () => { + const sequence = generator.generate({stages: [ + PARSED.defineAgents([{name: 'Baz', alias: 'B', flags: []}]), + PARSED.connect(['A', 'B']), + ]}); + expect(sequence.agents).toEqual([ + {name: '[', anchorRight: true}, + {name: 'Baz', anchorRight: false}, + {name: 'A', anchorRight: false}, + {name: ']', anchorRight: false}, + ]); + }); + + it('rejects duplicate aliases', () => { + expect(() => generator.generate({stages: [ + PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]), + PARSED.defineAgents([{name: 'Bar', alias: 'B', flags: []}]), + ]})).toThrow(); + }); + + it('rejects using agent names as aliases', () => { + expect(() => generator.generate({stages: [ + PARSED.defineAgents([{name: 'Foo', alias: 'B', flags: []}]), + PARSED.defineAgents([{name: 'Bar', alias: 'Foo', flags: []}]), + ]})).toThrow(); + }); + it('creates implicit begin stages for agents when used', () => { const sequence = generator.generate({stages: [ PARSED.connect(['A', 'B']), @@ -347,6 +374,16 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { ]); }); + it('removes duplicate begin agents', () => { + const sequence = generator.generate({stages: [ + PARSED.beginAgents(['A', 'A']), + ]}); + expect(sequence.stages).toEqual([ + GENERATED.beginAgents(['A']), + jasmine.anything(), + ]); + }); + it('collapses adjacent begin statements', () => { const sequence = generator.generate({stages: [ PARSED.connect(['A', 'B']), @@ -378,6 +415,17 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { ]); }); + it('removes duplicate end agents', () => { + const sequence = generator.generate({stages: [ + PARSED.beginAgents(['A']), + PARSED.endAgents(['A', 'A']), + ]}); + expect(sequence.stages).toEqual([ + jasmine.anything(), + GENERATED.endAgents(['A']), + ]); + }); + it('removes superfluous end statements', () => { const sequence = generator.generate({stages: [ PARSED.defineAgents(['E']), @@ -409,8 +457,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('adds parallel highlighting stages', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['start']}]), - PARSED.connect(['A', {name: 'B', flags: ['stop']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['stop']}]), ]}); expect(sequence.stages).toEqual([ jasmine.anything(), @@ -428,7 +476,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('adds parallel begin stages', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['begin']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['begin']}]), ]}); expect(sequence.stages).toEqual([ GENERATED.beginAgents(['A']), @@ -442,7 +490,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('adds parallel end stages', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['end']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['end']}]), ]}); expect(sequence.stages).toEqual([ GENERATED.beginAgents(['A', 'B']), @@ -456,8 +504,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('implicitly ends highlighting when ending a stage', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['start']}]), - PARSED.connect(['A', {name: 'B', flags: ['end']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['end']}]), ]}); expect(sequence.stages).toEqual([ jasmine.anything(), @@ -472,32 +520,41 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { }); it('rejects conflicting flags', () => { - expect(() => generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['start', 'stop']}]), - ]})).toThrow(); - expect(() => generator.generate({stages: [ PARSED.connect([ - {name: 'A', flags: ['start']}, - {name: 'A', flags: ['stop']}, + 'A', + {name: 'B', alias: '', flags: ['start', 'stop']}, ]), ]})).toThrow(); expect(() => generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['begin', 'end']}]), + PARSED.connect([ + {name: 'A', alias: '', flags: ['start']}, + {name: 'A', alias: '', flags: ['stop']}, + ]), ]})).toThrow(); expect(() => generator.generate({stages: [ PARSED.connect([ - {name: 'A', flags: ['begin']}, - {name: 'A', flags: ['end']}, + 'A', + {name: 'B', alias: '', flags: ['begin', 'end']}, + ]), + ]})).toThrow(); + + expect(() => generator.generate({stages: [ + PARSED.connect([ + {name: 'A', alias: '', flags: ['begin']}, + {name: 'A', alias: '', flags: ['end']}, ]), ]})).toThrow(); }); it('adds implicit highlight end with implicit terminator', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['start']}]), + PARSED.connect([ + 'A', + {name: 'B', alias: '', flags: ['start']}, + ]), ]}); expect(sequence.stages).toEqual([ jasmine.anything(), @@ -511,7 +568,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('adds implicit highlight end with explicit terminator', () => { const sequence = generator.generate({stages: [ - PARSED.connect(['A', {name: 'B', flags: ['start']}]), + PARSED.connect(['A', {name: 'B', alias: '', flags: ['start']}]), PARSED.endAgents(['A', 'B']), ]}); expect(sequence.stages).toEqual([ @@ -527,8 +584,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { it('collapses adjacent end statements containing highlighting', () => { const sequence = generator.generate({stages: [ PARSED.connect([ - {name: 'A', flags: ['start']}, - {name: 'B', flags: ['start']}, + {name: 'A', alias: '', flags: ['start']}, + {name: 'B', alias: '', flags: ['start']}, ]), PARSED.endAgents(['A']), PARSED.endAgents(['B']), @@ -849,6 +906,15 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => { ]); }); + it('rejects note between with a repeated agent', () => { + expect(() => generator.generate({stages: [ + PARSED.note('note between', ['A', 'A'], { + mode: 'foo', + label: 'bar', + }), + ]})).toThrow(); + }); + it('defaults to showing notes around the entire diagram', () => { const sequence = generator.generate({stages: [ PARSED.note('note right', []), diff --git a/scripts/sequence/Parser.js b/scripts/sequence/Parser.js index 2aaaf69..1992ec9 100644 --- a/scripts/sequence/Parser.js +++ b/scripts/sequence/Parser.js @@ -114,7 +114,27 @@ define([ return -1; } - function readAgent(line, start, end, flagTypes = {}) { + function readAgentAlias(line, start, end, enableAlias) { + let aliasSep = -1; + if(enableAlias) { + aliasSep = findToken(line, 'as', start); + } + if(aliasSep === -1 || aliasSep >= end) { + aliasSep = end; + } + if(start >= aliasSep) { + throw new Error('Missing agent name'); + } + return { + name: joinLabel(line, start, aliasSep), + alias: joinLabel(line, aliasSep + 1, end), + }; + } + + function readAgent(line, start, end, { + flagTypes = {}, + aliases = false, + } = {}) { const flags = []; let p = start; for(; p < end; ++ p) { @@ -129,23 +149,22 @@ define([ break; } } - if(p >= end) { - throw new Error('Missing agent name'); - } + const {name, alias} = readAgentAlias(line, p, end, aliases); return { - name: joinLabel(line, p, end), + name, + alias, flags, }; } - function readAgentList(line, start, end, flagTypes) { + function readAgentList(line, start, end, readAgentOpts) { const list = []; let currentStart = -1; for(let i = start; i < end; ++ i) { const token = line[i]; if(tokenKeyword(token) === ',') { if(currentStart !== -1) { - list.push(readAgent(line, currentStart, i, flagTypes)); + list.push(readAgent(line, currentStart, i, readAgentOpts)); currentStart = -1; } } else if(currentStart === -1) { @@ -153,7 +172,7 @@ define([ } } if(currentStart !== -1) { - list.push(readAgent(line, currentStart, end, flagTypes)); + list.push(readAgent(line, currentStart, end, readAgentOpts)); } return list; } @@ -208,7 +227,7 @@ define([ return null; } return Object.assign({ - agents: readAgentList(line, 1, line.length), + agents: readAgentList(line, 1, line.length, {aliases: true}), }, type); }, @@ -280,11 +299,14 @@ define([ if(typePos <= 0 || typePos >= labelSep - 1) { return null; } + const readAgentOpts = { + flagTypes: CONNECT_AGENT_FLAGS, + }; return { type: 'connect', agents: [ - readAgent(line, 0, typePos, CONNECT_AGENT_FLAGS), - readAgent(line, typePos + 1, labelSep, CONNECT_AGENT_FLAGS), + readAgent(line, 0, typePos, readAgentOpts), + readAgent(line, typePos + 1, labelSep, readAgentOpts), ], label: joinLabel(line, labelSep + 1), options, diff --git a/scripts/sequence/Parser_spec.js b/scripts/sequence/Parser_spec.js index 503428d..fdc6171 100644 --- a/scripts/sequence/Parser_spec.js +++ b/scripts/sequence/Parser_spec.js @@ -12,7 +12,11 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { } = {}) => { return { type: 'connect', - agents: agentNames.map((name) => ({name, flags: []})), + agents: agentNames.map((name) => ({ + name, + alias: '', + flags: [], + })), label, options: { line, @@ -64,6 +68,15 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { ]); }); + it('propagates aliases', () => { + const parsed = parser.parse('define Foo Bar as A B'); + expect(parsed.stages).toEqual([ + {type: 'agent define', agents: [ + {name: 'Foo Bar', alias: 'A B', flags: []}, + ]}, + ]); + }); + it('respects spacing within agent names', () => { const parsed = parser.parse('A+B -> C D'); expect(parsed.stages).toEqual([ @@ -84,8 +97,12 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { { type: 'connect', agents: [ - {name: 'A', flags: ['start']}, - {name: 'B', flags: ['stop', 'begin', 'end']}, + {name: 'A', alias: '', flags: ['start']}, + {name: 'B', alias: '', flags: [ + 'stop', + 'begin', + 'end', + ]}, ], label: jasmine.anything(), options: jasmine.anything(), @@ -192,7 +209,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { const parsed = parser.parse('note over A: hello there'); expect(parsed.stages).toEqual([{ type: 'note over', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'note', label: 'hello there', }]); @@ -209,31 +226,34 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { expect(parsed.stages).toEqual([ { type: 'note left', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'note', label: 'hello there', }, { type: 'note left', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'note', label: 'hello there', }, { type: 'note right', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'note', label: 'hello there', }, { type: 'note right', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'note', label: 'hello there', }, { type: 'note between', - agents: [{name: 'A', flags: []}, {name: 'B', flags: []}], + agents: [ + {name: 'A', alias: '', flags: []}, + {name: 'B', alias: '', flags: []}, + ], mode: 'note', label: 'hi', }, @@ -244,7 +264,10 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { const parsed = parser.parse('note over A B, C D: hi'); expect(parsed.stages).toEqual([{ type: 'note over', - agents: [{name: 'A B', flags: []}, {name: 'C D', flags: []}], + agents: [ + {name: 'A B', alias: '', flags: []}, + {name: 'C D', alias: '', flags: []}, + ], mode: 'note', label: 'hi', }]); @@ -258,7 +281,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { const parsed = parser.parse('state over A: doing stuff'); expect(parsed.stages).toEqual([{ type: 'note over', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'state', label: 'doing stuff', }]); @@ -272,7 +295,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { const parsed = parser.parse('text right of A: doing stuff'); expect(parsed.stages).toEqual([{ type: 'note right', - agents: [{name: 'A', flags: []}], + agents: [{name: 'A', alias: '', flags: []}], mode: 'text', label: 'doing stuff', }]); @@ -287,16 +310,25 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => { expect(parsed.stages).toEqual([ { type: 'agent define', - agents: [{name: 'A', flags: []}, {name: 'B', flags: []}], + agents: [ + {name: 'A', alias: '', flags: []}, + {name: 'B', alias: '', flags: []}, + ], }, { type: 'agent begin', - agents: [{name: 'A', flags: []}, {name: 'B', flags: []}], + agents: [ + {name: 'A', alias: '', flags: []}, + {name: 'B', alias: '', flags: []}, + ], mode: 'box', }, { type: 'agent end', - agents: [{name: 'A', flags: []}, {name: 'B', flags: []}], + agents: [ + {name: 'A', alias: '', flags: []}, + {name: 'B', alias: '', flags: []}, + ], mode: 'cross', }, ]); diff --git a/scripts/sequence/components/Note.js b/scripts/sequence/components/Note.js index bc705f9..742c8e1 100644 --- a/scripts/sequence/components/Note.js +++ b/scripts/sequence/components/Note.js @@ -103,11 +103,10 @@ define(['./BaseComponent'], (BaseComponent) => { config.padding.right ); - if(agentNames.length > 1) { - const {left, right} = findExtremes(env.agentInfos, agentNames); - const infoL = env.agentInfos.get(left); - const infoR = env.agentInfos.get(right); - + const {left, right} = findExtremes(env.agentInfos, agentNames); + const infoL = env.agentInfos.get(left); + const infoR = env.agentInfos.get(right); + if(infoL !== infoR) { const hangL = infoL.currentMaxRad + config.overlap.left; const hangR = infoR.currentMaxRad + config.overlap.right; @@ -116,7 +115,7 @@ define(['./BaseComponent'], (BaseComponent) => { env.addSpacing(left, {left: hangL, right: 0}); env.addSpacing(right, {left: 0, right: hangR}); } else { - env.addSpacing(agentNames[0], { + env.addSpacing(left, { left: width / 2, right: width / 2, }); @@ -126,10 +125,10 @@ define(['./BaseComponent'], (BaseComponent) => { render({agentNames, mode, label}, env) { const config = env.theme.note[mode]; - if(agentNames.length > 1) { - const {left, right} = findExtremes(env.agentInfos, agentNames); - const infoL = env.agentInfos.get(left); - const infoR = env.agentInfos.get(right); + const {left, right} = findExtremes(env.agentInfos, agentNames); + const infoL = env.agentInfos.get(left); + const infoR = env.agentInfos.get(right); + if(infoL !== infoR) { return this.renderNote({ x0: infoL.x - infoL.currentMaxRad - config.overlap.left, x1: infoR.x + infoR.currentMaxRad + config.overlap.right, @@ -138,7 +137,7 @@ define(['./BaseComponent'], (BaseComponent) => { label, }, env); } else { - const xMid = env.agentInfos.get(agentNames[0]).x; + const xMid = infoL.x; return this.renderNote({ xMid, anchor: 'middle', diff --git a/scripts/tester/jshintRunner.js b/scripts/tester/jshintRunner.js index 4306364..a3331ac 100644 --- a/scripts/tester/jshintRunner.js +++ b/scripts/tester/jshintRunner.js @@ -34,7 +34,7 @@ define(['jshintConfig', 'specs'], (jshintConfig) => { const OPTS_TEST = Object.assign({}, jshintConfig, { predef: PREDEF_TEST, - maxstatements: 50, // allow lots of tests + maxstatements: 100, // allow lots of tests }); function formatError(error) { diff --git a/styles/main.css b/styles/main.css index 36e38a0..9f20cbb 100644 --- a/styles/main.css +++ b/styles/main.css @@ -88,6 +88,7 @@ html, body { background: #FFFFFF; font-family: sans-serif; overflow: hidden; + user-select: none; } .options.links {