From a5f8d6b0b36ea82fbbd7ba7dab1a95cf5d8ece1c Mon Sep 17 00:00:00 2001 From: edwardalee Date: Thu, 19 Oct 2023 17:30:08 +0000 Subject: [PATCH] deploy: c00f2781f1cc9ec62c211a2873a1d474eac1165d --- assets/lingua-franca-handbook.epub | Bin 255364 -> 255365 bytes assets/lingua-franca-handbook_lf-c.pdf | Bin 718028 -> 718027 bytes assets/lingua-franca-handbook_lf-cpp.pdf | Bin 652622 -> 652621 bytes assets/lingua-franca-handbook_lf-py.pdf | Bin 747894 -> 747893 bytes assets/lingua-franca-handbook_lf-rs.pdf | Bin 649723 -> 649721 bytes assets/lingua-franca-handbook_lf-ts.pdf | Bin 808250 -> 808249 bytes chunk-map.json | 2 +- ...9a85.js => commons-b82d559d459fdf48f9b7.js | 6 +++--- ...ommons-b82d559d459fdf48f9b7.js.LICENSE.txt | 0 ...map => commons-b82d559d459fdf48f9b7.js.map | 2 +- community/index.html | 2 +- ...pages-download-tsx-0fc7218df25b40189519.js | 4 ++-- ...s-download-tsx-0fc7218df25b40189519.js.map | 1 + ...s-download-tsx-581d01e80dae64748c16.js.map | 1 - docs/handbook/a-first-reactor/index.html | 2 +- docs/handbook/actions/index.html | 2 +- docs/handbook/arduino/index.html | 2 +- docs/handbook/causality-loops/index.html | 2 +- docs/handbook/code-extension/index.html | 2 +- docs/handbook/command-line-tools/index.html | 2 +- docs/handbook/composing-reactors/index.html | 2 +- .../containerized-execution/index.html | 2 +- docs/handbook/contributing/index.html | 2 +- docs/handbook/deadlines/index.html | 2 +- docs/handbook/developer-setup/index.html | 2 +- .../handbook/distributed-execution/index.html | 2 +- docs/handbook/eclipse-oomph/index.html | 2 +- docs/handbook/epoch-ide/index.html | 2 +- docs/handbook/expressions/index.html | 2 +- docs/handbook/extending-reactors/index.html | 2 +- docs/handbook/features/index.html | 2 +- .../index.html | 2 +- docs/handbook/generics/index.html | 2 +- docs/handbook/import-system/index.html | 2 +- docs/handbook/index.html | 2 +- docs/handbook/inputs-and-outputs/index.html | 2 +- docs/handbook/intellij/index.html | 2 +- .../language-specification/index.html | 2 +- .../logical-execution-time/index.html | 2 +- docs/handbook/methods/index.html | 2 +- docs/handbook/modal-models/index.html | 2 +- docs/handbook/multiports-and-banks/index.html | 2 +- docs/handbook/overview/index.html | 2 +- .../parameters-and-state-variables/index.html | 2 +- docs/handbook/preambles/index.html | 2 +- docs/handbook/proof-import/index.html | 2 +- .../handbook/reaction-declarations/index.html | 2 +- docs/handbook/reactions/index.html | 2 +- docs/handbook/reactors-on-patmos/index.html | 2 +- docs/handbook/regression-tests/index.html | 2 +- docs/handbook/related-work/index.html | 2 +- docs/handbook/running-benchmarks/index.html | 2 +- docs/handbook/security/index.html | 2 +- docs/handbook/superdense-time/index.html | 2 +- docs/handbook/target-declaration/index.html | 2 +- .../target-language-details/index.html | 2 +- docs/handbook/termination/index.html | 2 +- docs/handbook/time-and-timers/index.html | 2 +- docs/handbook/timing-analysis/index.html | 2 +- docs/handbook/tools/index.html | 2 +- docs/handbook/tracing/index.html | 2 +- docs/handbook/troubleshooting/index.html | 2 +- docs/handbook/tutorial-video/index.html | 2 +- docs/handbook/website-development/index.html | 2 +- docs/handbook/zephyr/index.html | 2 +- docs/index.html | 2 +- download/index.html | 2 +- empty/index.html | 2 +- index.html | 2 +- page-data/app-data.json | 2 +- .../handbook/a-first-reactor/page-data.json | 2 +- .../docs/handbook/actions/page-data.json | 2 +- .../docs/handbook/arduino/page-data.json | 2 +- .../handbook/causality-loops/page-data.json | 2 +- .../handbook/code-extension/page-data.json | 2 +- .../command-line-tools/page-data.json | 2 +- .../composing-reactors/page-data.json | 2 +- .../containerized-execution/page-data.json | 2 +- .../docs/handbook/contributing/page-data.json | 2 +- .../docs/handbook/deadlines/page-data.json | 2 +- .../handbook/developer-setup/page-data.json | 2 +- .../distributed-execution/page-data.json | 2 +- .../handbook/eclipse-oomph/page-data.json | 2 +- .../docs/handbook/epoch-ide/page-data.json | 2 +- .../docs/handbook/expressions/page-data.json | 2 +- .../extending-reactors/page-data.json | 2 +- .../docs/handbook/features/page-data.json | 2 +- .../page-data.json | 2 +- .../docs/handbook/generics/page-data.json | 2 +- .../handbook/import-system/page-data.json | 2 +- .../inputs-and-outputs/page-data.json | 2 +- .../docs/handbook/intellij/page-data.json | 2 +- .../language-specification/page-data.json | 2 +- .../logical-execution-time/page-data.json | 2 +- .../docs/handbook/methods/page-data.json | 2 +- .../docs/handbook/modal-models/page-data.json | 2 +- .../multiports-and-banks/page-data.json | 2 +- .../docs/handbook/overview/page-data.json | 2 +- .../page-data.json | 2 +- .../docs/handbook/preambles/page-data.json | 2 +- .../docs/handbook/proof-import/page-data.json | 2 +- .../reaction-declarations/page-data.json | 2 +- .../docs/handbook/reactions/page-data.json | 2 +- .../reactors-on-patmos/page-data.json | 2 +- .../handbook/regression-tests/page-data.json | 2 +- .../docs/handbook/related-work/page-data.json | 2 +- .../running-benchmarks/page-data.json | 2 +- .../docs/handbook/security/page-data.json | 2 +- .../handbook/superdense-time/page-data.json | 2 +- .../target-declaration/page-data.json | 2 +- .../target-language-details/page-data.json | 2 +- .../docs/handbook/termination/page-data.json | 2 +- .../handbook/time-and-timers/page-data.json | 2 +- .../handbook/timing-analysis/page-data.json | 2 +- page-data/docs/handbook/tools/page-data.json | 2 +- .../docs/handbook/tracing/page-data.json | 2 +- .../handbook/troubleshooting/page-data.json | 2 +- .../handbook/tutorial-video/page-data.json | 2 +- .../website-development/page-data.json | 2 +- page-data/docs/handbook/zephyr/page-data.json | 2 +- publications-and-presentations/index.html | 2 +- sitemap/sitemap-0.xml | 2 +- ...=> webpack-runtime-587676fc722d4109d293.js | 4 ++-- ...ebpack-runtime-587676fc722d4109d293.js.map | 2 +- webpack.stats.json | 2 +- 125 files changed, 121 insertions(+), 121 deletions(-) rename commons-530c9aa67c6d41969a85.js => commons-b82d559d459fdf48f9b7.js (99%) rename commons-530c9aa67c6d41969a85.js.LICENSE.txt => commons-b82d559d459fdf48f9b7.js.LICENSE.txt (100%) rename commons-530c9aa67c6d41969a85.js.map => commons-b82d559d459fdf48f9b7.js.map (99%) rename component---src-templates-pages-download-tsx-581d01e80dae64748c16.js => component---src-templates-pages-download-tsx-0fc7218df25b40189519.js (52%) create mode 100644 component---src-templates-pages-download-tsx-0fc7218df25b40189519.js.map delete mode 100644 component---src-templates-pages-download-tsx-581d01e80dae64748c16.js.map rename webpack-runtime-6b3dd33249beab12ce1c.js => webpack-runtime-587676fc722d4109d293.js (96%) rename webpack-runtime-6b3dd33249beab12ce1c.js.map => webpack-runtime-587676fc722d4109d293.js.map (99%) diff --git a/assets/lingua-franca-handbook.epub b/assets/lingua-franca-handbook.epub index 50bb85e4cb0dca7bf6c5ebb0cba344c41bd164ea..891bfd4c17026e001c9cfc7006997d461ae2500f 100644 GIT binary patch delta 2425 zcmZuz3p|urAD)@h6fwqaDEeG>*-S)j5m_mXm?<@KNo7T%i%a`eDBX}@Vv*d!7z{!&@>_)L`x0OK+izxm=e*DJf6nus_niOxKXZ{!4CfQ2={vy^ zMT`XUjgO9(rs0s*X#i=VkONkPIb$Q8u~@D%7MEm?k^@aZ1%HD2|u&|ue)Nqs{%VXQTHYhNDO>1I&{CA@$4 zqv!$!7ixC1`}4l-k=Cj9`bMQ9rPR2CNbmD zM@dpUAmKvNqn15(r%eCO%uy3@HW^$=h}y6=jM~&d8Y0is_`V7o%Q3o^&F@^#Xp$O= zuaa-k40|g--9!}nCbqW;O#icW|E4gX3!5)iD;pi^?+VcMGIvdg@3ob0bhUYE^o7o! zR69?8ND4B(F`Uuz0eeKZKr(+&*Hrq;(o;p_Og7_Esjnf!J&}^ozen}iM*Ce(cpKhS zflNm>Lt^xj#jWF~gJRyss>|^SqQm&2IFHd;Rbr&G-qp)&5lCcW={uK6V31Ef7Hu(@ z@6%&$XSw)tT;KA=eKzO>%ebUkoW&x=4Q$0pN-hYvncX-q1q5-Cv|vjw(r*B`NJ0^k zW*YFk5J+%?R)SId0>zC{2g6Gi5l0K;{15q(fsx`u490XJgKNw{^4v{PKkg+f@KJ6- zz=it(iLi)jJi&go1q5$&BZF^72}(Oq&@??_4&*UhU=Kes>a7_q%%Nc-m9_-e`z4hgg&ra)7> zC5DxK`Cau7f~V}jYr@DTj*J7<<;7-?v53*~1o_(f9@?@~R=wfuQoRByR;o)66}R!XEw z@YNhBzG&_JIkaFz$_clOxcNw*JS(Km`_~ZVqLK7!&qThR1;d@6yi0#2i=Kvw5#x!D zMb6hrnrr0DqY~2U{@9~oy;q4!9@Cf4f$A+lm&t6abq>l+Jm^qZ)-RDkgC87TK}qIB z)v)7-nSp^XQz`gI{T1xjG~W3Y{*n73&HSp_{S&$kX9H+@toDRq8@yp}SbCnT$_!QN zRjQ+L-9$;=nK+Ta@Yv{{Gt@1rA9GrbjtLLzTn~i)I_sRL2s5`bis`RXw zyRCi%$Ccs;mPYn#exu4cX}dP8(pkQEhQBp+qoSX=!tOT|xf^u_6yx660_iE|1t z*?aa!<;ao;RdzXdb1mcoD^(x=sBLM@ww!)Y$bZ_Me)S=v{&jQRtaNHpMyHaAj@gr9^60)_EQ$ys)}3Av0}b3b*{HteYg`_KY6af?(sOR`1Ez}pg(bB-SjqZ zv)IoSBU^v_lv7d>iIvJ&ecJEYzQWCIcSnZ2VjGiGTHLse-WahQty?eHhbvX8{J#<>t zYC{|3Cpb}cIOOQy#0g%bam2XumEmOnPZ>T>&XCj(an7BT5oED6Pg_+M&ni8fMT*{h z5gY{nrwAga-U_Z>8>m5R0Gw_I8<)pN+wDV@cQCM12o&M&4)7Bi^y>f?XkXj`%+P+a z18hh8EdpRFxiBanh6=!Dlzt`vtFW!`y#Q#T5Zj5sZm8A?wA2^G{d2_`gCWgN>|47V zx_vLADa`Z#6a~UeM1&ewp~g#L-#17QA}|fE5za^HD71n;LZH1k$7(1L0ut68<{>O< z(Ts?|87%l_=eN`92ZYKkHhzyiv=sU5vwU~wf7SI(5VpHL{E}*@*V0^i` z1;hRdUw%)2mY%1*A-;S5etO-AbA~5?&prR`{2LO)s=+IWcG3Bz!n{QttoxpRqBBo7 zLQ>DX^DjM!^E)1ZV&1=R#H-;CNMQ~&?~ delta 2384 zcmZvd3p~^7AIG;nxkgKDF42^GQeL5=mUA4LOQK<;R&IyV4XcP`6vlDC)NlTgNS*k1 zl7<~c3SC%eF4e?bMw~yzTpBsm`TfYwdHw(Y?e%(|=l%IU-|ut%eV=$PHjax`aCHJJ zR8X?;A4ktraKzy0nV$cFOK9K)P`gA!Gihivhla*naYw|lH$VegMZvs;fzw05{CN~< zbk|{&0b-u0#~3s6Ojp-Vl(DRixuDrs4_R(N++ik;9n$JBws?t)A&lx#H)BTADbG;e%!VVkD!)4Uyf-XF{JBhDl zu@Y}sI*9TOAZDTX@*|n_ZLV5Gvq!`B`_P*C} zio1Z%q?DwTB}|u_cg<94vb=Ne+O)^rEpOq~Z>VS{6}hGE`>{y>3@26Dvo!ee&Rq76 z_hYM+z8W(sB3CoK^Iwr0Dccq7JoaB@J5A+(sILu4ZC6siT5nk{UV9?5eRb~c_zulK z`>?0P&0%5=B_iY5KA&UP@xHV1U8>!tf_7H8+Mer{Dseb9t-Fe?RKLi$zO21TtJ2;) z_X&%{@MLciPkwHnsqOwzzv^on`;+h6)S@s$;SYm{_Gz^!4(nVwtYp_qCb@4p8$wUi z49KUo`hFhJ@hdM%*`h5hO0=#Mg}j*CRg#fw?JB~$I?2eQ;2#@}lql5ScVg*GwV@Av zK!&6E6w@z{ocBZR ze+qhxrZnNU)n9NIEv2_Q^J~?sq?=HeG%pTi0^-=cA(!Gj7*x#y?x<`G6)cbyLmh#LLg#zY(wW zG~vwKqtzL={Cru}8oD~CH`Yqu_5nIXQSIKYR#80J4b5@@{ zWTRUZ@#Mgq)TUTftkusWYjR?FCl1D{R!~S$j9D=eQQ4(nL~NN_9^j zZvC2#)=61~yjw2X_GG++(aicaCm{kK7&E>-xKTahW6UWDN#ZHlUEbW`Q4h)0Ei}Jb z8(r&F_9)oPG&wpi=uX@?Gc=ravSdcba!X^CXg!bI<2qvbXvXAF&Rk~8|j#2CygY@5{b{=5J z8E;oRU>ar~nI5B{c-}&qj4zF|-C^}UHN9nXFJ)7@))`wZI4irU?2sCI z<#Smn*j}#wSW0${T$qwaO@$)O^T|y*=?*uzplQN~P(JFdGqtOu`NkesH;o=6!%WsD z${BI~?b3ter$a3kWHNRya7T+>x6U1BtSntia1>ZXyjAg=)t8D4F`urAy>?~xWb$j9 ze`7SK46z=xPOiOGi;(Fb8Yprl7?x;LH;BqC&alS|6MjnEHA%Y{B^2<18Ot%Cf=M;QsuTKz{0lj0pAGZ-NW_5ijOvMxTPmZ)C~O0v@%8kMZyM`lTWc%D zbEZDNGe5Vsish-yow#ckKJdE9I&1G2U{5&Kw>b4r+h4sVb%fOa)%FXzn5b`n8Z&y_ zrP3cMA7Uo;dp@Se869F>U^D4pYn8-u&vt2UGMF98zBuKuXTI2_IEvhHhTJjp+pSIb zb5*Hj2`7^M&$V^UrA3VF?yhp8ibS(~@%zaOj_O;(bM7nzuV0WP!7=cEk|6xlsc}qt zKr0%9qeEYMfw?@M?nXs=-bO(K13(3G5deK8FN6z#4N^Z803uR<7J#3S+PV+ylsK`` zP)0xO#Oecjh_S8@XrlR0Pan{g^f8oQ?1LVJ2>t(%M`f2}XUGqh5$D8y*!diE^E+}8 zl!8XTY&%2mA;CiXi}1 zh&uolQ#c<9bXpfcnYyrw9b8hi24U642tYnYK=r@F`@%-_zo6(rpeI=tS7~v-&@`z2 zpYpWAl6)MJ7B20Hi4cxl6bPWlf&Uvh6gHx@pftFzB=QsG#Sq|CGJrlN!*d)H0tJcn zq2ba1T@Zd~X#nC79HSFD@Ev0jw1BP+fyI~N5rWFlJGeqNc^*LlgdYal2<{k$Q5NzT M1{R diff --git a/assets/lingua-franca-handbook_lf-c.pdf b/assets/lingua-franca-handbook_lf-c.pdf index efd722d9e62324d0028f276ab32f66952d60df27..87b31b8c3a1f6950bcb7478253b3ad604c48704d 100644 GIT binary patch delta 49786 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 delta 42370 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 diff --git a/assets/lingua-franca-handbook_lf-cpp.pdf b/assets/lingua-franca-handbook_lf-cpp.pdf index f80c43404158f72ad0e86b936ca7174af9773e57..19983b5d31efe8e35f0529228032280efdad3d92 100644 GIT binary patch delta 44224 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 HfBXLfnph4t delta 44098 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 MU31NopZ=8p4~&!ViU0rr delta 51413 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 XARm?9b<6I@9nv1UckiQ4KJotnT)_+O delta 45233 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 diff --git a/assets/lingua-franca-handbook_lf-ts.pdf b/assets/lingua-franca-handbook_lf-ts.pdf index 88ac82915a68f430a44370c16643a93b711e3746..8b28c83401395a5830157dde9bcb11d1bf45e5ee 100644 GIT binary patch delta 57954 zcmZvl3-CsHS=VQ|!>+=*!UPzs*cQxCz31|tbKYPbAmt+0RUrkeK|m=QA}Iy}eTT{m zhzPF3pGm-&tRWC^F7fFf2afAvXiUCH1#DV%e@AaJT=UwJM%RKu#zsvJ` z?w8;DYj10Q^=-{-pLM6Po%+{3_kp{A-}V!adF5T6y;}W1JKp-?kKX6j|NYAKHRG+{ z{Hkxf=F7JZuX@1t2Oj?JKl9layyf*j_2_T;4{vED0<8-DV~ z-~K0`dCYU)_tO5qf9d1T{?{LW{H;&?hoAbn&wS{vFZstey>EKpO&@sPpMTVYUwOwD z{QQ%iwR-dC-u9LLy}$RLUiFvncXi|M{?>Dz^papQ;nevg0h-~XA1 zU-PhE{>US5UqAJ$*FWv;UwPNVe)l_n^Y8uoe|zy$w_m;Yv;O3JKKmaZcKcWU;_E;7 z{?C8Ihi`xLufE_bH~rZCn=jq{v5(k3{y{(bi+B9#AHByb-|~u&Km3bd{>Z=k*lXYM zfv@}FH~mU;{U1GI_@fX1_G4cBf9~?YpT6y_H-E5y|9}15`psW__Tz8<-<(=tJ!Xp7m4z_g{VNrGMeRkAL>d9@<^~ z*t4g*zUF)0^w>Xq$PFL4%OC¢=2e&S=lewUZs`9n{?|DA7s+O04Ag+Kd4kA2h= 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 ac=&fc@9O_O@$TEZ?yi5xL%#j-kNN*;CIngl diff --git a/chunk-map.json b/chunk-map.json index b643131ee..17e38c3e8 100644 --- a/chunk-map.json +++ b/chunk-map.json @@ -1 +1 @@ -{"polyfill":["/polyfill-8093e63a736b03ae8dca.js"],"app":["/app-91a8e178c6b8a94bf17c.js"],"component---src-templates-documentation-tsx":["/component---src-templates-documentation-tsx-69662a933b3589c31818.js"],"component---src-templates-pages-community-tsx":["/component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js"],"component---src-templates-pages-docs-handbook-index-tsx":["/component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js"],"component---src-templates-pages-docs-index-tsx":["/component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js"],"component---src-templates-pages-download-tsx":["/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js"],"component---src-templates-pages-empty-tsx":["/component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js"],"component---src-templates-pages-index-tsx":["/component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js"],"component---src-templates-pages-publications-and-presentations-tsx":["/component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js"]} \ No newline at end of file +{"polyfill":["/polyfill-8093e63a736b03ae8dca.js"],"app":["/app-91a8e178c6b8a94bf17c.js"],"component---src-templates-documentation-tsx":["/component---src-templates-documentation-tsx-69662a933b3589c31818.js"],"component---src-templates-pages-community-tsx":["/component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js"],"component---src-templates-pages-docs-handbook-index-tsx":["/component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js"],"component---src-templates-pages-docs-index-tsx":["/component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js"],"component---src-templates-pages-download-tsx":["/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js"],"component---src-templates-pages-empty-tsx":["/component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js"],"component---src-templates-pages-index-tsx":["/component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js"],"component---src-templates-pages-publications-and-presentations-tsx":["/component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js"]} \ No newline at end of file diff --git a/commons-530c9aa67c6d41969a85.js b/commons-b82d559d459fdf48f9b7.js similarity index 99% rename from commons-530c9aa67c6d41969a85.js rename to commons-b82d559d459fdf48f9b7.js index 5a3e424a0..dd69da807 100644 --- a/commons-530c9aa67c6d41969a85.js +++ b/commons-b82d559d459fdf48f9b7.js @@ -1,3 +1,3 @@ -/*! For license information please see commons-530c9aa67c6d41969a85.js.LICENSE.txt */ -(self.webpackChunklingua_franca=self.webpackChunklingua_franca||[]).push([[351],{2324:function(e,t,n){"use strict";var r=n(9310),o=n(1787),i=n(5358);e.exports=function(e,t,n){var a=r(t);a in e?o.f(e,a,i(0,n)):e[a]=n}},1466:function(e,t,n){"use strict";var r=n(1017).end,o=n(9445);e.exports=o("trimEnd")?function(){return r(this)}:"".trimEnd},9445:function(e,t,n){var r=n(1805).PROPER,o=n(4229),i=n(1647);e.exports=function(e){return o((function(){return!!i[e]()||"​…᠎"!=="​…᠎"[e]()||r&&i[e].name!==e}))}},8747:function(e,t,n){"use strict";var r=n(1017).start,o=n(9445);e.exports=o("trimStart")?function(){return r(this)}:"".trimStart},1017:function(e,t,n){var r=n(5968),o=n(8885),i=n(3326),a=n(1647),s=r("".replace),c="["+a+"]",u=RegExp("^"+c+c+"*"),l=RegExp(c+c+"*$"),h=function(e){return function(t){var n=i(o(t));return 1&e&&(n=s(n,u,"")),2&e&&(n=s(n,l,"")),n}};e.exports={start:h(1),end:h(2),trim:h(3)}},1647:function(e){e.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},1804:function(e,t,n){var r=n(3103),o=n(9003),i=n(2324);r({target:"Object",stat:!0},{fromEntries:function(e){var t={};return o(e,(function(e,n){i(t,e,n)}),{AS_ENTRIES:!0}),t}})},8827:function(e,t,n){n(5625);var r=n(3103),o=n(1466);r({target:"String",proto:!0,name:"trimEnd",forced:"".trimEnd!==o},{trimEnd:o})},8763:function(e,t,n){var r=n(3103),o=n(8747);r({target:"String",proto:!0,name:"trimStart",forced:"".trimLeft!==o},{trimLeft:o})},5625:function(e,t,n){var r=n(3103),o=n(1466);r({target:"String",proto:!0,name:"trimEnd",forced:"".trimRight!==o},{trimRight:o})},1715:function(e,t,n){n(8763);var r=n(3103),o=n(8747);r({target:"String",proto:!0,name:"trimStart",forced:"".trimStart!==o},{trimStart:o})},2077:function(e,t,n){"use strict";n.d(t,{R:function(){return W}});var r=n(2784),o=n(5542),i=n(6911),a=n(3087),s=n(8349),c=n(1875),u=n(7309),l=n(7846);function h(e,t){return Object.keys(e).reduce((function(n,r){return n[r]=(0,o.pi)({timeZone:t},e[r]),n}),{})}function f(e,t){return Object.keys((0,o.pi)((0,o.pi)({},e),t)).reduce((function(n,r){return n[r]=(0,o.pi)((0,o.pi)({},e[r]||{}),t[r]||{}),n}),{})}function m(e,t){if(!t)return e;var n=c.C.formats;return(0,o.pi)((0,o.pi)((0,o.pi)({},n),e),{date:f(h(n.date,t),h(e.date||{},t)),time:f(h(n.time,t),h(e.time||{},t))})}var p=function(e,t,n,r,i){var a=e.locale,c=e.formats,h=e.messages,f=e.defaultLocale,p=e.defaultFormats,g=e.fallbackOnEmptyString,d=e.onError,y=e.timeZone,b=e.defaultRichTextElements;void 0===n&&(n={id:""});var I=n.id,v=n.defaultMessage;(0,s.kG)(!!I,"[@formatjs/intl] An `id` must be provided to format a message. You can either:\n1. Configure your build toolchain with [babel-plugin-formatjs](https://formatjs.io/docs/tooling/babel-plugin)\nor [@formatjs/ts-transformer](https://formatjs.io/docs/tooling/ts-transformer) OR\n2. Configure your `eslint` config to include [eslint-plugin-formatjs](https://formatjs.io/docs/tooling/linter#enforce-id)\nto autofix this issue");var A=String(I),E=h&&Object.prototype.hasOwnProperty.call(h,A)&&h[A];if(Array.isArray(E)&&1===E.length&&E[0].type===l.wD.literal)return E[0].value;if(!r&&E&&"string"==typeof E&&!b)return E.replace(/'\{(.*?)\}'/gi,"{$1}");if(r=(0,o.pi)((0,o.pi)({},b),r||{}),c=m(c,y),p=m(p,y),!E){if(!1===g&&""===E)return E;if((!v||a&&a.toLowerCase()!==f.toLowerCase())&&d(new u.$6(n,a)),v)try{return t.getMessageFormat(v,f,p,i).format(r)}catch(C){return d(new u.X9('Error formatting default message for: "'.concat(A,'", rendering default message verbatim'),a,n,C)),"string"==typeof v?v:A}return A}try{return t.getMessageFormat(E,a,c,(0,o.pi)({formatters:t},i||{})).format(r)}catch(C){d(new u.X9('Error formatting message: "'.concat(A,'", using ').concat(v?"default message":"id"," as fallback."),a,n,C))}if(v)try{return t.getMessageFormat(v,f,p,i).format(r)}catch(C){d(new u.X9('Error formatting the default message for: "'.concat(A,'", rendering message verbatim'),a,n,C))}return"string"==typeof E?E:"string"==typeof v?v:A},g=n(3167),d=["style","currency","currencyDisplay","unit","unitDisplay","useGrouping","minimumIntegerDigits","minimumFractionDigits","maximumFractionDigits","minimumSignificantDigits","maximumSignificantDigits","compactDisplay","currencyDisplay","currencySign","notation","signDisplay","unit","unitDisplay","numberingSystem"];function y(e,t,n){var r=e.locale,o=e.formats,i=e.onError;void 0===n&&(n={});var a=n.format,s=a&&(0,g.TB)(o,"number",a,i)||{};return t(r,(0,g.L6)(n,d,s))}function b(e,t,n,r){void 0===r&&(r={});try{return y(e,t,r).format(n)}catch(o){e.onError(new u.Rw(u.bc.FORMAT_ERROR,"Error formatting number.",o))}return String(n)}function I(e,t,n,r){void 0===r&&(r={});try{return y(e,t,r).formatToParts(n)}catch(o){e.onError(new u.Rw(u.bc.FORMAT_ERROR,"Error formatting number.",o))}return[]}var v=n(1694),A=["numeric","style"];function E(e,t,n,r,o){void 0===o&&(o={}),r||(r="second"),Intl.RelativeTimeFormat||e.onError(new v.u_('Intl.RelativeTimeFormat is not available in this environment.\nTry polyfilling it using "@formatjs/intl-relativetimeformat"\n',v.jK.MISSING_INTL_API));try{return function(e,t,n){var r=e.locale,o=e.formats,i=e.onError;void 0===n&&(n={});var a=n.format,s=!!a&&(0,g.TB)(o,"relative",a,i)||{};return t(r,(0,g.L6)(n,A,s))}(e,t,o).format(n,r)}catch(i){e.onError(new u.Qe("Error formatting relative time.",e.locale,i))}return String(n)}var C=["formatMatcher","timeZone","hour12","weekday","era","year","month","day","hour","minute","second","timeZoneName","hourCycle","dateStyle","timeStyle","calendar","numberingSystem","fractionalSecondDigits"];function T(e,t,n,r){var i=e.locale,a=e.formats,s=e.onError,c=e.timeZone;void 0===r&&(r={});var u=r.format,l=(0,o.pi)((0,o.pi)({},c&&{timeZone:c}),u&&(0,g.TB)(a,t,u,s)),h=(0,g.L6)(r,C,l);return"time"!==t||h.hour||h.minute||h.second||h.timeStyle||h.dateStyle||(h=(0,o.pi)((0,o.pi)({},h),{hour:"numeric",minute:"numeric"})),n(i,h)}function _(e,t){for(var n=[],r=2;r1&&window.pageYOffset>n;n=window.pageYOffset,r?(e.classList.add("down"),e.classList.remove("up"),null==t||t.classList.add("hidden")):(e.classList.add("up"),e.classList.remove("down"),null==t||t.classList.remove("hidden"))}};document.removeEventListener("scroll",r,{capture:!0,passive:!0}),document.addEventListener("scroll",r,{capture:!0,passive:!0})}(),window.docsearch&&u(),!document.getElementById("algolia-search")){var e=document.createElement("script");e.id="algolia-search";var t=document.createElement("link");e.src=(0,o.withPrefix)("/js/docsearch.js"),e.async=!0,e.onload=function(){var e;window.docsearch&&(u(),t.rel="stylesheet",t.href=(0,o.withPrefix)("/css/docsearch.css"),t.type="text/css",document.body.appendChild(t),null===(e=document.getElementById("search-form"))||void 0===e||e.classList.add("search-enabled"))},document.body.appendChild(e)}}),[]),r.createElement("header",{dir:"ltr"},r.createElement("a",{className:"skip-to-main",href:"#site-content",tabIndex:0},t("skip_to_content")),r.createElement("div",{id:"top-menu",className:"up"},r.createElement("div",{className:"left below-small"},r.createElement(c,{id:"home-page-logo",to:"/","aria-label":"Lingua Franca Home Page"},r.createElement("picture",null,r.createElement("source",{media:"(min-width: 600px)",srcSet:n(2285).Z}),r.createElement("img",{src:n(6732).Z}))),r.createElement("nav",{role:"navigation"},r.createElement("ul",null,r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/download"},t("nav_download"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/docs/"},t("nav_documentation_short"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/docs/handbook/overview"},t("nav_handbook"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/community"},t("nav_community")))))),r.createElement("div",{className:"right above-small"},r.createElement("div",{className:"search-section"},r.createElement("div",{className:"nav-item"},r.createElement("form",{id:"search-form",className:"search top-nav",role:"search"},r.createElement("svg",{fill:"none",height:"16",viewBox:"0 0 16 6",width:"20",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"m10.5 0c.5052 0 .9922.0651042 1.4609.195312.4688.130209.9063.315105 1.3125.554688.4063.239583.7761.52865 1.1094.86719.3386.33333.6276.70312.8672 1.10937s.4245.84375.5547 1.3125.1953.95573.1953 1.46094-.0651.99219-.1953 1.46094-.3151.90625-.5547 1.3125-.5286.77864-.8672 1.11718c-.3333.33334-.7031.61978-1.1094.85938-.4062.2396-.8437.4245-1.3125.5547-.4687.1302-.9557.1953-1.4609.1953-.65104 0-1.27604-.1094-1.875-.3281-.59375-.2188-1.14062-.5339-1.64062-.94534l-6.132818 6.12504c-.098958.0989-.216145.1484-.351562.1484s-.252604-.0495-.351562-.1484c-.0989588-.099-.148438-.2162-.148438-.3516s.0494792-.2526.148438-.3516l6.125002-6.13278c-.41146-.5-.72656-1.04687-.94532-1.64062-.21874-.59896-.32812-1.22396-.32812-1.875 0-.50521.0651-.99219.19531-1.46094s.31511-.90625.55469-1.3125.52604-.77604.85938-1.10937c.33854-.33854.71093-.627607 1.11718-.86719s.84375-.424479 1.3125-.554688c.46875-.1302078.95573-.195312 1.46094-.195312zm0 10c.6198 0 1.2031-.11719 1.75-.35156.5469-.23959 1.0234-.5625 1.4297-.96875.4062-.40625.7265-.88281.9609-1.42969.2396-.54688.3594-1.13021.3594-1.75s-.1198-1.20312-.3594-1.75c-.2344-.54688-.5547-1.02344-.9609-1.42969-.4063-.40625-.8828-.72656-1.4297-.96093-.5469-.23959-1.1302-.35938-1.75-.35938-.61979 0-1.20312.11979-1.75.35938-.54688.23437-1.02344.55468-1.42969.96093s-.72916.88281-.96875 1.42969c-.23437.54688-.35156 1.13021-.35156 1.75s.11719 1.20312.35156 1.75c.23959.54688.5625 1.02344.96875 1.42969s.88281.72916 1.42969.96875c.54688.23437 1.13021.35156 1.75.35156z",fill:"#fff"})),r.createElement("span",null,r.createElement("input",{id:"search-box-top",type:"search",placeholder:t("nav_search_placeholder"),"aria-label":t("nav_search_aria")})),r.createElement("input",{type:"submit",style:{display:"none"}})))))),r.createElement("div",{id:"site-content"}))},u=n(8760),l=function(){document.documentElement.classList.remove("light-theme"),document.documentElement.classList.add("dark-theme")},h=function(){document.documentElement.classList.remove("dark-theme"),document.documentElement.classList.add("light-theme")},f=function(){var e=(0,s.D)((0,i.Z)()),t="undefined"!=typeof window&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,n=u.e&&localStorage.getItem("force-color-theme")||"system",o=(0,r.useState)(n),a=o[0],c=o[1],f=u.e&&localStorage.getItem("force-font")||"cascadia",m=(0,r.useState)(f),p=m[0],g=m[1];return r.createElement("section",{id:"customize"},r.createElement("article",null,r.createElement("h3",null,e("footer_customize")),r.createElement("label",null,r.createElement("p",null,e("footer_site_colours"),":"),r.createElement("div",{className:"switch-wrap"},r.createElement("select",{name:"colours",value:a,onChange:function(e){"system"===e.target.value?(t?l():h(),u.e&&localStorage.removeItem("force-color-theme")):"force-light"===e.target.value?(h(),u.e&&localStorage.setItem("force-color-theme","force-light")):"force-dark"===e.target.value&&(l(),u.e&&localStorage.setItem("force-color-theme","force-dark")),c(e.target.value)}},r.createElement("option",{value:"system"},e("footer_site_colours_options_system")),r.createElement("option",{value:"force-light"},e("footer_site_colours_options_always_light")),r.createElement("option",{value:"force-dark"},e("footer_site_colours_options_always_dark"))))),r.createElement("label",null,r.createElement("p",null,e("footer_code_font"),":"),r.createElement("div",{className:"switch-wrap"},r.createElement("select",{name:"font",value:p,onChange:function(e){var t,n;localStorage.setItem("force-font",e.target.value),t=e.target.value,(n=f)&&document.documentElement.classList.remove("font-"+n),document.documentElement.classList.add("font-"+t),g(e.target.value)}},r.createElement("option",{value:"cascadia"},"Cascadia"),r.createElement("option",{value:"cascadia-ligatures"},"Cascadia (ligatures)"),r.createElement("option",{value:"consolas"},"Consolas"),r.createElement("option",{value:"dank-mono"},"Dank Mono"),r.createElement("option",{value:"fira-code"},"Fira Code"),r.createElement("option",{value:"jetbrains-mono"},"JetBrains Mono"),r.createElement("option",{value:"open-dyslexic"},"OpenDyslexic"),r.createElement("option",{value:"sf-mono"},"SF Mono"),r.createElement("option",{value:"source-code-pro"},"Source Code Pro"))))))},m=[{title:"Get Started",url:"/docs/handbook/overview"},{title:"Download",url:"/download"},{title:"Why Lingua Franca",url:"/"},{title:"Publications",url:"/publications-and-presentations"}],p=[{title:"Get Help",url:"/community"},{title:"GitHub Repo",url:"https://github.com/lf-lang/lingua-franca"},{title:"@thelflang",url:"https://twitter.com/thelflang"},{title:"Web Repo",url:"https://github.com/lf-lang/website-lingua-franca"},{title:"Zulip",url:"https://zulip.lf-lang.org"}],g=function(e){var t=m.filter((function(e){return!e.url.includes("#show-examples")})),o=(0,a.i)(e.lang);e.suppressDocRecommendations;return r.createElement("footer",{id:"site-footer",role:"contentinfo"},e.suppressCustomization?null:r.createElement(f,null),r.createElement("section",{id:"community"},r.createElement("article",{id:"logos"},r.createElement("a",{href:""},r.createElement("img",{id:"lf-logo",width:195,height:75,src:n(9808).Z,alt:"Lingua Franca Logo"})),r.createElement("p",null,"Made with ♥ in Berkeley, Dallas, Dresden, Kiel, and Seoul"),r.createElement("p",null,"© 2019-",(new Date).getFullYear()," The Lingua Franca Team",r.createElement("br",null))),r.createElement("article",{id:"using-lf"},r.createElement("h3",null,"Using Lingua Franca"),r.createElement("ul",null,t.map((function(e){return r.createElement("li",{key:e.url},r.createElement(o,{to:e.url},e.title))})))),r.createElement("article",{id:"community-links"},r.createElement("h3",null,"Community"),r.createElement("ul",null,p.map((function(e){var t=function(e){switch(e){case"https://github.com/lf-lang/website-lingua-franca":case"https://github.com/lf-lang/lingua-franca":return r.createElement("svg",{fill:"none",height:"12",viewBox:"0 0 12 12",width:"12",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{clipRule:"evenodd",d:"m6.03927.165405c-3.27055 0-5.922909 2.652005-5.922909 5.923645 0 2.61709 1.697089 4.83705 4.050909 5.62035.29636.0546.40436-.1284.40436-.2854 0-.1408-.00509-.5131-.008-1.0073-1.64763.3578-1.99527-.7942-1.99527-.7942-.26946-.68436-.65782-.86654-.65782-.86654-.53782-.36727.04073-.36001.04073-.36001.59454.04182.90727.61055.90727.61055.52836.90509 1.38655.64364 1.724.492.05382-.38254.20691-.64363.376-.79163-1.31527-.14946-2.69818-.65782-2.69818-2.92764 0-.64654.23091-1.17564.60982-1.58946-.06109-.14981-.26437-.75236.05818-1.56763 0 0 .49709-.15927 1.62872.60727.47237-.13163.97928-.19709 1.48291-.19964.50328.00255 1.00982.06801 1.48291.19964 1.13091-.76654 1.62727-.60727 1.62727-.60727.32328.81527.12001 1.41782.05928 1.56763.37964.41382.60873.94292.60873 1.58946 0 2.27564-1.38509 2.77636-2.70437 2.92291.21237.18291.40182.54436.40182 1.09672 0 .79204-.00727 1.43094-.00727 1.62514 0 .1585.10691.3429.40727.2851 2.35197-.7851 4.04767-3.00369 4.04767-5.62005 0-3.27164-2.6524-5.923645-5.92403-5.923645z",fill:"#ffffff",fillRule:"evenodd"}));case"https://twitter.com/thelflang":return r.createElement("svg",{fill:"none",height:"10",viewBox:"0 0 13 10",width:"13",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"m4.58519 10c4.62962 0 7.16291-3.83919 7.16291-7.16289 0-.10801 0-.21602-.0049-.32403.4909-.35348.918-.80024 1.2568-1.30591-.4517.20128-.9377.33384-1.4483.39766.5204-.30929.9181-.805148 1.1095-1.394284-.486.289658-1.026.495856-1.6004.608773-.4615-.490946-1.11448-.7953322-1.83617-.7953322-1.38938 0-2.51856 1.1291732-2.51856 2.5185532 0 .19638.02455.38785.06383.57441-2.09143-.1031-3.94721-1.10954-5.1893-2.631474-.21602.373119-.33876.805154-.33876 1.266644 0 .87388.44677 1.64467 1.11936 2.09634-.41239-.01473-.80024-.12765-1.13899-.31421v.03437c0 1.21754.86897 2.23871 2.01778 2.46946-.2111.05891-.43203.08837-.66277.08837-.16202 0-.31912-.01473-.47131-.04419.31911 1.00153 1.25191 1.72813 2.35163 1.74777-.86406.67751-1.94906 1.08008-3.12733 1.08008-.20128 0-.402571-.00982-.59895-.03436 1.10954.70696 2.43509 1.12425 3.85393 1.12425z",fill:"#ffffff"}));case"https://zulip.lf-lang.org":return r.createElement("svg",{fill:"none",viewBox:"-30 -10 55 55",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"M22.767 3.589c0 1.209-.543 2.283-1.37 2.934l-8.034 7.174c-.149.128-.343-.078-.235-.25l2.946-5.9c.083-.165-.024-.368-.194-.368H4.452c-1.77 0-3.219-1.615-3.219-3.59C1.233 1.616 2.682 0 4.452 0h15.096c1.77-.001 3.219 1.614 3.219 3.589zM4.452 24h15.096c1.77 0 3.219-1.616 3.219-3.59 0-1.974-1.449-3.59-3.219-3.59H8.12c-.17 0-.277-.202-.194-.367l2.946-5.9c.108-.172-.086-.378-.235-.25l-8.033 7.173c-.828.65-1.37 1.725-1.37 2.934 0 1.974 1.448 3.59 3.218 3.59z",fill:"#ffffff"}))}}(e.url),n=t?r.createElement("span",{className:"link-prefix"},t):null;return r.createElement("li",{key:e.url},r.createElement("a",{style:{position:"relative"},href:e.url},n,e.title))}))))))},d=n(7609),y=function(e){var t=Object.assign({},e.ogTags,{"og:title":e.title,"og:description":e.description,"twitter:site":"thelflang"});return r.createElement(r.Fragment,null,r.createElement(d.q,{title:e.title,titleTemplate:"%s"},r.createElement("meta",{name:"description",key:"description",content:e.description}),Object.keys(t).map((function(e){return r.createElement("meta",{key:e,property:e,content:t[e]})}))))},b=function(e){return r.createElement(r.Fragment,null,r.createElement(d.q,{htmlAttributes:{lang:e.lang}},r.createElement("script",{src:"https://polyfill.io/v3/polyfill.min.js?features=es2015%2CArray.prototype.forEach%2CNodeList.prototype.forEach"}),r.createElement("link",{rel:"preload",href:(0,o.withPrefix)("/css/docsearch.css"),as:"style"}),r.createElement("style",null,"\npre data-err {\n background:url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\") repeat-x 0 100%;\n padding-bottom: 3px;\n}")),r.createElement(y,e),r.createElement("div",{className:"ms-Fabric"},r.createElement(c,e),r.createElement("main",{role:"main"},e.children),r.createElement(g,e)))}},9931:function(e,t,n){"use strict";n.r(t),n.d(t,{comCopy:function(){return r}});var r={com_layout_title:"Lingua Franca Community",com_layout_description:"Connect with other LF Programmers online and offline.",com_connect_online:"Online",com_connect_online_description:"Tap into our online resources to learn more about Lingua Franca, provide feedback, connect with our developers, and find out about new updates.",com_online_publications_desc:"View our publications and presentations.",com_online_publications_tag:"",com_online_github_desc:"Found a bug, or want to provide feedback?",com_online_github_href:"Tell us on GitHub.",com_online_zulip_href:"Join the conversation on Zulip.",com_online_zulip_desc:"Have questions, or want to chat with other users?",com_online_twitter_href:"@thelflang",com_online_twitter_desc:"Stay up to date. Follow us on Twitter"}},636:function(e,t,n){"use strict";n.r(t),n.d(t,{docCopy:function(){return r}});var r={doc_layout_title:"Lingua Franca Documentation",doc_layout_description:"Find Lingua Franca starter projects: from Python to C to C++ to TypeScript.",doc_headline:"Learning Resources",doc_headline_handbook_blurb:"The Lingua Franca language reference"}},4158:function(e,t,n){"use strict";n.r(t),n.d(t,{dtCopy:function(){return r}});var r={dt_s_page_title:"Search for typed packages",dt_s_title:"Type Search",dt_s_subtitle:"Find npm packages that have type declarations, either bundled or on Definitely Typed.",dt_s_match:"match",dt_s_matchs:"matches",dt_s_match_exact:"Exact Match",dt_s_popular_on_dt:"Popular on Definitely Typed",dt_s_downloads_short:"DLs",dt_s_downloads_via:"Via",dt_s_module:"Module",dt_s_last_update:"Last Updated",dt_s_install:"Install",dt_s_no_results:"No results found for",dt_s_no_results_try:"Try another search?",dt_s_copy:"copy",dt_s_copied:"copied"}},5437:function(e,t,n){"use strict";n.r(t),n.d(t,{lang:function(){return b},messages:function(){return y}});var r,o,i=n(5542),a=n(2784),s=n(6563);!function(e){e.formatDate="FormattedDate",e.formatTime="FormattedTime",e.formatNumber="FormattedNumber",e.formatList="FormattedList",e.formatDisplayName="FormattedDisplayName"}(r||(r={})),function(e){e.formatDate="FormattedDateParts",e.formatTime="FormattedTimeParts",e.formatNumber="FormattedNumberParts",e.formatList="FormattedListParts"}(o||(o={}));var c=function(e){var t=(0,s.Z)(),n=e.value,r=e.children,o=(0,i._T)(e,["value","children"]);return r(t.formatNumberToParts(n,o))};c.displayName="FormattedNumberParts";function u(e){var t=function(t){var n=(0,s.Z)(),r=t.value,o=t.children,a=(0,i._T)(t,["value","children"]),c="string"==typeof r?new Date(r||0):r;return o("formatDate"===e?n.formatDateToParts(c,a):n.formatTimeToParts(c,a))};return t.displayName=o[e],t}function l(e){var t=function(t){var n=(0,s.Z)(),r=t.value,o=t.children,c=(0,i._T)(t,["value","children"]),u=n[e](r,c);if("function"==typeof o)return o(u);var l=n.textComponent||a.Fragment;return a.createElement(l,null,u)};return t.displayName=r[e],t}c.displayName="FormattedNumberParts";l("formatDate"),l("formatTime"),l("formatNumber"),l("formatList"),l("formatDisplayName"),u("formatDate"),u("formatTime");var h=n(6801),f=n(636),m=n(2990),p=n(9931),g=n(5636),d=n(519),y=Object.assign({},h.navCopy,f.docCopy,p.comCopy,g.handbookCopy,m.indexCopy,d.footerCopy),b=y},519:function(e,t,n){"use strict";n.r(t),n.d(t,{footerCopy:function(){return r}});var r={footer_customize:"Customize",footer_site_colours:"Color Mode",footer_code_font:"Code Font",footer_site_colours_options_system:"System",footer_site_colours_options_always_light:"Always Light",footer_site_colours_options_always_dark:"Always Dark"}},5636:function(e,t,n){"use strict";n.r(t),n.d(t,{handbookCopy:function(){return r}});var r={handb_prev:"Previous",handb_next:"Next",handb_on_this_page:"On this page",handb_like_dislike_title:"Is this page helpful?",handb_like_desc:"Yes",handb_dislike_desc:"No",handb_thanks:"Thanks for the feedback",handb_deprecated_title:"This page has been deprecated",handb_deprecated_subtitle:"This handbook page has been replaced, ",handb_deprecated_subtitle_link:"go to the new page",handb_deprecated_subtitle_action:"Go to new page",handb_experimental_title:"This page contains experimental documentation",handb_experimental_subtitle:"The contents are about a work in progress topic."}},2990:function(e,t,n){"use strict";n.r(t),n.d(t,{indexCopy:function(){return r}});var r={index_2_headline:"Lingua Franca is a polyglot coordination language for reactive, concurrent, and time-sensitive applications.",index_2_byline:"Lingua Franca",index_2_summary:"Lingua Franca (LF) is a polyglot coordination language built to bring deterministic reactive concurrency and time to mainstream target programming languages (currently C, C++, Python, TypeScript, and Rust). LF is supported by a runtime system that is capable of concurrent and distributed execution of reactive programs that are deployable on the Cloud, the Edge, and even on bare-iron embedded platforms.",index_2_detail:"A Lingua Franca program specifies the interactions between components called reactors. The logic of each reactor is written in plain target code. A code generator synthesizes one or more programs in the target language, which are then compiled using standard tool chains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.",index_2_cta_install:"Download Lingua Franca",index_2_cta_install_subtitle:"Version",index_2_cta_install_fallback:"Latest stable release",index_2_cta_download:"On your computer",index_2_cta_download_subtitle:"via Github",index_2_what_is:"What is Lingua Franca?",index_2_what_is_lf:"Reactor-oriented",index_2_what_is_lf_copy:"Reactors are reactive and composable concurrent software components with inputs, outputs, and local state.",index_2_trust:"Concurrent",index_2_trust_copy:"Reactions to events are concurrent unless there is an explicit dependency between them.",index_2_scale:"Deterministic",index_2_scale_copy:"Lingua Franca programs are deterministic by default and therefore easy to test.",index_2_started_title:"Get Started",index_2_started_handbook:"Handbook",index_2_started_handbook_blurb:"Learn the language",index_2_install:"Install Lingua Franca"}},6801:function(e,t,n){"use strict";n.r(t),n.d(t,{navCopy:function(){return r}});var r={skip_to_content:"Skip to main content",nav_documentation:"Documentation",nav_documentation_short:"Docs",nav_download:"Download",nav_community:"Community",nav_handbook:"Handbook",nav_tools:"Tools",nav_search_placeholder:"Search Docs",nav_search_aria:"Search the Lingua Franca site",nav_this_page_in_your_lang:"This page is available in your language",nav_this_page_in_your_lang_open:"Open",nav_this_page_in_your_lang_no_more:"Don't show again"}},9577:function(e,t,n){"use strict";n.r(t),n.d(t,{inYourLanguage:function(){return r}});var r={en:{shorthand:"In En",body:"This page is available in English",open:"Go",cancel:"Don't ask again"}}},2419:function(e,t,n){"use strict";function r(e){return function(t,n){return e.formatMessage({id:t},n)}}n.d(t,{D:function(){return r}})},8349:function(e,t,n){"use strict";n.d(t,{kG:function(){return r}});function r(e,t,n){if(void 0===n&&(n=Error),!e)throw new n(t)}},8770:function(e,t,n){"use strict";function r(e,t){var n=t&&t.cache?t.cache:l,r=t&&t.serializer?t.serializer:c;return(t&&t.strategy?t.strategy:s)(e,{cache:n,serializer:r})}function o(e,t,n,r){var o,i=null==(o=r)||"number"==typeof o||"boolean"==typeof o?r:n(r),a=t.get(i);return void 0===a&&(a=e.call(this,r),t.set(i,a)),a}function i(e,t,n){var r=Array.prototype.slice.call(arguments,3),o=n(r),i=t.get(o);return void 0===i&&(i=e.apply(this,r),t.set(o,i)),i}function a(e,t,n,r,o){return n.bind(t,e,r,o)}function s(e,t){return a(e,this,1===e.length?o:i,t.cache.create(),t.serializer)}n.d(t,{A:function(){return h},Z:function(){return r}});var c=function(){return JSON.stringify(arguments)};function u(){this.cache=Object.create(null)}u.prototype.get=function(e){return this.cache[e]},u.prototype.set=function(e,t){this.cache[e]=t};var l={create:function(){return new u}},h={variadic:function(e,t){return a(e,this,i,t.cache.create(),t.serializer)},monadic:function(e,t){return a(e,this,o,t.cache.create(),t.serializer)}}},7846:function(e,t,n){"use strict";n.d(t,{wD:function(){return i},VG:function(){return c},rp:function(){return l},Ii:function(){return y},O4:function(){return s},uf:function(){return u},Wh:function(){return d},Jo:function(){return m},yx:function(){return p},Wi:function(){return f},HI:function(){return g},pe:function(){return h},Qc:function(){return ie}});var r,o=n(5542);!function(e){e[e.EXPECT_ARGUMENT_CLOSING_BRACE=1]="EXPECT_ARGUMENT_CLOSING_BRACE",e[e.EMPTY_ARGUMENT=2]="EMPTY_ARGUMENT",e[e.MALFORMED_ARGUMENT=3]="MALFORMED_ARGUMENT",e[e.EXPECT_ARGUMENT_TYPE=4]="EXPECT_ARGUMENT_TYPE",e[e.INVALID_ARGUMENT_TYPE=5]="INVALID_ARGUMENT_TYPE",e[e.EXPECT_ARGUMENT_STYLE=6]="EXPECT_ARGUMENT_STYLE",e[e.INVALID_NUMBER_SKELETON=7]="INVALID_NUMBER_SKELETON",e[e.INVALID_DATE_TIME_SKELETON=8]="INVALID_DATE_TIME_SKELETON",e[e.EXPECT_NUMBER_SKELETON=9]="EXPECT_NUMBER_SKELETON",e[e.EXPECT_DATE_TIME_SKELETON=10]="EXPECT_DATE_TIME_SKELETON",e[e.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]="UNCLOSED_QUOTE_IN_ARGUMENT_STYLE",e[e.EXPECT_SELECT_ARGUMENT_OPTIONS=12]="EXPECT_SELECT_ARGUMENT_OPTIONS",e[e.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]="EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]="INVALID_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR=15]="EXPECT_SELECT_ARGUMENT_SELECTOR",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]="EXPECT_PLURAL_ARGUMENT_SELECTOR",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]="EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]="EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT",e[e.INVALID_PLURAL_ARGUMENT_SELECTOR=19]="INVALID_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]="DUPLICATE_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]="DUPLICATE_SELECT_ARGUMENT_SELECTOR",e[e.MISSING_OTHER_CLAUSE=22]="MISSING_OTHER_CLAUSE",e[e.INVALID_TAG=23]="INVALID_TAG",e[e.INVALID_TAG_NAME=25]="INVALID_TAG_NAME",e[e.UNMATCHED_CLOSING_TAG=26]="UNMATCHED_CLOSING_TAG",e[e.UNCLOSED_TAG=27]="UNCLOSED_TAG"}(r||(r={}));var i,a;n(1804),n(1715),n(8827);function s(e){return e.type===i.literal}function c(e){return e.type===i.argument}function u(e){return e.type===i.number}function l(e){return e.type===i.date}function h(e){return e.type===i.time}function f(e){return e.type===i.select}function m(e){return e.type===i.plural}function p(e){return e.type===i.pound}function g(e){return e.type===i.tag}function d(e){return!(!e||"object"!=typeof e||e.type!==a.number)}function y(e){return!(!e||"object"!=typeof e||e.type!==a.dateTime)}!function(e){e[e.literal=0]="literal",e[e.argument=1]="argument",e[e.number=2]="number",e[e.date=3]="date",e[e.time=4]="time",e[e.select=5]="select",e[e.plural=6]="plural",e[e.pound=7]="pound",e[e.tag=8]="tag"}(i||(i={})),function(e){e[e.number=0]="number",e[e.dateTime=1]="dateTime"}(a||(a={}));var b=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,I=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function v(e){var t={};return e.replace(I,(function(e){var n=e.length;switch(e[0]){case"G":t.era=4===n?"long":5===n?"narrow":"short";break;case"y":t.year=2===n?"2-digit":"numeric";break;case"Y":case"u":case"U":case"r":throw new RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case"q":case"Q":throw new RangeError("`q/Q` (quarter) patterns are not supported");case"M":case"L":t.month=["numeric","2-digit","short","long","narrow"][n-1];break;case"w":case"W":throw new RangeError("`w/W` (week) patterns are not supported");case"d":t.day=["numeric","2-digit"][n-1];break;case"D":case"F":case"g":throw new RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case"E":t.weekday=4===n?"short":5===n?"narrow":"short";break;case"e":if(n<4)throw new RangeError("`e..eee` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"c":if(n<4)throw new RangeError("`c..ccc` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"a":t.hour12=!0;break;case"b":case"B":throw new RangeError("`b/B` (period) patterns are not supported, use `a` instead");case"h":t.hourCycle="h12",t.hour=["numeric","2-digit"][n-1];break;case"H":t.hourCycle="h23",t.hour=["numeric","2-digit"][n-1];break;case"K":t.hourCycle="h11",t.hour=["numeric","2-digit"][n-1];break;case"k":t.hourCycle="h24",t.hour=["numeric","2-digit"][n-1];break;case"j":case"J":case"C":throw new RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case"m":t.minute=["numeric","2-digit"][n-1];break;case"s":t.second=["numeric","2-digit"][n-1];break;case"S":case"A":throw new RangeError("`S/A` (second) patterns are not supported, use `s` instead");case"z":t.timeZoneName=n<4?"short":"long";break;case"Z":case"O":case"v":case"V":case"X":case"x":throw new RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return""})),t}var A=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;var E=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,C=/^(@+)?(\+|#+)?[rs]?$/g,T=/(\*)(0+)|(#+)(0+)|(0+)/g,_=/^(0+)$/;function w(e){var t={};return"r"===e[e.length-1]?t.roundingPriority="morePrecision":"s"===e[e.length-1]&&(t.roundingPriority="lessPrecision"),e.replace(C,(function(e,n,r){return"string"!=typeof r?(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length):"+"===r?t.minimumSignificantDigits=n.length:"#"===n[0]?t.maximumSignificantDigits=n.length:(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length+("string"==typeof r?r.length:0)),""})),t}function M(e){switch(e){case"sign-auto":return{signDisplay:"auto"};case"sign-accounting":case"()":return{currencySign:"accounting"};case"sign-always":case"+!":return{signDisplay:"always"};case"sign-accounting-always":case"()!":return{signDisplay:"always",currencySign:"accounting"};case"sign-except-zero":case"+?":return{signDisplay:"exceptZero"};case"sign-accounting-except-zero":case"()?":return{signDisplay:"exceptZero",currencySign:"accounting"};case"sign-never":case"+_":return{signDisplay:"never"}}}function N(e){var t;if("E"===e[0]&&"E"===e[1]?(t={notation:"engineering"},e=e.slice(2)):"E"===e[0]&&(t={notation:"scientific"},e=e.slice(1)),t){var n=e.slice(0,2);if("+!"===n?(t.signDisplay="always",e=e.slice(2)):"+?"===n&&(t.signDisplay="exceptZero",e=e.slice(2)),!_.test(e))throw new Error("Malformed concise eng/scientific notation");t.minimumIntegerDigits=e.length}return t}function L(e){var t=M(e);return t||{}}function S(e){for(var t={},n=0,r=e;n1)throw new RangeError("integer-width stems only accept a single optional option");i.options[0].replace(T,(function(e,n,r,o,i,a){if(n)t.minimumIntegerDigits=r.length;else{if(o&&i)throw new Error("We currently do not support maximum integer digits");if(a)throw new Error("We currently do not support exact integer digits")}return""}));continue}if(_.test(i.stem))t.minimumIntegerDigits=i.stem.length;else if(E.test(i.stem)){if(i.options.length>1)throw new RangeError("Fraction-precision stems only accept a single optional option");i.stem.replace(E,(function(e,n,r,o,i,a){return"*"===r?t.minimumFractionDigits=n.length:o&&"#"===o[0]?t.maximumFractionDigits=o.length:i&&a?(t.minimumFractionDigits=i.length,t.maximumFractionDigits=i.length+a.length):(t.minimumFractionDigits=n.length,t.maximumFractionDigits=n.length),""}));var a=i.options[0];"w"===a?t=(0,o.pi)((0,o.pi)({},t),{trailingZeroDisplay:"stripIfInteger"}):a&&(t=(0,o.pi)((0,o.pi)({},t),w(a)))}else if(C.test(i.stem))t=(0,o.pi)((0,o.pi)({},t),w(i.stem));else{var s=M(i.stem);s&&(t=(0,o.pi)((0,o.pi)({},t),s));var c=N(i.stem);c&&(t=(0,o.pi)((0,o.pi)({},t),c))}}return t}var O,D={AX:["H"],BQ:["H"],CP:["H"],CZ:["H"],DK:["H"],FI:["H"],ID:["H"],IS:["H"],ML:["H"],NE:["H"],RU:["H"],SE:["H"],SJ:["H"],SK:["H"],AS:["h","H"],BT:["h","H"],DJ:["h","H"],ER:["h","H"],GH:["h","H"],IN:["h","H"],LS:["h","H"],PG:["h","H"],PW:["h","H"],SO:["h","H"],TO:["h","H"],VU:["h","H"],WS:["h","H"],"001":["H","h"],AL:["h","H","hB"],TD:["h","H","hB"],"ca-ES":["H","h","hB"],CF:["H","h","hB"],CM:["H","h","hB"],"fr-CA":["H","h","hB"],"gl-ES":["H","h","hB"],"it-CH":["H","h","hB"],"it-IT":["H","h","hB"],LU:["H","h","hB"],NP:["H","h","hB"],PF:["H","h","hB"],SC:["H","h","hB"],SM:["H","h","hB"],SN:["H","h","hB"],TF:["H","h","hB"],VA:["H","h","hB"],CY:["h","H","hb","hB"],GR:["h","H","hb","hB"],CO:["h","H","hB","hb"],DO:["h","H","hB","hb"],KP:["h","H","hB","hb"],KR:["h","H","hB","hb"],NA:["h","H","hB","hb"],PA:["h","H","hB","hb"],PR:["h","H","hB","hb"],VE:["h","H","hB","hb"],AC:["H","h","hb","hB"],AI:["H","h","hb","hB"],BW:["H","h","hb","hB"],BZ:["H","h","hb","hB"],CC:["H","h","hb","hB"],CK:["H","h","hb","hB"],CX:["H","h","hb","hB"],DG:["H","h","hb","hB"],FK:["H","h","hb","hB"],GB:["H","h","hb","hB"],GG:["H","h","hb","hB"],GI:["H","h","hb","hB"],IE:["H","h","hb","hB"],IM:["H","h","hb","hB"],IO:["H","h","hb","hB"],JE:["H","h","hb","hB"],LT:["H","h","hb","hB"],MK:["H","h","hb","hB"],MN:["H","h","hb","hB"],MS:["H","h","hb","hB"],NF:["H","h","hb","hB"],NG:["H","h","hb","hB"],NR:["H","h","hb","hB"],NU:["H","h","hb","hB"],PN:["H","h","hb","hB"],SH:["H","h","hb","hB"],SX:["H","h","hb","hB"],TA:["H","h","hb","hB"],ZA:["H","h","hb","hB"],"af-ZA":["H","h","hB","hb"],AR:["H","h","hB","hb"],CL:["H","h","hB","hb"],CR:["H","h","hB","hb"],CU:["H","h","hB","hb"],EA:["H","h","hB","hb"],"es-BO":["H","h","hB","hb"],"es-BR":["H","h","hB","hb"],"es-EC":["H","h","hB","hb"],"es-ES":["H","h","hB","hb"],"es-GQ":["H","h","hB","hb"],"es-PE":["H","h","hB","hb"],GT:["H","h","hB","hb"],HN:["H","h","hB","hb"],IC:["H","h","hB","hb"],KG:["H","h","hB","hb"],KM:["H","h","hB","hb"],LK:["H","h","hB","hb"],MA:["H","h","hB","hb"],MX:["H","h","hB","hb"],NI:["H","h","hB","hb"],PY:["H","h","hB","hb"],SV:["H","h","hB","hb"],UY:["H","h","hB","hb"],JP:["H","h","K"],AD:["H","hB"],AM:["H","hB"],AO:["H","hB"],AT:["H","hB"],AW:["H","hB"],BE:["H","hB"],BF:["H","hB"],BJ:["H","hB"],BL:["H","hB"],BR:["H","hB"],CG:["H","hB"],CI:["H","hB"],CV:["H","hB"],DE:["H","hB"],EE:["H","hB"],FR:["H","hB"],GA:["H","hB"],GF:["H","hB"],GN:["H","hB"],GP:["H","hB"],GW:["H","hB"],HR:["H","hB"],IL:["H","hB"],IT:["H","hB"],KZ:["H","hB"],MC:["H","hB"],MD:["H","hB"],MF:["H","hB"],MQ:["H","hB"],MZ:["H","hB"],NC:["H","hB"],NL:["H","hB"],PM:["H","hB"],PT:["H","hB"],RE:["H","hB"],RO:["H","hB"],SI:["H","hB"],SR:["H","hB"],ST:["H","hB"],TG:["H","hB"],TR:["H","hB"],WF:["H","hB"],YT:["H","hB"],BD:["h","hB","H"],PK:["h","hB","H"],AZ:["H","hB","h"],BA:["H","hB","h"],BG:["H","hB","h"],CH:["H","hB","h"],GE:["H","hB","h"],LI:["H","hB","h"],ME:["H","hB","h"],RS:["H","hB","h"],UA:["H","hB","h"],UZ:["H","hB","h"],XK:["H","hB","h"],AG:["h","hb","H","hB"],AU:["h","hb","H","hB"],BB:["h","hb","H","hB"],BM:["h","hb","H","hB"],BS:["h","hb","H","hB"],CA:["h","hb","H","hB"],DM:["h","hb","H","hB"],"en-001":["h","hb","H","hB"],FJ:["h","hb","H","hB"],FM:["h","hb","H","hB"],GD:["h","hb","H","hB"],GM:["h","hb","H","hB"],GU:["h","hb","H","hB"],GY:["h","hb","H","hB"],JM:["h","hb","H","hB"],KI:["h","hb","H","hB"],KN:["h","hb","H","hB"],KY:["h","hb","H","hB"],LC:["h","hb","H","hB"],LR:["h","hb","H","hB"],MH:["h","hb","H","hB"],MP:["h","hb","H","hB"],MW:["h","hb","H","hB"],NZ:["h","hb","H","hB"],SB:["h","hb","H","hB"],SG:["h","hb","H","hB"],SL:["h","hb","H","hB"],SS:["h","hb","H","hB"],SZ:["h","hb","H","hB"],TC:["h","hb","H","hB"],TT:["h","hb","H","hB"],UM:["h","hb","H","hB"],US:["h","hb","H","hB"],VC:["h","hb","H","hB"],VG:["h","hb","H","hB"],VI:["h","hb","H","hB"],ZM:["h","hb","H","hB"],BO:["H","hB","h","hb"],EC:["H","hB","h","hb"],ES:["H","hB","h","hb"],GQ:["H","hB","h","hb"],PE:["H","hB","h","hb"],AE:["h","hB","hb","H"],"ar-001":["h","hB","hb","H"],BH:["h","hB","hb","H"],DZ:["h","hB","hb","H"],EG:["h","hB","hb","H"],EH:["h","hB","hb","H"],HK:["h","hB","hb","H"],IQ:["h","hB","hb","H"],JO:["h","hB","hb","H"],KW:["h","hB","hb","H"],LB:["h","hB","hb","H"],LY:["h","hB","hb","H"],MO:["h","hB","hb","H"],MR:["h","hB","hb","H"],OM:["h","hB","hb","H"],PH:["h","hB","hb","H"],PS:["h","hB","hb","H"],QA:["h","hB","hb","H"],SA:["h","hB","hb","H"],SD:["h","hB","hb","H"],SY:["h","hB","hb","H"],TN:["h","hB","hb","H"],YE:["h","hB","hb","H"],AF:["H","hb","hB","h"],LA:["H","hb","hB","h"],CN:["H","hB","hb","h"],LV:["H","hB","hb","h"],TL:["H","hB","hb","h"],"zu-ZA":["H","hB","hb","h"],CD:["hB","H"],IR:["hB","H"],"hi-IN":["hB","h","H"],"kn-IN":["hB","h","H"],"ml-IN":["hB","h","H"],"te-IN":["hB","h","H"],KH:["hB","h","H","hb"],"ta-IN":["hB","h","hb","H"],BN:["hb","hB","h","H"],MY:["hb","hB","h","H"],ET:["hB","hb","h","H"],"gu-IN":["hB","hb","h","H"],"mr-IN":["hB","hb","h","H"],"pa-IN":["hB","hb","h","H"],TW:["hB","hb","h","H"],KE:["hB","hb","H","h"],MM:["hB","hb","H","h"],TZ:["hB","hb","H","h"],UG:["hB","hb","H","h"]};function P(e){var t=e.hourCycle;if(void 0===t&&e.hourCycles&&e.hourCycles.length&&(t=e.hourCycles[0]),t)switch(t){case"h24":return"k";case"h23":return"H";case"h12":return"h";case"h11":return"K";default:throw new Error("Invalid hourCycle")}var n,r=e.language;return"root"!==r&&(n=e.maximize().region),(D[n||""]||D[r||""]||D["".concat(r,"-001")]||D["001"])[0]}var B=new RegExp("^".concat(b.source,"*")),R=new RegExp("".concat(b.source,"*$"));function H(e,t){return{start:e,end:t}}var j=!!String.prototype.startsWith,k=!!String.fromCodePoint,F=!!Object.fromEntries,x=!!String.prototype.codePointAt,G=!!String.prototype.trimStart,Z=!!String.prototype.trimEnd,U=!!Number.isSafeInteger?Number.isSafeInteger:function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e&&Math.abs(e)<=9007199254740991},z=!0;try{z="a"===(null===(O=$("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu").exec("a"))||void 0===O?void 0:O[0])}catch(ae){z=!1}var Y,W=j?function(e,t,n){return e.startsWith(t,n)}:function(e,t,n){return e.slice(n,n+t.length)===t},V=k?String.fromCodePoint:function(){for(var e=[],t=0;ti;){if((n=e[i++])>1114111)throw RangeError(n+" is not a valid code point");r+=n<65536?String.fromCharCode(n):String.fromCharCode(55296+((n-=65536)>>10),n%1024+56320)}return r},X=F?Object.fromEntries:function(e){for(var t={},n=0,r=e;n=n)){var r,o=e.charCodeAt(t);return o<55296||o>56319||t+1===n||(r=e.charCodeAt(t+1))<56320||r>57343?o:r-56320+(o-55296<<10)+65536}},Q=G?function(e){return e.trimStart()}:function(e){return e.replace(B,"")},J=Z?function(e){return e.trimEnd()}:function(e){return e.replace(R,"")};function $(e,t){return new RegExp(e,t)}if(z){var q=$("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu");Y=function(e,t){var n;return q.lastIndex=t,null!==(n=q.exec(e)[1])&&void 0!==n?n:""}}else Y=function(e,t){for(var n=[];;){var r=K(e,t);if(void 0===r||ne(r)||re(r))break;n.push(r),t+=r>=65536?2:1}return V.apply(void 0,n)};var ee=function(){function e(e,t){void 0===t&&(t={}),this.message=e,this.position={offset:0,line:1,column:1},this.ignoreTag=!!t.ignoreTag,this.locale=t.locale,this.requiresOtherClause=!!t.requiresOtherClause,this.shouldParseSkeletons=!!t.shouldParseSkeletons}return e.prototype.parse=function(){if(0!==this.offset())throw Error("parser can only be used once");return this.parseMessage(0,"",!1)},e.prototype.parseMessage=function(e,t,n){for(var o=[];!this.isEOF();){var a=this.char();if(123===a){if((s=this.parseArgument(e,n)).err)return s;o.push(s.val)}else{if(125===a&&e>0)break;if(35!==a||"plural"!==t&&"selectordinal"!==t){if(60===a&&!this.ignoreTag&&47===this.peek()){if(n)break;return this.error(r.UNMATCHED_CLOSING_TAG,H(this.clonePosition(),this.clonePosition()))}if(60===a&&!this.ignoreTag&&te(this.peek()||0)){if((s=this.parseTag(e,t)).err)return s;o.push(s.val)}else{var s;if((s=this.parseLiteral(e,t)).err)return s;o.push(s.val)}}else{var c=this.clonePosition();this.bump(),o.push({type:i.pound,location:H(c,this.clonePosition())})}}}return{val:o,err:null}},e.prototype.parseTag=function(e,t){var n=this.clonePosition();this.bump();var o=this.parseTagName();if(this.bumpSpace(),this.bumpIf("/>"))return{val:{type:i.literal,value:"<".concat(o,"/>"),location:H(n,this.clonePosition())},err:null};if(this.bumpIf(">")){var a=this.parseMessage(e+1,t,!0);if(a.err)return a;var s=a.val,c=this.clonePosition();if(this.bumpIf("")?{val:{type:i.tag,value:o,children:s,location:H(n,this.clonePosition())},err:null}:this.error(r.INVALID_TAG,H(c,this.clonePosition())))}return this.error(r.UNCLOSED_TAG,H(n,this.clonePosition()))}return this.error(r.INVALID_TAG,H(n,this.clonePosition()))},e.prototype.parseTagName=function(){var e,t=this.offset();for(this.bump();!this.isEOF()&&(45===(e=this.char())||46===e||e>=48&&e<=57||95===e||e>=97&&e<=122||e>=65&&e<=90||183==e||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8255&&e<=8256||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039);)this.bump();return this.message.slice(t,this.offset())},e.prototype.parseLiteral=function(e,t){for(var n=this.clonePosition(),r="";;){var o=this.tryParseQuote(t);if(o)r+=o;else{var a=this.tryParseUnquoted(e,t);if(a)r+=a;else{var s=this.tryParseLeftAngleBracket();if(!s)break;r+=s}}}var c=H(n,this.clonePosition());return{val:{type:i.literal,value:r,location:c},err:null}},e.prototype.tryParseLeftAngleBracket=function(){return this.isEOF()||60!==this.char()||!this.ignoreTag&&(te(e=this.peek()||0)||47===e)?null:(this.bump(),"<");var e},e.prototype.tryParseQuote=function(e){if(this.isEOF()||39!==this.char())return null;switch(this.peek()){case 39:return this.bump(),this.bump(),"'";case 123:case 60:case 62:case 125:break;case 35:if("plural"===e||"selectordinal"===e)break;return null;default:return null}this.bump();var t=[this.char()];for(this.bump();!this.isEOF();){var n=this.char();if(39===n){if(39!==this.peek()){this.bump();break}t.push(39),this.bump()}else t.push(n);this.bump()}return V.apply(void 0,t)},e.prototype.tryParseUnquoted=function(e,t){if(this.isEOF())return null;var n=this.char();return 60===n||123===n||35===n&&("plural"===t||"selectordinal"===t)||125===n&&e>0?null:(this.bump(),V(n))},e.prototype.parseArgument=function(e,t){var n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition()));if(125===this.char())return this.bump(),this.error(r.EMPTY_ARGUMENT,H(n,this.clonePosition()));var o=this.parseIdentifierIfPossible().value;if(!o)return this.error(r.MALFORMED_ARGUMENT,H(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:i.argument,value:o,location:H(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition())):this.parseArgumentOptions(e,t,o,n);default:return this.error(r.MALFORMED_ARGUMENT,H(n,this.clonePosition()))}},e.prototype.parseIdentifierIfPossible=function(){var e=this.clonePosition(),t=this.offset(),n=Y(this.message,t),r=t+n.length;return this.bumpTo(r),{value:n,location:H(e,this.clonePosition())}},e.prototype.parseArgumentOptions=function(e,t,n,s){var c,u=this.clonePosition(),l=this.parseIdentifierIfPossible().value,h=this.clonePosition();switch(l){case"":return this.error(r.EXPECT_ARGUMENT_TYPE,H(u,h));case"number":case"date":case"time":this.bumpSpace();var f=null;if(this.bumpIf(",")){this.bumpSpace();var m=this.clonePosition();if((E=this.parseSimpleArgStyleIfPossible()).err)return E;if(0===(y=J(E.val)).length)return this.error(r.EXPECT_ARGUMENT_STYLE,H(this.clonePosition(),this.clonePosition()));f={style:y,styleLocation:H(m,this.clonePosition())}}if((C=this.tryParseArgumentClose(s)).err)return C;var p=H(s,this.clonePosition());if(f&&W(null==f?void 0:f.style,"::",0)){var g=Q(f.style.slice(2));if("number"===l)return(E=this.parseNumberSkeletonFromString(g,f.styleLocation)).err?E:{val:{type:i.number,value:n,location:p,style:E.val},err:null};if(0===g.length)return this.error(r.EXPECT_DATE_TIME_SKELETON,p);var d=g;this.locale&&(d=function(e,t){for(var n="",r=0;r>1),c=P(t);for("H"!=c&&"k"!=c||(s=0);s-- >0;)n+="a";for(;a-- >0;)n=c+n}else n+="J"===o?"H":o}return n}(g,this.locale));var y={type:a.dateTime,pattern:d,location:f.styleLocation,parsedOptions:this.shouldParseSkeletons?v(d):{}};return{val:{type:"date"===l?i.date:i.time,value:n,location:p,style:y},err:null}}return{val:{type:"number"===l?i.number:"date"===l?i.date:i.time,value:n,location:p,style:null!==(c=null==f?void 0:f.style)&&void 0!==c?c:null},err:null};case"plural":case"selectordinal":case"select":var b=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(","))return this.error(r.EXPECT_SELECT_ARGUMENT_OPTIONS,H(b,(0,o.pi)({},b)));this.bumpSpace();var I=this.parseIdentifierIfPossible(),A=0;if("select"!==l&&"offset"===I.value){if(!this.bumpIf(":"))return this.error(r.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,H(this.clonePosition(),this.clonePosition()));var E;if(this.bumpSpace(),(E=this.tryParseDecimalInteger(r.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,r.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE)).err)return E;this.bumpSpace(),I=this.parseIdentifierIfPossible(),A=E.val}var C,T=this.tryParsePluralOrSelectOptions(e,l,t,I);if(T.err)return T;if((C=this.tryParseArgumentClose(s)).err)return C;var _=H(s,this.clonePosition());return"select"===l?{val:{type:i.select,value:n,options:X(T.val),location:_},err:null}:{val:{type:i.plural,value:n,options:X(T.val),offset:A,pluralType:"plural"===l?"cardinal":"ordinal",location:_},err:null};default:return this.error(r.INVALID_ARGUMENT_TYPE,H(u,h))}},e.prototype.tryParseArgumentClose=function(e){return this.isEOF()||125!==this.char()?this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(e,this.clonePosition())):(this.bump(),{val:!0,err:null})},e.prototype.parseSimpleArgStyleIfPossible=function(){for(var e=0,t=this.clonePosition();!this.isEOF();){switch(this.char()){case 39:this.bump();var n=this.clonePosition();if(!this.bumpUntil("'"))return this.error(r.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE,H(n,this.clonePosition()));this.bump();break;case 123:e+=1,this.bump();break;case 125:if(!(e>0))return{val:this.message.slice(t.offset,this.offset()),err:null};e-=1;break;default:this.bump()}}return{val:this.message.slice(t.offset,this.offset()),err:null}},e.prototype.parseNumberSkeletonFromString=function(e,t){var n=[];try{n=function(e){if(0===e.length)throw new Error("Number skeleton cannot be empty");for(var t=[],n=0,r=e.split(A).filter((function(e){return e.length>0}));n=48&&a<=57))break;o=!0,i=10*i+(a-48),this.bump()}var s=H(r,this.clonePosition());return o?U(i*=n)?{val:i,err:null}:this.error(t,s):this.error(e,s)},e.prototype.offset=function(){return this.position.offset},e.prototype.isEOF=function(){return this.offset()===this.message.length},e.prototype.clonePosition=function(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}},e.prototype.char=function(){var e=this.position.offset;if(e>=this.message.length)throw Error("out of bound");var t=K(this.message,e);if(void 0===t)throw Error("Offset ".concat(e," is at invalid UTF-16 code unit boundary"));return t},e.prototype.error=function(e,t){return{val:null,err:{kind:e,message:this.message,location:t}}},e.prototype.bump=function(){if(!this.isEOF()){var e=this.char();10===e?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=e<65536?1:2)}},e.prototype.bumpIf=function(e){if(W(this.message,e,this.offset())){for(var t=0;t=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)},e.prototype.bumpTo=function(e){if(this.offset()>e)throw Error("targetOffset ".concat(e," must be greater than or equal to the current offset ").concat(this.offset()));for(e=Math.min(e,this.message.length);;){var t=this.offset();if(t===e)break;if(t>e)throw Error("targetOffset ".concat(e," is at invalid UTF-16 code unit boundary"));if(this.bump(),this.isEOF())break}},e.prototype.bumpSpace=function(){for(;!this.isEOF()&&ne(this.char());)this.bump()},e.prototype.peek=function(){if(this.isEOF())return null;var e=this.char(),t=this.offset(),n=this.message.charCodeAt(t+(e>=65536?2:1));return null!=n?n:null},e}();function te(e){return e>=97&&e<=122||e>=65&&e<=90}function ne(e){return e>=9&&e<=13||32===e||133===e||e>=8206&&e<=8207||8232===e||8233===e}function re(e){return e>=33&&e<=35||36===e||e>=37&&e<=39||40===e||41===e||42===e||43===e||44===e||45===e||e>=46&&e<=47||e>=58&&e<=59||e>=60&&e<=62||e>=63&&e<=64||91===e||92===e||93===e||94===e||96===e||123===e||124===e||125===e||126===e||161===e||e>=162&&e<=165||166===e||167===e||169===e||171===e||172===e||174===e||176===e||177===e||182===e||187===e||191===e||215===e||247===e||e>=8208&&e<=8213||e>=8214&&e<=8215||8216===e||8217===e||8218===e||e>=8219&&e<=8220||8221===e||8222===e||8223===e||e>=8224&&e<=8231||e>=8240&&e<=8248||8249===e||8250===e||e>=8251&&e<=8254||e>=8257&&e<=8259||8260===e||8261===e||8262===e||e>=8263&&e<=8273||8274===e||8275===e||e>=8277&&e<=8286||e>=8592&&e<=8596||e>=8597&&e<=8601||e>=8602&&e<=8603||e>=8604&&e<=8607||8608===e||e>=8609&&e<=8610||8611===e||e>=8612&&e<=8613||8614===e||e>=8615&&e<=8621||8622===e||e>=8623&&e<=8653||e>=8654&&e<=8655||e>=8656&&e<=8657||8658===e||8659===e||8660===e||e>=8661&&e<=8691||e>=8692&&e<=8959||e>=8960&&e<=8967||8968===e||8969===e||8970===e||8971===e||e>=8972&&e<=8991||e>=8992&&e<=8993||e>=8994&&e<=9e3||9001===e||9002===e||e>=9003&&e<=9083||9084===e||e>=9085&&e<=9114||e>=9115&&e<=9139||e>=9140&&e<=9179||e>=9180&&e<=9185||e>=9186&&e<=9254||e>=9255&&e<=9279||e>=9280&&e<=9290||e>=9291&&e<=9311||e>=9472&&e<=9654||9655===e||e>=9656&&e<=9664||9665===e||e>=9666&&e<=9719||e>=9720&&e<=9727||e>=9728&&e<=9838||9839===e||e>=9840&&e<=10087||10088===e||10089===e||10090===e||10091===e||10092===e||10093===e||10094===e||10095===e||10096===e||10097===e||10098===e||10099===e||10100===e||10101===e||e>=10132&&e<=10175||e>=10176&&e<=10180||10181===e||10182===e||e>=10183&&e<=10213||10214===e||10215===e||10216===e||10217===e||10218===e||10219===e||10220===e||10221===e||10222===e||10223===e||e>=10224&&e<=10239||e>=10240&&e<=10495||e>=10496&&e<=10626||10627===e||10628===e||10629===e||10630===e||10631===e||10632===e||10633===e||10634===e||10635===e||10636===e||10637===e||10638===e||10639===e||10640===e||10641===e||10642===e||10643===e||10644===e||10645===e||10646===e||10647===e||10648===e||e>=10649&&e<=10711||10712===e||10713===e||10714===e||10715===e||e>=10716&&e<=10747||10748===e||10749===e||e>=10750&&e<=11007||e>=11008&&e<=11055||e>=11056&&e<=11076||e>=11077&&e<=11078||e>=11079&&e<=11084||e>=11085&&e<=11123||e>=11124&&e<=11125||e>=11126&&e<=11157||11158===e||e>=11159&&e<=11263||e>=11776&&e<=11777||11778===e||11779===e||11780===e||11781===e||e>=11782&&e<=11784||11785===e||11786===e||11787===e||11788===e||11789===e||e>=11790&&e<=11798||11799===e||e>=11800&&e<=11801||11802===e||11803===e||11804===e||11805===e||e>=11806&&e<=11807||11808===e||11809===e||11810===e||11811===e||11812===e||11813===e||11814===e||11815===e||11816===e||11817===e||e>=11818&&e<=11822||11823===e||e>=11824&&e<=11833||e>=11834&&e<=11835||e>=11836&&e<=11839||11840===e||11841===e||11842===e||e>=11843&&e<=11855||e>=11856&&e<=11857||11858===e||e>=11859&&e<=11903||e>=12289&&e<=12291||12296===e||12297===e||12298===e||12299===e||12300===e||12301===e||12302===e||12303===e||12304===e||12305===e||e>=12306&&e<=12307||12308===e||12309===e||12310===e||12311===e||12312===e||12313===e||12314===e||12315===e||12316===e||12317===e||e>=12318&&e<=12319||12320===e||12336===e||64830===e||64831===e||e>=65093&&e<=65094}function oe(e){e.forEach((function(e){if(delete e.location,f(e)||m(e))for(var t in e.options)delete e.options[t].location,oe(e.options[t].value);else u(e)&&d(e.style)||(l(e)||h(e))&&y(e.style)?delete e.style.location:g(e)&&oe(e.children)}))}function ie(e,t){void 0===t&&(t={}),t=(0,o.pi)({shouldParseSkeletons:!0,requiresOtherClause:!0},t);var n=new ee(e,t).parse();if(n.err){var i=SyntaxError(r[n.err.kind]);throw i.location=n.err.location,i.originalMessage=n.err.message,i}return(null==t?void 0:t.captureLocation)||oe(n.val),n.val}},7309:function(e,t,n){"use strict";n.d(t,{$6:function(){return h},OV:function(){return s},Qe:function(){return u},Rw:function(){return i},X9:function(){return l},bc:function(){return r},gb:function(){return c},wI:function(){return a}});var r,o=n(5542);!function(e){e.FORMAT_ERROR="FORMAT_ERROR",e.UNSUPPORTED_FORMATTER="UNSUPPORTED_FORMATTER",e.INVALID_CONFIG="INVALID_CONFIG",e.MISSING_DATA="MISSING_DATA",e.MISSING_TRANSLATION="MISSING_TRANSLATION"}(r||(r={}));var i=function(e){function t(n,r,o){var i=this,a=o?o instanceof Error?o:new Error(String(o)):void 0;return(i=e.call(this,"[@formatjs/intl Error ".concat(n,"] ").concat(r,"\n").concat(a?"\n".concat(a.message,"\n").concat(a.stack):""))||this).code=n,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(i,t),i}return(0,o.ZT)(t,e),t}(Error),a=function(e){function t(t,n){return e.call(this,r.UNSUPPORTED_FORMATTER,t,n)||this}return(0,o.ZT)(t,e),t}(i),s=function(e){function t(t,n){return e.call(this,r.INVALID_CONFIG,t,n)||this}return(0,o.ZT)(t,e),t}(i),c=function(e){function t(t,n){return e.call(this,r.MISSING_DATA,t,n)||this}return(0,o.ZT)(t,e),t}(i),u=function(e){function t(t,n,o){return e.call(this,r.FORMAT_ERROR,"".concat(t,"\nLocale: ").concat(n,"\n"),o)||this}return(0,o.ZT)(t,e),t}(i),l=function(e){function t(t,n,r,o){var i=e.call(this,"".concat(t,"\nMessageID: ").concat(null==r?void 0:r.id,"\nDefault Message: ").concat(null==r?void 0:r.defaultMessage,"\nDescription: ").concat(null==r?void 0:r.description,"\n"),n,o)||this;return i.descriptor=r,i}return(0,o.ZT)(t,e),t}(u),h=function(e){function t(t,n){var o=e.call(this,r.MISSING_TRANSLATION,'Missing message: "'.concat(t.id,'" for locale "').concat(n,'", using ').concat(t.defaultMessage?"default message (".concat("string"==typeof t.defaultMessage?t.defaultMessage:t.defaultMessage.map((function(e){var t;return null!==(t=e.value)&&void 0!==t?t:JSON.stringify(e)})).join(),")"):"id"," as fallback."))||this;return o.descriptor=t,o}return(0,o.ZT)(t,e),t}(i)},3167:function(e,t,n){"use strict";n.d(t,{L6:function(){return s},Sn:function(){return u},TB:function(){return f},Z0:function(){return c},ax:function(){return h}});var r=n(5542),o=n(1875),i=n(8770),a=n(7309);function s(e,t,n){return void 0===n&&(n={}),t.reduce((function(t,r){return r in e?t[r]=e[r]:r in n&&(t[r]=n[r]),t}),{})}var c={formats:{},messages:{},timeZone:void 0,defaultLocale:"en",defaultFormats:{},fallbackOnEmptyString:!0,onError:function(e){0},onWarn:function(e){0}};function u(){return{dateTime:{},number:{},message:{},relativeTime:{},pluralRules:{},list:{},displayNames:{}}}function l(e){return{create:function(){return{get:function(t){return e[t]},set:function(t,n){e[t]=n}}}}}function h(e){void 0===e&&(e={dateTime:{},number:{},message:{},relativeTime:{},pluralRules:{},list:{},displayNames:{}});var t=Intl.RelativeTimeFormat,n=Intl.ListFormat,a=Intl.DisplayNames,s=(0,i.Z)((function(){for(var e,t=[],n=0;n0?new Intl.Locale(t[0]):new Intl.Locale("string"==typeof e?e:e[0])}},e.__parse=o.Qc,e.formats={number:{integer:{maximumFractionDigits:0},currency:{style:"currency"},percent:{style:"percent"}},date:{short:{month:"numeric",day:"numeric",year:"2-digit"},medium:{month:"short",day:"numeric",year:"numeric"},long:{month:"long",day:"numeric",year:"numeric"},full:{weekday:"long",month:"long",day:"numeric",year:"numeric"}},time:{short:{hour:"numeric",minute:"numeric"},medium:{hour:"numeric",minute:"numeric",second:"numeric"},long:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"},full:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"}}},e}()},1694:function(e,t,n){"use strict";n.d(t,{C8:function(){return a},HR:function(){return c},YR:function(){return s},jK:function(){return r},u_:function(){return i}});var r,o=n(5542);!function(e){e.MISSING_VALUE="MISSING_VALUE",e.INVALID_VALUE="INVALID_VALUE",e.MISSING_INTL_API="MISSING_INTL_API"}(r||(r={}));var i=function(e){function t(t,n,r){var o=e.call(this,t)||this;return o.code=n,o.originalMessage=r,o}return(0,o.ZT)(t,e),t.prototype.toString=function(){return"[formatjs Error: ".concat(this.code,"] ").concat(this.message)},t}(Error),a=function(e){function t(t,n,o,i){return e.call(this,'Invalid values for "'.concat(t,'": "').concat(n,'". Options are "').concat(Object.keys(o).join('", "'),'"'),r.INVALID_VALUE,i)||this}return(0,o.ZT)(t,e),t}(i),s=function(e){function t(t,n,o){return e.call(this,'Value for "'.concat(t,'" must be of type ').concat(n),r.INVALID_VALUE,o)||this}return(0,o.ZT)(t,e),t}(i),c=function(e){function t(t,n){return e.call(this,'The intl string context variable "'.concat(t,'" was not provided to the string "').concat(n,'"'),r.MISSING_VALUE,n)||this}return(0,o.ZT)(t,e),t}(i)},3528:function(e,t,n){"use strict";n.d(t,{FK:function(){return s},Gt:function(){return a},du:function(){return r}});var r,o=n(7846),i=n(1694);function a(e){return"function"==typeof e}function s(e,t,n,c,u,l,h){if(1===e.length&&(0,o.O4)(e[0]))return[{type:r.literal,value:e[0].value}];for(var f=[],m=0,p=e;m=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n},W=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t},V=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return!1===t?String(e):String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},X=function(e){var t=q(e,v.TITLE),n=q(e,j);if(n&&t)return n.replace(/%s/g,(function(){return Array.isArray(t)?t.join(""):t}));var r=q(e,P);return t||r||void 0},K=function(e){return q(e,H)||function(){}},Q=function(e,t){return t.filter((function(t){return void 0!==t[e]})).map((function(t){return t[e]})).reduce((function(e,t){return z({},e,t)}),{})},J=function(e,t){return t.filter((function(e){return void 0!==e[v.BASE]})).map((function(e){return e[v.BASE]})).reverse().reduce((function(t,n){if(!t.length)for(var r=Object.keys(n),o=0;o=0;n--){var r=e[n];if(r.hasOwnProperty(t))return r[t]}return null},ee=(r=Date.now(),function(e){var t=Date.now();t-r>16?(r=t,e(t)):setTimeout((function(){ee(e)}),0)}),te=function(e){return clearTimeout(e)},ne="undefined"!=typeof window?window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||ee:n.g.requestAnimationFrame||ee,re="undefined"!=typeof window?window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||te:n.g.cancelAnimationFrame||te,oe=function(e){return d&&"function"==typeof d.warn&&d.warn(e)},ie=null,ae=function(e,t){var n=e.baseTag,r=e.bodyAttributes,o=e.htmlAttributes,i=e.linkTags,a=e.metaTags,s=e.noscriptTags,c=e.onChangeClientState,u=e.scriptTags,l=e.styleTags,h=e.title,f=e.titleAttributes;ue(v.BODY,r),ue(v.HTML,o),ce(h,f);var m={baseTag:le(v.BASE,n),linkTags:le(v.LINK,i),metaTags:le(v.META,a),noscriptTags:le(v.NOSCRIPT,s),scriptTags:le(v.SCRIPT,u),styleTags:le(v.STYLE,l)},p={},g={};Object.keys(m).forEach((function(e){var t=m[e],n=t.newTags,r=t.oldTags;n.length&&(p[e]=n),r.length&&(g[e]=m[e].oldTags)})),t&&t(),c(e,p,g)},se=function(e){return Array.isArray(e)?e.join(""):e},ce=function(e,t){void 0!==e&&document.title!==e&&(document.title=se(e)),ue(v.TITLE,t)},ue=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute(x),o=r?r.split(","):[],i=[].concat(o),a=Object.keys(t),s=0;s=0;h--)n.removeAttribute(i[h]);o.length===i.length?n.removeAttribute(x):n.getAttribute(x)!==a.join(",")&&n.setAttribute(x,a.join(","))}},le=function(e,t){var n=document.head||document.querySelector(v.HEAD),r=n.querySelectorAll(e+"["+"data-react-helmet]"),o=Array.prototype.slice.call(r),i=[],a=void 0;return t&&t.length&&t.forEach((function(t){var n=document.createElement(e);for(var r in t)if(t.hasOwnProperty(r))if(r===_)n.innerHTML=t.innerHTML;else if(r===E)n.styleSheet?n.styleSheet.cssText=t.cssText:n.appendChild(document.createTextNode(t.cssText));else{var s=void 0===t[r]?"":t[r];n.setAttribute(r,s)}n.setAttribute(x,"true"),o.some((function(e,t){return a=t,n.isEqualNode(e)}))?o.splice(a,1):i.push(n)})),o.forEach((function(e){return e.parentNode.removeChild(e)})),i.forEach((function(e){return n.appendChild(e)})),{oldTags:o,newTags:i}},he=function(e){return Object.keys(e).reduce((function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r}),"")},fe=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.keys(e).reduce((function(t,n){return t[D[n]||n]=e[n],t}),t)},me=function(e,t,n){switch(e){case v.TITLE:return{toComponent:function(){return e=t.title,n=t.titleAttributes,(r={key:e})[x]=!0,o=fe(n,r),[m.createElement(v.TITLE,o,e)];var e,n,r,o},toString:function(){return function(e,t,n,r){var o=he(n),i=se(t);return o?"<"+e+' data-react-helmet="true" '+o+">"+V(i,r)+"":"<"+e+' data-react-helmet="true">'+V(i,r)+""}(e,t.title,t.titleAttributes,n)}};case y:case b:return{toComponent:function(){return fe(t)},toString:function(){return he(t)}};default:return{toComponent:function(){return function(e,t){return t.map((function(t,n){var r,o=((r={key:n})[x]=!0,r);return Object.keys(t).forEach((function(e){var n=D[e]||e;if(n===_||n===E){var r=t.innerHTML||t.cssText;o.dangerouslySetInnerHTML={__html:r}}else o[n]=t[e]})),m.createElement(e,o)}))}(e,t)},toString:function(){return function(e,t,n){return t.reduce((function(t,r){var o=Object.keys(r).filter((function(e){return!(e===_||e===E)})).reduce((function(e,t){var o=void 0===r[t]?t:t+'="'+V(r[t],n)+'"';return e?e+" "+o:o}),""),i=r.innerHTML||r.cssText||"",a=-1===F.indexOf(e);return t+"<"+e+' data-react-helmet="true" '+o+(a?"/>":">"+i+"")}),"")}(e,t,n)}}}},pe=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,o=e.htmlAttributes,i=e.linkTags,a=e.metaTags,s=e.noscriptTags,c=e.scriptTags,u=e.styleTags,l=e.title,h=void 0===l?"":l,f=e.titleAttributes;return{base:me(v.BASE,t,r),bodyAttributes:me(y,n,r),htmlAttributes:me(b,o,r),link:me(v.LINK,i,r),meta:me(v.META,a,r),noscript:me(v.NOSCRIPT,s,r),script:me(v.SCRIPT,c,r),style:me(v.STYLE,u,r),title:me(v.TITLE,{title:h,titleAttributes:f},r)}},ge=l()((function(e){return{baseTag:J([C,O],e),bodyAttributes:Q(y,e),defer:q(e,B),encode:q(e,R),htmlAttributes:Q(b,e),linkTags:$(v.LINK,[L,C],e),metaTags:$(v.META,[M,A,T,N,w],e),noscriptTags:$(v.NOSCRIPT,[_],e),onChangeClientState:K(e),scriptTags:$(v.SCRIPT,[S,_],e),styleTags:$(v.STYLE,[E],e),title:X(e),titleAttributes:Q(I,e)}}),(function(e){ie&&re(ie),e.defer?ie=ne((function(){ae(e,(function(){ie=null}))})):(ae(e),ie=null)}),pe)((function(){return null})),de=(o=ge,a=i=function(e){function t(){return Z(this,t),W(this,e.apply(this,arguments))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.shouldComponentUpdate=function(e){return!f()(this.props,e)},t.prototype.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case v.SCRIPT:case v.NOSCRIPT:return{innerHTML:t};case v.STYLE:return{cssText:t}}throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")},t.prototype.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren,o=e.newChildProps,i=e.nestedChildren;return z({},r,((t={})[n.type]=[].concat(r[n.type]||[],[z({},o,this.mapNestedChildrenToProps(n,i))]),t))},t.prototype.mapObjectTypeChildren=function(e){var t,n,r=e.child,o=e.newProps,i=e.newChildProps,a=e.nestedChildren;switch(r.type){case v.TITLE:return z({},o,((t={})[r.type]=a,t.titleAttributes=z({},i),t));case v.BODY:return z({},o,{bodyAttributes:z({},i)});case v.HTML:return z({},o,{htmlAttributes:z({},i)})}return z({},o,((n={})[r.type]=z({},i),n))},t.prototype.mapArrayTypeChildrenToProps=function(e,t){var n=z({},t);return Object.keys(e).forEach((function(t){var r;n=z({},n,((r={})[t]=e[t],r))})),n},t.prototype.warnOnInvalidChildren=function(e,t){return!0},t.prototype.mapChildrenToProps=function(e,t){var n=this,r={};return m.Children.forEach(e,(function(e){if(e&&e.props){var o=e.props,i=o.children,a=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.keys(e).reduce((function(t,n){return t[k[n]||n]=e[n],t}),t)}(Y(o,["children"]));switch(n.warnOnInvalidChildren(e,i),e.type){case v.LINK:case v.META:case v.NOSCRIPT:case v.SCRIPT:case v.STYLE:r=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:r,newChildProps:a,nestedChildren:i});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:a,nestedChildren:i})}}})),t=this.mapArrayTypeChildrenToProps(r,t)},t.prototype.render=function(){var e=this.props,t=e.children,n=Y(e,["children"]),r=z({},n);return t&&(r=this.mapChildrenToProps(t,r)),m.createElement(o,r)},U(t,null,[{key:"canUseDOM",set:function(e){o.canUseDOM=e}}]),t}(m.Component),i.propTypes={base:c().object,bodyAttributes:c().object,children:c().oneOfType([c().arrayOf(c().node),c().node]),defaultTitle:c().string,defer:c().bool,encodeSpecialCharacters:c().bool,htmlAttributes:c().object,link:c().arrayOf(c().object),meta:c().arrayOf(c().object),noscript:c().arrayOf(c().object),onChangeClientState:c().func,script:c().arrayOf(c().object),style:c().arrayOf(c().object),title:c().string,titleAttributes:c().object,titleTemplate:c().string},i.defaultProps={defer:!0,encodeSpecialCharacters:!0},i.peek=o.peek,i.rewind=function(){var e=o.rewind();return e||(e=pe({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}})),e},a);de.renderStatic=de.rewind,t.Z=de},6911:function(e,t,n){"use strict";n.d(t,{_y:function(){return a},zt:function(){return i}});var r=n(2784);n(9703);var o=r.createContext(null),i=(o.Consumer,o.Provider),a=o},6563:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(2784),o=n(6911),i=n(3087);function a(){var e=r.useContext(o._y);return(0,i.lq)(e),e}},3087:function(e,t,n){"use strict";n.d(t,{Z0:function(){return c},dt:function(){return u},lq:function(){return s},wU:function(){return l}});var r=n(5542),o=n(2784),i=n(8349),a=n(3167);function s(e){(0,i.kG)(e,"[React Intl] Could not find required `intl` object. needs to exist in the component ancestry.")}var c=(0,r.pi)((0,r.pi)({},a.Z0),{textComponent:o.Fragment});function u(e){return function(t){return e(o.Children.toArray(t))}}function l(e,t){if(e===t)return!0;if(!e||!t)return!1;var n=Object.keys(e),r=Object.keys(t),o=n.length;if(r.length!==o)return!1;for(var i=0;i1&&window.pageYOffset>n;n=window.pageYOffset,r?(e.classList.add("down"),e.classList.remove("up"),null==t||t.classList.add("hidden")):(e.classList.add("up"),e.classList.remove("down"),null==t||t.classList.remove("hidden"))}};document.removeEventListener("scroll",r,{capture:!0,passive:!0}),document.addEventListener("scroll",r,{capture:!0,passive:!0})}(),window.docsearch&&u(),!document.getElementById("algolia-search")){var e=document.createElement("script");e.id="algolia-search";var t=document.createElement("link");e.src=(0,o.withPrefix)("/js/docsearch.js"),e.async=!0,e.onload=function(){var e;window.docsearch&&(u(),t.rel="stylesheet",t.href=(0,o.withPrefix)("/css/docsearch.css"),t.type="text/css",document.body.appendChild(t),null===(e=document.getElementById("search-form"))||void 0===e||e.classList.add("search-enabled"))},document.body.appendChild(e)}}),[]),r.createElement("header",{dir:"ltr"},r.createElement("a",{className:"skip-to-main",href:"#site-content",tabIndex:0},t("skip_to_content")),r.createElement("div",{id:"top-menu",className:"up"},r.createElement("div",{className:"left below-small"},r.createElement(c,{id:"home-page-logo",to:"/","aria-label":"Lingua Franca Home Page"},r.createElement("picture",null,r.createElement("source",{media:"(min-width: 600px)",srcSet:n(2285).Z}),r.createElement("img",{src:n(6732).Z}))),r.createElement("nav",{role:"navigation"},r.createElement("ul",null,r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/download"},t("nav_download"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/docs/"},t("nav_documentation_short"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/docs/handbook/overview"},t("nav_handbook"))),r.createElement("li",{className:"nav-item"},r.createElement(c,{to:"/community"},t("nav_community")))))),r.createElement("div",{className:"right above-small"},r.createElement("div",{className:"search-section"},r.createElement("div",{className:"nav-item"},r.createElement("form",{id:"search-form",className:"search top-nav",role:"search"},r.createElement("svg",{fill:"none",height:"16",viewBox:"0 0 16 6",width:"20",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"m10.5 0c.5052 0 .9922.0651042 1.4609.195312.4688.130209.9063.315105 1.3125.554688.4063.239583.7761.52865 1.1094.86719.3386.33333.6276.70312.8672 1.10937s.4245.84375.5547 1.3125.1953.95573.1953 1.46094-.0651.99219-.1953 1.46094-.3151.90625-.5547 1.3125-.5286.77864-.8672 1.11718c-.3333.33334-.7031.61978-1.1094.85938-.4062.2396-.8437.4245-1.3125.5547-.4687.1302-.9557.1953-1.4609.1953-.65104 0-1.27604-.1094-1.875-.3281-.59375-.2188-1.14062-.5339-1.64062-.94534l-6.132818 6.12504c-.098958.0989-.216145.1484-.351562.1484s-.252604-.0495-.351562-.1484c-.0989588-.099-.148438-.2162-.148438-.3516s.0494792-.2526.148438-.3516l6.125002-6.13278c-.41146-.5-.72656-1.04687-.94532-1.64062-.21874-.59896-.32812-1.22396-.32812-1.875 0-.50521.0651-.99219.19531-1.46094s.31511-.90625.55469-1.3125.52604-.77604.85938-1.10937c.33854-.33854.71093-.627607 1.11718-.86719s.84375-.424479 1.3125-.554688c.46875-.1302078.95573-.195312 1.46094-.195312zm0 10c.6198 0 1.2031-.11719 1.75-.35156.5469-.23959 1.0234-.5625 1.4297-.96875.4062-.40625.7265-.88281.9609-1.42969.2396-.54688.3594-1.13021.3594-1.75s-.1198-1.20312-.3594-1.75c-.2344-.54688-.5547-1.02344-.9609-1.42969-.4063-.40625-.8828-.72656-1.4297-.96093-.5469-.23959-1.1302-.35938-1.75-.35938-.61979 0-1.20312.11979-1.75.35938-.54688.23437-1.02344.55468-1.42969.96093s-.72916.88281-.96875 1.42969c-.23437.54688-.35156 1.13021-.35156 1.75s.11719 1.20312.35156 1.75c.23959.54688.5625 1.02344.96875 1.42969s.88281.72916 1.42969.96875c.54688.23437 1.13021.35156 1.75.35156z",fill:"#fff"})),r.createElement("span",null,r.createElement("input",{id:"search-box-top",type:"search",placeholder:t("nav_search_placeholder"),"aria-label":t("nav_search_aria")})),r.createElement("input",{type:"submit",style:{display:"none"}})))))),r.createElement("div",{id:"site-content"}))},u=n(8760),l=function(){document.documentElement.classList.remove("light-theme"),document.documentElement.classList.add("dark-theme")},h=function(){document.documentElement.classList.remove("dark-theme"),document.documentElement.classList.add("light-theme")},f=function(){var e=(0,s.D)((0,i.Z)()),t="undefined"!=typeof window&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,n=u.e&&localStorage.getItem("force-color-theme")||"system",o=(0,r.useState)(n),a=o[0],c=o[1],f=u.e&&localStorage.getItem("force-font")||"cascadia",m=(0,r.useState)(f),p=m[0],g=m[1];return r.createElement("section",{id:"customize"},r.createElement("article",null,r.createElement("h3",null,e("footer_customize")),r.createElement("label",null,r.createElement("p",null,e("footer_site_colours"),":"),r.createElement("div",{className:"switch-wrap"},r.createElement("select",{name:"colours",value:a,onChange:function(e){"system"===e.target.value?(t?l():h(),u.e&&localStorage.removeItem("force-color-theme")):"force-light"===e.target.value?(h(),u.e&&localStorage.setItem("force-color-theme","force-light")):"force-dark"===e.target.value&&(l(),u.e&&localStorage.setItem("force-color-theme","force-dark")),c(e.target.value)}},r.createElement("option",{value:"system"},e("footer_site_colours_options_system")),r.createElement("option",{value:"force-light"},e("footer_site_colours_options_always_light")),r.createElement("option",{value:"force-dark"},e("footer_site_colours_options_always_dark"))))),r.createElement("label",null,r.createElement("p",null,e("footer_code_font"),":"),r.createElement("div",{className:"switch-wrap"},r.createElement("select",{name:"font",value:p,onChange:function(e){var t,n;localStorage.setItem("force-font",e.target.value),t=e.target.value,(n=f)&&document.documentElement.classList.remove("font-"+n),document.documentElement.classList.add("font-"+t),g(e.target.value)}},r.createElement("option",{value:"cascadia"},"Cascadia"),r.createElement("option",{value:"cascadia-ligatures"},"Cascadia (ligatures)"),r.createElement("option",{value:"consolas"},"Consolas"),r.createElement("option",{value:"dank-mono"},"Dank Mono"),r.createElement("option",{value:"fira-code"},"Fira Code"),r.createElement("option",{value:"jetbrains-mono"},"JetBrains Mono"),r.createElement("option",{value:"open-dyslexic"},"OpenDyslexic"),r.createElement("option",{value:"sf-mono"},"SF Mono"),r.createElement("option",{value:"source-code-pro"},"Source Code Pro"))))))},m=[{title:"Get Started",url:"/docs/handbook/overview"},{title:"Download",url:"/download"},{title:"Why Lingua Franca",url:"/"},{title:"Publications",url:"/publications-and-presentations"}],p=[{title:"Get Help",url:"/community"},{title:"GitHub Repo",url:"https://github.com/lf-lang/lingua-franca"},{title:"@thelflang",url:"https://twitter.com/thelflang"},{title:"Web Repo",url:"https://github.com/lf-lang/website-lingua-franca"},{title:"Zulip",url:"https://zulip.lf-lang.org"}],g=function(e){var t=m.filter((function(e){return!e.url.includes("#show-examples")})),o=(0,a.i)(e.lang);e.suppressDocRecommendations;return r.createElement("footer",{id:"site-footer",role:"contentinfo"},e.suppressCustomization?null:r.createElement(f,null),r.createElement("section",{id:"community"},r.createElement("article",{id:"logos"},r.createElement("a",{href:""},r.createElement("img",{id:"lf-logo",width:195,height:75,src:n(9808).Z,alt:"Lingua Franca Logo"})),r.createElement("p",null,"Made with ♥ in Berkeley, Dallas, Dresden, Kiel, and Seoul"),r.createElement("p",null,"© 2019-",(new Date).getFullYear()," The Lingua Franca Team",r.createElement("br",null))),r.createElement("article",{id:"using-lf"},r.createElement("h3",null,"Using Lingua Franca"),r.createElement("ul",null,t.map((function(e){return r.createElement("li",{key:e.url},r.createElement(o,{to:e.url},e.title))})))),r.createElement("article",{id:"community-links"},r.createElement("h3",null,"Community"),r.createElement("ul",null,p.map((function(e){var t=function(e){switch(e){case"https://github.com/lf-lang/website-lingua-franca":case"https://github.com/lf-lang/lingua-franca":return r.createElement("svg",{fill:"none",height:"12",viewBox:"0 0 12 12",width:"12",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{clipRule:"evenodd",d:"m6.03927.165405c-3.27055 0-5.922909 2.652005-5.922909 5.923645 0 2.61709 1.697089 4.83705 4.050909 5.62035.29636.0546.40436-.1284.40436-.2854 0-.1408-.00509-.5131-.008-1.0073-1.64763.3578-1.99527-.7942-1.99527-.7942-.26946-.68436-.65782-.86654-.65782-.86654-.53782-.36727.04073-.36001.04073-.36001.59454.04182.90727.61055.90727.61055.52836.90509 1.38655.64364 1.724.492.05382-.38254.20691-.64363.376-.79163-1.31527-.14946-2.69818-.65782-2.69818-2.92764 0-.64654.23091-1.17564.60982-1.58946-.06109-.14981-.26437-.75236.05818-1.56763 0 0 .49709-.15927 1.62872.60727.47237-.13163.97928-.19709 1.48291-.19964.50328.00255 1.00982.06801 1.48291.19964 1.13091-.76654 1.62727-.60727 1.62727-.60727.32328.81527.12001 1.41782.05928 1.56763.37964.41382.60873.94292.60873 1.58946 0 2.27564-1.38509 2.77636-2.70437 2.92291.21237.18291.40182.54436.40182 1.09672 0 .79204-.00727 1.43094-.00727 1.62514 0 .1585.10691.3429.40727.2851 2.35197-.7851 4.04767-3.00369 4.04767-5.62005 0-3.27164-2.6524-5.923645-5.92403-5.923645z",fill:"#ffffff",fillRule:"evenodd"}));case"https://twitter.com/thelflang":return r.createElement("svg",{fill:"none",height:"10",viewBox:"0 0 13 10",width:"13",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"m4.58519 10c4.62962 0 7.16291-3.83919 7.16291-7.16289 0-.10801 0-.21602-.0049-.32403.4909-.35348.918-.80024 1.2568-1.30591-.4517.20128-.9377.33384-1.4483.39766.5204-.30929.9181-.805148 1.1095-1.394284-.486.289658-1.026.495856-1.6004.608773-.4615-.490946-1.11448-.7953322-1.83617-.7953322-1.38938 0-2.51856 1.1291732-2.51856 2.5185532 0 .19638.02455.38785.06383.57441-2.09143-.1031-3.94721-1.10954-5.1893-2.631474-.21602.373119-.33876.805154-.33876 1.266644 0 .87388.44677 1.64467 1.11936 2.09634-.41239-.01473-.80024-.12765-1.13899-.31421v.03437c0 1.21754.86897 2.23871 2.01778 2.46946-.2111.05891-.43203.08837-.66277.08837-.16202 0-.31912-.01473-.47131-.04419.31911 1.00153 1.25191 1.72813 2.35163 1.74777-.86406.67751-1.94906 1.08008-3.12733 1.08008-.20128 0-.402571-.00982-.59895-.03436 1.10954.70696 2.43509 1.12425 3.85393 1.12425z",fill:"#ffffff"}));case"https://zulip.lf-lang.org":return r.createElement("svg",{fill:"none",viewBox:"-30 -10 55 55",xmlns:"http://www.w3.org/2000/svg"},r.createElement("path",{d:"M22.767 3.589c0 1.209-.543 2.283-1.37 2.934l-8.034 7.174c-.149.128-.343-.078-.235-.25l2.946-5.9c.083-.165-.024-.368-.194-.368H4.452c-1.77 0-3.219-1.615-3.219-3.59C1.233 1.616 2.682 0 4.452 0h15.096c1.77-.001 3.219 1.614 3.219 3.589zM4.452 24h15.096c1.77 0 3.219-1.616 3.219-3.59 0-1.974-1.449-3.59-3.219-3.59H8.12c-.17 0-.277-.202-.194-.367l2.946-5.9c.108-.172-.086-.378-.235-.25l-8.033 7.173c-.828.65-1.37 1.725-1.37 2.934 0 1.974 1.448 3.59 3.218 3.59z",fill:"#ffffff"}))}}(e.url),n=t?r.createElement("span",{className:"link-prefix"},t):null;return r.createElement("li",{key:e.url},r.createElement("a",{style:{position:"relative"},href:e.url},n,e.title))}))))))},d=n(7609),y=function(e){var t=Object.assign({},e.ogTags,{"og:title":e.title,"og:description":e.description,"twitter:site":"thelflang"});return r.createElement(r.Fragment,null,r.createElement(d.q,{title:e.title,titleTemplate:"%s"},r.createElement("meta",{name:"description",key:"description",content:e.description}),Object.keys(t).map((function(e){return r.createElement("meta",{key:e,property:e,content:t[e]})}))))},b=function(e){return r.createElement(r.Fragment,null,r.createElement(d.q,{htmlAttributes:{lang:e.lang}},r.createElement("script",{src:"https://polyfill.io/v3/polyfill.min.js?features=es2015%2CArray.prototype.forEach%2CNodeList.prototype.forEach"}),r.createElement("link",{rel:"preload",href:(0,o.withPrefix)("/css/docsearch.css"),as:"style"}),r.createElement("style",null,"\npre data-err {\n background:url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\") repeat-x 0 100%;\n padding-bottom: 3px;\n}")),r.createElement(y,e),r.createElement("div",{className:"ms-Fabric"},r.createElement(c,e),r.createElement("main",{role:"main"},e.children),r.createElement(g,e)))}},9931:function(e,t,n){"use strict";n.r(t),n.d(t,{comCopy:function(){return r}});var r={com_layout_title:"Lingua Franca Community",com_layout_description:"Connect with other LF Programmers online and offline.",com_connect_online:"Online",com_connect_online_description:"Tap into our online resources to learn more about Lingua Franca, provide feedback, connect with our developers, and find out about new updates.",com_online_publications_desc:"View our publications and presentations.",com_online_publications_tag:"",com_online_github_desc:"Found a bug, or want to provide feedback?",com_online_github_href:"Tell us on GitHub.",com_online_zulip_href:"Join the conversation on Zulip.",com_online_zulip_desc:"Have questions, or want to chat with other users?",com_online_twitter_href:"@thelflang",com_online_twitter_desc:"Stay up to date. Follow us on Twitter"}},636:function(e,t,n){"use strict";n.r(t),n.d(t,{docCopy:function(){return r}});var r={doc_layout_title:"Lingua Franca Documentation",doc_layout_description:"Find Lingua Franca starter projects: from Python to C to C++ to TypeScript.",doc_headline:"Learning Resources",doc_headline_handbook_blurb:"The Lingua Franca language reference"}},4158:function(e,t,n){"use strict";n.r(t),n.d(t,{dtCopy:function(){return r}});var r={dt_s_page_title:"Search for typed packages",dt_s_title:"Type Search",dt_s_subtitle:"Find npm packages that have type declarations, either bundled or on Definitely Typed.",dt_s_match:"match",dt_s_matchs:"matches",dt_s_match_exact:"Exact Match",dt_s_popular_on_dt:"Popular on Definitely Typed",dt_s_downloads_short:"DLs",dt_s_downloads_via:"Via",dt_s_module:"Module",dt_s_last_update:"Last Updated",dt_s_install:"Install",dt_s_no_results:"No results found for",dt_s_no_results_try:"Try another search?",dt_s_copy:"copy",dt_s_copied:"copied"}},5437:function(e,t,n){"use strict";n.r(t),n.d(t,{lang:function(){return b},messages:function(){return y}});var r,o,i=n(5542),a=n(2784),s=n(6563);!function(e){e.formatDate="FormattedDate",e.formatTime="FormattedTime",e.formatNumber="FormattedNumber",e.formatList="FormattedList",e.formatDisplayName="FormattedDisplayName"}(r||(r={})),function(e){e.formatDate="FormattedDateParts",e.formatTime="FormattedTimeParts",e.formatNumber="FormattedNumberParts",e.formatList="FormattedListParts"}(o||(o={}));var c=function(e){var t=(0,s.Z)(),n=e.value,r=e.children,o=(0,i._T)(e,["value","children"]);return r(t.formatNumberToParts(n,o))};c.displayName="FormattedNumberParts";function u(e){var t=function(t){var n=(0,s.Z)(),r=t.value,o=t.children,a=(0,i._T)(t,["value","children"]),c="string"==typeof r?new Date(r||0):r;return o("formatDate"===e?n.formatDateToParts(c,a):n.formatTimeToParts(c,a))};return t.displayName=o[e],t}function l(e){var t=function(t){var n=(0,s.Z)(),r=t.value,o=t.children,c=(0,i._T)(t,["value","children"]),u=n[e](r,c);if("function"==typeof o)return o(u);var l=n.textComponent||a.Fragment;return a.createElement(l,null,u)};return t.displayName=r[e],t}c.displayName="FormattedNumberParts";l("formatDate"),l("formatTime"),l("formatNumber"),l("formatList"),l("formatDisplayName"),u("formatDate"),u("formatTime");var h=n(6801),f=n(636),m=n(2990),p=n(9931),g=n(5636),d=n(519),y=Object.assign({},h.navCopy,f.docCopy,p.comCopy,g.handbookCopy,m.indexCopy,d.footerCopy),b=y},519:function(e,t,n){"use strict";n.r(t),n.d(t,{footerCopy:function(){return r}});var r={footer_customize:"Customize",footer_site_colours:"Color Mode",footer_code_font:"Code Font",footer_site_colours_options_system:"System",footer_site_colours_options_always_light:"Always Light",footer_site_colours_options_always_dark:"Always Dark"}},5636:function(e,t,n){"use strict";n.r(t),n.d(t,{handbookCopy:function(){return r}});var r={handb_prev:"Previous",handb_next:"Next",handb_on_this_page:"On this page",handb_like_dislike_title:"Is this page helpful?",handb_like_desc:"Yes",handb_dislike_desc:"No",handb_thanks:"Thanks for the feedback",handb_deprecated_title:"This page has been deprecated",handb_deprecated_subtitle:"This handbook page has been replaced, ",handb_deprecated_subtitle_link:"go to the new page",handb_deprecated_subtitle_action:"Go to new page",handb_experimental_title:"This page contains experimental documentation",handb_experimental_subtitle:"The contents are about a work in progress topic."}},2990:function(e,t,n){"use strict";n.r(t),n.d(t,{indexCopy:function(){return r}});var r={index_2_headline:"Lingua Franca is a polyglot coordination language for reactive, concurrent, and time-sensitive applications.",index_2_byline:"Lingua Franca",index_2_summary:"Lingua Franca (LF) is a polyglot coordination language built to bring deterministic reactive concurrency and time to mainstream target programming languages (currently C, C++, Python, TypeScript, and Rust). LF is supported by a runtime system that is capable of concurrent and distributed execution of reactive programs that are deployable on the Cloud, the Edge, and even on bare-iron embedded platforms.",index_2_detail:"A Lingua Franca program specifies the interactions between components called reactors. The logic of each reactor is written in plain target code. A code generator synthesizes one or more programs in the target language, which are then compiled using standard tool chains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.",index_2_cta_install:"Download Lingua Franca",index_2_cta_install_subtitle:"Version",index_2_cta_install_fallback:"Latest stable release",index_2_cta_download:"On your computer",index_2_cta_download_subtitle:"via Github",index_2_what_is:"What is Lingua Franca?",index_2_what_is_lf:"Reactor-oriented",index_2_what_is_lf_copy:"Reactors are reactive and composable concurrent software components with inputs, outputs, and local state.",index_2_trust:"Concurrent",index_2_trust_copy:"Reactions to events are concurrent unless there is an explicit dependency between them.",index_2_scale:"Deterministic",index_2_scale_copy:"Lingua Franca programs are deterministic by default and therefore easy to test.",index_2_started_title:"Get Started",index_2_started_handbook:"Handbook",index_2_started_handbook_blurb:"Learn the language",index_2_install:"Install Lingua Franca"}},6801:function(e,t,n){"use strict";n.r(t),n.d(t,{navCopy:function(){return r}});var r={skip_to_content:"Skip to main content",nav_documentation:"Documentation",nav_documentation_short:"Docs",nav_download:"Download",nav_community:"Community",nav_handbook:"Handbook",nav_tools:"Tools",nav_search_placeholder:"Search Docs",nav_search_aria:"Search the Lingua Franca site",nav_this_page_in_your_lang:"This page is available in your language",nav_this_page_in_your_lang_open:"Open",nav_this_page_in_your_lang_no_more:"Don't show again"}},9577:function(e,t,n){"use strict";n.r(t),n.d(t,{inYourLanguage:function(){return r}});var r={en:{shorthand:"In En",body:"This page is available in English",open:"Go",cancel:"Don't ask again"}}},2419:function(e,t,n){"use strict";function r(e){return function(t,n){return e.formatMessage({id:t},n)}}n.d(t,{D:function(){return r}})},8349:function(e,t,n){"use strict";n.d(t,{kG:function(){return r}});function r(e,t,n){if(void 0===n&&(n=Error),!e)throw new n(t)}},8770:function(e,t,n){"use strict";function r(e,t){var n=t&&t.cache?t.cache:l,r=t&&t.serializer?t.serializer:c;return(t&&t.strategy?t.strategy:s)(e,{cache:n,serializer:r})}function o(e,t,n,r){var o,i=null==(o=r)||"number"==typeof o||"boolean"==typeof o?r:n(r),a=t.get(i);return void 0===a&&(a=e.call(this,r),t.set(i,a)),a}function i(e,t,n){var r=Array.prototype.slice.call(arguments,3),o=n(r),i=t.get(o);return void 0===i&&(i=e.apply(this,r),t.set(o,i)),i}function a(e,t,n,r,o){return n.bind(t,e,r,o)}function s(e,t){return a(e,this,1===e.length?o:i,t.cache.create(),t.serializer)}n.d(t,{A:function(){return h},Z:function(){return r}});var c=function(){return JSON.stringify(arguments)};function u(){this.cache=Object.create(null)}u.prototype.get=function(e){return this.cache[e]},u.prototype.set=function(e,t){this.cache[e]=t};var l={create:function(){return new u}},h={variadic:function(e,t){return a(e,this,i,t.cache.create(),t.serializer)},monadic:function(e,t){return a(e,this,o,t.cache.create(),t.serializer)}}},7846:function(e,t,n){"use strict";n.d(t,{wD:function(){return i},VG:function(){return c},rp:function(){return l},Ii:function(){return y},O4:function(){return s},uf:function(){return u},Wh:function(){return d},Jo:function(){return m},yx:function(){return p},Wi:function(){return f},HI:function(){return g},pe:function(){return h},Qc:function(){return ie}});var r,o=n(5542);!function(e){e[e.EXPECT_ARGUMENT_CLOSING_BRACE=1]="EXPECT_ARGUMENT_CLOSING_BRACE",e[e.EMPTY_ARGUMENT=2]="EMPTY_ARGUMENT",e[e.MALFORMED_ARGUMENT=3]="MALFORMED_ARGUMENT",e[e.EXPECT_ARGUMENT_TYPE=4]="EXPECT_ARGUMENT_TYPE",e[e.INVALID_ARGUMENT_TYPE=5]="INVALID_ARGUMENT_TYPE",e[e.EXPECT_ARGUMENT_STYLE=6]="EXPECT_ARGUMENT_STYLE",e[e.INVALID_NUMBER_SKELETON=7]="INVALID_NUMBER_SKELETON",e[e.INVALID_DATE_TIME_SKELETON=8]="INVALID_DATE_TIME_SKELETON",e[e.EXPECT_NUMBER_SKELETON=9]="EXPECT_NUMBER_SKELETON",e[e.EXPECT_DATE_TIME_SKELETON=10]="EXPECT_DATE_TIME_SKELETON",e[e.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]="UNCLOSED_QUOTE_IN_ARGUMENT_STYLE",e[e.EXPECT_SELECT_ARGUMENT_OPTIONS=12]="EXPECT_SELECT_ARGUMENT_OPTIONS",e[e.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]="EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]="INVALID_PLURAL_ARGUMENT_OFFSET_VALUE",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR=15]="EXPECT_SELECT_ARGUMENT_SELECTOR",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]="EXPECT_PLURAL_ARGUMENT_SELECTOR",e[e.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]="EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT",e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]="EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT",e[e.INVALID_PLURAL_ARGUMENT_SELECTOR=19]="INVALID_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]="DUPLICATE_PLURAL_ARGUMENT_SELECTOR",e[e.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]="DUPLICATE_SELECT_ARGUMENT_SELECTOR",e[e.MISSING_OTHER_CLAUSE=22]="MISSING_OTHER_CLAUSE",e[e.INVALID_TAG=23]="INVALID_TAG",e[e.INVALID_TAG_NAME=25]="INVALID_TAG_NAME",e[e.UNMATCHED_CLOSING_TAG=26]="UNMATCHED_CLOSING_TAG",e[e.UNCLOSED_TAG=27]="UNCLOSED_TAG"}(r||(r={}));var i,a;n(1804),n(1715),n(8827);function s(e){return e.type===i.literal}function c(e){return e.type===i.argument}function u(e){return e.type===i.number}function l(e){return e.type===i.date}function h(e){return e.type===i.time}function f(e){return e.type===i.select}function m(e){return e.type===i.plural}function p(e){return e.type===i.pound}function g(e){return e.type===i.tag}function d(e){return!(!e||"object"!=typeof e||e.type!==a.number)}function y(e){return!(!e||"object"!=typeof e||e.type!==a.dateTime)}!function(e){e[e.literal=0]="literal",e[e.argument=1]="argument",e[e.number=2]="number",e[e.date=3]="date",e[e.time=4]="time",e[e.select=5]="select",e[e.plural=6]="plural",e[e.pound=7]="pound",e[e.tag=8]="tag"}(i||(i={})),function(e){e[e.number=0]="number",e[e.dateTime=1]="dateTime"}(a||(a={}));var b=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,I=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function v(e){var t={};return e.replace(I,(function(e){var n=e.length;switch(e[0]){case"G":t.era=4===n?"long":5===n?"narrow":"short";break;case"y":t.year=2===n?"2-digit":"numeric";break;case"Y":case"u":case"U":case"r":throw new RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case"q":case"Q":throw new RangeError("`q/Q` (quarter) patterns are not supported");case"M":case"L":t.month=["numeric","2-digit","short","long","narrow"][n-1];break;case"w":case"W":throw new RangeError("`w/W` (week) patterns are not supported");case"d":t.day=["numeric","2-digit"][n-1];break;case"D":case"F":case"g":throw new RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case"E":t.weekday=4===n?"short":5===n?"narrow":"short";break;case"e":if(n<4)throw new RangeError("`e..eee` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"c":if(n<4)throw new RangeError("`c..ccc` (weekday) patterns are not supported");t.weekday=["short","long","narrow","short"][n-4];break;case"a":t.hour12=!0;break;case"b":case"B":throw new RangeError("`b/B` (period) patterns are not supported, use `a` instead");case"h":t.hourCycle="h12",t.hour=["numeric","2-digit"][n-1];break;case"H":t.hourCycle="h23",t.hour=["numeric","2-digit"][n-1];break;case"K":t.hourCycle="h11",t.hour=["numeric","2-digit"][n-1];break;case"k":t.hourCycle="h24",t.hour=["numeric","2-digit"][n-1];break;case"j":case"J":case"C":throw new RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case"m":t.minute=["numeric","2-digit"][n-1];break;case"s":t.second=["numeric","2-digit"][n-1];break;case"S":case"A":throw new RangeError("`S/A` (second) patterns are not supported, use `s` instead");case"z":t.timeZoneName=n<4?"short":"long";break;case"Z":case"O":case"v":case"V":case"X":case"x":throw new RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return""})),t}var A=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;var E=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,C=/^(@+)?(\+|#+)?[rs]?$/g,T=/(\*)(0+)|(#+)(0+)|(0+)/g,_=/^(0+)$/;function w(e){var t={};return"r"===e[e.length-1]?t.roundingPriority="morePrecision":"s"===e[e.length-1]&&(t.roundingPriority="lessPrecision"),e.replace(C,(function(e,n,r){return"string"!=typeof r?(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length):"+"===r?t.minimumSignificantDigits=n.length:"#"===n[0]?t.maximumSignificantDigits=n.length:(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length+("string"==typeof r?r.length:0)),""})),t}function M(e){switch(e){case"sign-auto":return{signDisplay:"auto"};case"sign-accounting":case"()":return{currencySign:"accounting"};case"sign-always":case"+!":return{signDisplay:"always"};case"sign-accounting-always":case"()!":return{signDisplay:"always",currencySign:"accounting"};case"sign-except-zero":case"+?":return{signDisplay:"exceptZero"};case"sign-accounting-except-zero":case"()?":return{signDisplay:"exceptZero",currencySign:"accounting"};case"sign-never":case"+_":return{signDisplay:"never"}}}function N(e){var t;if("E"===e[0]&&"E"===e[1]?(t={notation:"engineering"},e=e.slice(2)):"E"===e[0]&&(t={notation:"scientific"},e=e.slice(1)),t){var n=e.slice(0,2);if("+!"===n?(t.signDisplay="always",e=e.slice(2)):"+?"===n&&(t.signDisplay="exceptZero",e=e.slice(2)),!_.test(e))throw new Error("Malformed concise eng/scientific notation");t.minimumIntegerDigits=e.length}return t}function L(e){var t=M(e);return t||{}}function S(e){for(var t={},n=0,r=e;n1)throw new RangeError("integer-width stems only accept a single optional option");i.options[0].replace(T,(function(e,n,r,o,i,a){if(n)t.minimumIntegerDigits=r.length;else{if(o&&i)throw new Error("We currently do not support maximum integer digits");if(a)throw new Error("We currently do not support exact integer digits")}return""}));continue}if(_.test(i.stem))t.minimumIntegerDigits=i.stem.length;else if(E.test(i.stem)){if(i.options.length>1)throw new RangeError("Fraction-precision stems only accept a single optional option");i.stem.replace(E,(function(e,n,r,o,i,a){return"*"===r?t.minimumFractionDigits=n.length:o&&"#"===o[0]?t.maximumFractionDigits=o.length:i&&a?(t.minimumFractionDigits=i.length,t.maximumFractionDigits=i.length+a.length):(t.minimumFractionDigits=n.length,t.maximumFractionDigits=n.length),""}));var a=i.options[0];"w"===a?t=(0,o.pi)((0,o.pi)({},t),{trailingZeroDisplay:"stripIfInteger"}):a&&(t=(0,o.pi)((0,o.pi)({},t),w(a)))}else if(C.test(i.stem))t=(0,o.pi)((0,o.pi)({},t),w(i.stem));else{var s=M(i.stem);s&&(t=(0,o.pi)((0,o.pi)({},t),s));var c=N(i.stem);c&&(t=(0,o.pi)((0,o.pi)({},t),c))}}return t}var O,D={AX:["H"],BQ:["H"],CP:["H"],CZ:["H"],DK:["H"],FI:["H"],ID:["H"],IS:["H"],ML:["H"],NE:["H"],RU:["H"],SE:["H"],SJ:["H"],SK:["H"],AS:["h","H"],BT:["h","H"],DJ:["h","H"],ER:["h","H"],GH:["h","H"],IN:["h","H"],LS:["h","H"],PG:["h","H"],PW:["h","H"],SO:["h","H"],TO:["h","H"],VU:["h","H"],WS:["h","H"],"001":["H","h"],AL:["h","H","hB"],TD:["h","H","hB"],"ca-ES":["H","h","hB"],CF:["H","h","hB"],CM:["H","h","hB"],"fr-CA":["H","h","hB"],"gl-ES":["H","h","hB"],"it-CH":["H","h","hB"],"it-IT":["H","h","hB"],LU:["H","h","hB"],NP:["H","h","hB"],PF:["H","h","hB"],SC:["H","h","hB"],SM:["H","h","hB"],SN:["H","h","hB"],TF:["H","h","hB"],VA:["H","h","hB"],CY:["h","H","hb","hB"],GR:["h","H","hb","hB"],CO:["h","H","hB","hb"],DO:["h","H","hB","hb"],KP:["h","H","hB","hb"],KR:["h","H","hB","hb"],NA:["h","H","hB","hb"],PA:["h","H","hB","hb"],PR:["h","H","hB","hb"],VE:["h","H","hB","hb"],AC:["H","h","hb","hB"],AI:["H","h","hb","hB"],BW:["H","h","hb","hB"],BZ:["H","h","hb","hB"],CC:["H","h","hb","hB"],CK:["H","h","hb","hB"],CX:["H","h","hb","hB"],DG:["H","h","hb","hB"],FK:["H","h","hb","hB"],GB:["H","h","hb","hB"],GG:["H","h","hb","hB"],GI:["H","h","hb","hB"],IE:["H","h","hb","hB"],IM:["H","h","hb","hB"],IO:["H","h","hb","hB"],JE:["H","h","hb","hB"],LT:["H","h","hb","hB"],MK:["H","h","hb","hB"],MN:["H","h","hb","hB"],MS:["H","h","hb","hB"],NF:["H","h","hb","hB"],NG:["H","h","hb","hB"],NR:["H","h","hb","hB"],NU:["H","h","hb","hB"],PN:["H","h","hb","hB"],SH:["H","h","hb","hB"],SX:["H","h","hb","hB"],TA:["H","h","hb","hB"],ZA:["H","h","hb","hB"],"af-ZA":["H","h","hB","hb"],AR:["H","h","hB","hb"],CL:["H","h","hB","hb"],CR:["H","h","hB","hb"],CU:["H","h","hB","hb"],EA:["H","h","hB","hb"],"es-BO":["H","h","hB","hb"],"es-BR":["H","h","hB","hb"],"es-EC":["H","h","hB","hb"],"es-ES":["H","h","hB","hb"],"es-GQ":["H","h","hB","hb"],"es-PE":["H","h","hB","hb"],GT:["H","h","hB","hb"],HN:["H","h","hB","hb"],IC:["H","h","hB","hb"],KG:["H","h","hB","hb"],KM:["H","h","hB","hb"],LK:["H","h","hB","hb"],MA:["H","h","hB","hb"],MX:["H","h","hB","hb"],NI:["H","h","hB","hb"],PY:["H","h","hB","hb"],SV:["H","h","hB","hb"],UY:["H","h","hB","hb"],JP:["H","h","K"],AD:["H","hB"],AM:["H","hB"],AO:["H","hB"],AT:["H","hB"],AW:["H","hB"],BE:["H","hB"],BF:["H","hB"],BJ:["H","hB"],BL:["H","hB"],BR:["H","hB"],CG:["H","hB"],CI:["H","hB"],CV:["H","hB"],DE:["H","hB"],EE:["H","hB"],FR:["H","hB"],GA:["H","hB"],GF:["H","hB"],GN:["H","hB"],GP:["H","hB"],GW:["H","hB"],HR:["H","hB"],IL:["H","hB"],IT:["H","hB"],KZ:["H","hB"],MC:["H","hB"],MD:["H","hB"],MF:["H","hB"],MQ:["H","hB"],MZ:["H","hB"],NC:["H","hB"],NL:["H","hB"],PM:["H","hB"],PT:["H","hB"],RE:["H","hB"],RO:["H","hB"],SI:["H","hB"],SR:["H","hB"],ST:["H","hB"],TG:["H","hB"],TR:["H","hB"],WF:["H","hB"],YT:["H","hB"],BD:["h","hB","H"],PK:["h","hB","H"],AZ:["H","hB","h"],BA:["H","hB","h"],BG:["H","hB","h"],CH:["H","hB","h"],GE:["H","hB","h"],LI:["H","hB","h"],ME:["H","hB","h"],RS:["H","hB","h"],UA:["H","hB","h"],UZ:["H","hB","h"],XK:["H","hB","h"],AG:["h","hb","H","hB"],AU:["h","hb","H","hB"],BB:["h","hb","H","hB"],BM:["h","hb","H","hB"],BS:["h","hb","H","hB"],CA:["h","hb","H","hB"],DM:["h","hb","H","hB"],"en-001":["h","hb","H","hB"],FJ:["h","hb","H","hB"],FM:["h","hb","H","hB"],GD:["h","hb","H","hB"],GM:["h","hb","H","hB"],GU:["h","hb","H","hB"],GY:["h","hb","H","hB"],JM:["h","hb","H","hB"],KI:["h","hb","H","hB"],KN:["h","hb","H","hB"],KY:["h","hb","H","hB"],LC:["h","hb","H","hB"],LR:["h","hb","H","hB"],MH:["h","hb","H","hB"],MP:["h","hb","H","hB"],MW:["h","hb","H","hB"],NZ:["h","hb","H","hB"],SB:["h","hb","H","hB"],SG:["h","hb","H","hB"],SL:["h","hb","H","hB"],SS:["h","hb","H","hB"],SZ:["h","hb","H","hB"],TC:["h","hb","H","hB"],TT:["h","hb","H","hB"],UM:["h","hb","H","hB"],US:["h","hb","H","hB"],VC:["h","hb","H","hB"],VG:["h","hb","H","hB"],VI:["h","hb","H","hB"],ZM:["h","hb","H","hB"],BO:["H","hB","h","hb"],EC:["H","hB","h","hb"],ES:["H","hB","h","hb"],GQ:["H","hB","h","hb"],PE:["H","hB","h","hb"],AE:["h","hB","hb","H"],"ar-001":["h","hB","hb","H"],BH:["h","hB","hb","H"],DZ:["h","hB","hb","H"],EG:["h","hB","hb","H"],EH:["h","hB","hb","H"],HK:["h","hB","hb","H"],IQ:["h","hB","hb","H"],JO:["h","hB","hb","H"],KW:["h","hB","hb","H"],LB:["h","hB","hb","H"],LY:["h","hB","hb","H"],MO:["h","hB","hb","H"],MR:["h","hB","hb","H"],OM:["h","hB","hb","H"],PH:["h","hB","hb","H"],PS:["h","hB","hb","H"],QA:["h","hB","hb","H"],SA:["h","hB","hb","H"],SD:["h","hB","hb","H"],SY:["h","hB","hb","H"],TN:["h","hB","hb","H"],YE:["h","hB","hb","H"],AF:["H","hb","hB","h"],LA:["H","hb","hB","h"],CN:["H","hB","hb","h"],LV:["H","hB","hb","h"],TL:["H","hB","hb","h"],"zu-ZA":["H","hB","hb","h"],CD:["hB","H"],IR:["hB","H"],"hi-IN":["hB","h","H"],"kn-IN":["hB","h","H"],"ml-IN":["hB","h","H"],"te-IN":["hB","h","H"],KH:["hB","h","H","hb"],"ta-IN":["hB","h","hb","H"],BN:["hb","hB","h","H"],MY:["hb","hB","h","H"],ET:["hB","hb","h","H"],"gu-IN":["hB","hb","h","H"],"mr-IN":["hB","hb","h","H"],"pa-IN":["hB","hb","h","H"],TW:["hB","hb","h","H"],KE:["hB","hb","H","h"],MM:["hB","hb","H","h"],TZ:["hB","hb","H","h"],UG:["hB","hb","H","h"]};function P(e){var t=e.hourCycle;if(void 0===t&&e.hourCycles&&e.hourCycles.length&&(t=e.hourCycles[0]),t)switch(t){case"h24":return"k";case"h23":return"H";case"h12":return"h";case"h11":return"K";default:throw new Error("Invalid hourCycle")}var n,r=e.language;return"root"!==r&&(n=e.maximize().region),(D[n||""]||D[r||""]||D["".concat(r,"-001")]||D["001"])[0]}var B=new RegExp("^".concat(b.source,"*")),R=new RegExp("".concat(b.source,"*$"));function H(e,t){return{start:e,end:t}}var j=!!String.prototype.startsWith,k=!!String.fromCodePoint,F=!!Object.fromEntries,x=!!String.prototype.codePointAt,G=!!String.prototype.trimStart,Z=!!String.prototype.trimEnd,U=!!Number.isSafeInteger?Number.isSafeInteger:function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e&&Math.abs(e)<=9007199254740991},z=!0;try{z="a"===(null===(O=$("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu").exec("a"))||void 0===O?void 0:O[0])}catch(ae){z=!1}var Y,W=j?function(e,t,n){return e.startsWith(t,n)}:function(e,t,n){return e.slice(n,n+t.length)===t},V=k?String.fromCodePoint:function(){for(var e=[],t=0;ti;){if((n=e[i++])>1114111)throw RangeError(n+" is not a valid code point");r+=n<65536?String.fromCharCode(n):String.fromCharCode(55296+((n-=65536)>>10),n%1024+56320)}return r},X=F?Object.fromEntries:function(e){for(var t={},n=0,r=e;n=n)){var r,o=e.charCodeAt(t);return o<55296||o>56319||t+1===n||(r=e.charCodeAt(t+1))<56320||r>57343?o:r-56320+(o-55296<<10)+65536}},Q=G?function(e){return e.trimStart()}:function(e){return e.replace(B,"")},J=Z?function(e){return e.trimEnd()}:function(e){return e.replace(R,"")};function $(e,t){return new RegExp(e,t)}if(z){var q=$("([^\\p{White_Space}\\p{Pattern_Syntax}]*)","yu");Y=function(e,t){var n;return q.lastIndex=t,null!==(n=q.exec(e)[1])&&void 0!==n?n:""}}else Y=function(e,t){for(var n=[];;){var r=K(e,t);if(void 0===r||ne(r)||re(r))break;n.push(r),t+=r>=65536?2:1}return V.apply(void 0,n)};var ee=function(){function e(e,t){void 0===t&&(t={}),this.message=e,this.position={offset:0,line:1,column:1},this.ignoreTag=!!t.ignoreTag,this.locale=t.locale,this.requiresOtherClause=!!t.requiresOtherClause,this.shouldParseSkeletons=!!t.shouldParseSkeletons}return e.prototype.parse=function(){if(0!==this.offset())throw Error("parser can only be used once");return this.parseMessage(0,"",!1)},e.prototype.parseMessage=function(e,t,n){for(var o=[];!this.isEOF();){var a=this.char();if(123===a){if((s=this.parseArgument(e,n)).err)return s;o.push(s.val)}else{if(125===a&&e>0)break;if(35!==a||"plural"!==t&&"selectordinal"!==t){if(60===a&&!this.ignoreTag&&47===this.peek()){if(n)break;return this.error(r.UNMATCHED_CLOSING_TAG,H(this.clonePosition(),this.clonePosition()))}if(60===a&&!this.ignoreTag&&te(this.peek()||0)){if((s=this.parseTag(e,t)).err)return s;o.push(s.val)}else{var s;if((s=this.parseLiteral(e,t)).err)return s;o.push(s.val)}}else{var c=this.clonePosition();this.bump(),o.push({type:i.pound,location:H(c,this.clonePosition())})}}}return{val:o,err:null}},e.prototype.parseTag=function(e,t){var n=this.clonePosition();this.bump();var o=this.parseTagName();if(this.bumpSpace(),this.bumpIf("/>"))return{val:{type:i.literal,value:"<".concat(o,"/>"),location:H(n,this.clonePosition())},err:null};if(this.bumpIf(">")){var a=this.parseMessage(e+1,t,!0);if(a.err)return a;var s=a.val,c=this.clonePosition();if(this.bumpIf("")?{val:{type:i.tag,value:o,children:s,location:H(n,this.clonePosition())},err:null}:this.error(r.INVALID_TAG,H(c,this.clonePosition())))}return this.error(r.UNCLOSED_TAG,H(n,this.clonePosition()))}return this.error(r.INVALID_TAG,H(n,this.clonePosition()))},e.prototype.parseTagName=function(){var e,t=this.offset();for(this.bump();!this.isEOF()&&(45===(e=this.char())||46===e||e>=48&&e<=57||95===e||e>=97&&e<=122||e>=65&&e<=90||183==e||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8255&&e<=8256||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039);)this.bump();return this.message.slice(t,this.offset())},e.prototype.parseLiteral=function(e,t){for(var n=this.clonePosition(),r="";;){var o=this.tryParseQuote(t);if(o)r+=o;else{var a=this.tryParseUnquoted(e,t);if(a)r+=a;else{var s=this.tryParseLeftAngleBracket();if(!s)break;r+=s}}}var c=H(n,this.clonePosition());return{val:{type:i.literal,value:r,location:c},err:null}},e.prototype.tryParseLeftAngleBracket=function(){return this.isEOF()||60!==this.char()||!this.ignoreTag&&(te(e=this.peek()||0)||47===e)?null:(this.bump(),"<");var e},e.prototype.tryParseQuote=function(e){if(this.isEOF()||39!==this.char())return null;switch(this.peek()){case 39:return this.bump(),this.bump(),"'";case 123:case 60:case 62:case 125:break;case 35:if("plural"===e||"selectordinal"===e)break;return null;default:return null}this.bump();var t=[this.char()];for(this.bump();!this.isEOF();){var n=this.char();if(39===n){if(39!==this.peek()){this.bump();break}t.push(39),this.bump()}else t.push(n);this.bump()}return V.apply(void 0,t)},e.prototype.tryParseUnquoted=function(e,t){if(this.isEOF())return null;var n=this.char();return 60===n||123===n||35===n&&("plural"===t||"selectordinal"===t)||125===n&&e>0?null:(this.bump(),V(n))},e.prototype.parseArgument=function(e,t){var n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition()));if(125===this.char())return this.bump(),this.error(r.EMPTY_ARGUMENT,H(n,this.clonePosition()));var o=this.parseIdentifierIfPossible().value;if(!o)return this.error(r.MALFORMED_ARGUMENT,H(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:i.argument,value:o,location:H(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(n,this.clonePosition())):this.parseArgumentOptions(e,t,o,n);default:return this.error(r.MALFORMED_ARGUMENT,H(n,this.clonePosition()))}},e.prototype.parseIdentifierIfPossible=function(){var e=this.clonePosition(),t=this.offset(),n=Y(this.message,t),r=t+n.length;return this.bumpTo(r),{value:n,location:H(e,this.clonePosition())}},e.prototype.parseArgumentOptions=function(e,t,n,s){var c,u=this.clonePosition(),l=this.parseIdentifierIfPossible().value,h=this.clonePosition();switch(l){case"":return this.error(r.EXPECT_ARGUMENT_TYPE,H(u,h));case"number":case"date":case"time":this.bumpSpace();var f=null;if(this.bumpIf(",")){this.bumpSpace();var m=this.clonePosition();if((E=this.parseSimpleArgStyleIfPossible()).err)return E;if(0===(y=J(E.val)).length)return this.error(r.EXPECT_ARGUMENT_STYLE,H(this.clonePosition(),this.clonePosition()));f={style:y,styleLocation:H(m,this.clonePosition())}}if((C=this.tryParseArgumentClose(s)).err)return C;var p=H(s,this.clonePosition());if(f&&W(null==f?void 0:f.style,"::",0)){var g=Q(f.style.slice(2));if("number"===l)return(E=this.parseNumberSkeletonFromString(g,f.styleLocation)).err?E:{val:{type:i.number,value:n,location:p,style:E.val},err:null};if(0===g.length)return this.error(r.EXPECT_DATE_TIME_SKELETON,p);var d=g;this.locale&&(d=function(e,t){for(var n="",r=0;r>1),c=P(t);for("H"!=c&&"k"!=c||(s=0);s-- >0;)n+="a";for(;a-- >0;)n=c+n}else n+="J"===o?"H":o}return n}(g,this.locale));var y={type:a.dateTime,pattern:d,location:f.styleLocation,parsedOptions:this.shouldParseSkeletons?v(d):{}};return{val:{type:"date"===l?i.date:i.time,value:n,location:p,style:y},err:null}}return{val:{type:"number"===l?i.number:"date"===l?i.date:i.time,value:n,location:p,style:null!==(c=null==f?void 0:f.style)&&void 0!==c?c:null},err:null};case"plural":case"selectordinal":case"select":var b=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(","))return this.error(r.EXPECT_SELECT_ARGUMENT_OPTIONS,H(b,(0,o.pi)({},b)));this.bumpSpace();var I=this.parseIdentifierIfPossible(),A=0;if("select"!==l&&"offset"===I.value){if(!this.bumpIf(":"))return this.error(r.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,H(this.clonePosition(),this.clonePosition()));var E;if(this.bumpSpace(),(E=this.tryParseDecimalInteger(r.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE,r.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE)).err)return E;this.bumpSpace(),I=this.parseIdentifierIfPossible(),A=E.val}var C,T=this.tryParsePluralOrSelectOptions(e,l,t,I);if(T.err)return T;if((C=this.tryParseArgumentClose(s)).err)return C;var _=H(s,this.clonePosition());return"select"===l?{val:{type:i.select,value:n,options:X(T.val),location:_},err:null}:{val:{type:i.plural,value:n,options:X(T.val),offset:A,pluralType:"plural"===l?"cardinal":"ordinal",location:_},err:null};default:return this.error(r.INVALID_ARGUMENT_TYPE,H(u,h))}},e.prototype.tryParseArgumentClose=function(e){return this.isEOF()||125!==this.char()?this.error(r.EXPECT_ARGUMENT_CLOSING_BRACE,H(e,this.clonePosition())):(this.bump(),{val:!0,err:null})},e.prototype.parseSimpleArgStyleIfPossible=function(){for(var e=0,t=this.clonePosition();!this.isEOF();){switch(this.char()){case 39:this.bump();var n=this.clonePosition();if(!this.bumpUntil("'"))return this.error(r.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE,H(n,this.clonePosition()));this.bump();break;case 123:e+=1,this.bump();break;case 125:if(!(e>0))return{val:this.message.slice(t.offset,this.offset()),err:null};e-=1;break;default:this.bump()}}return{val:this.message.slice(t.offset,this.offset()),err:null}},e.prototype.parseNumberSkeletonFromString=function(e,t){var n=[];try{n=function(e){if(0===e.length)throw new Error("Number skeleton cannot be empty");for(var t=[],n=0,r=e.split(A).filter((function(e){return e.length>0}));n=48&&a<=57))break;o=!0,i=10*i+(a-48),this.bump()}var s=H(r,this.clonePosition());return o?U(i*=n)?{val:i,err:null}:this.error(t,s):this.error(e,s)},e.prototype.offset=function(){return this.position.offset},e.prototype.isEOF=function(){return this.offset()===this.message.length},e.prototype.clonePosition=function(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}},e.prototype.char=function(){var e=this.position.offset;if(e>=this.message.length)throw Error("out of bound");var t=K(this.message,e);if(void 0===t)throw Error("Offset ".concat(e," is at invalid UTF-16 code unit boundary"));return t},e.prototype.error=function(e,t){return{val:null,err:{kind:e,message:this.message,location:t}}},e.prototype.bump=function(){if(!this.isEOF()){var e=this.char();10===e?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=e<65536?1:2)}},e.prototype.bumpIf=function(e){if(W(this.message,e,this.offset())){for(var t=0;t=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)},e.prototype.bumpTo=function(e){if(this.offset()>e)throw Error("targetOffset ".concat(e," must be greater than or equal to the current offset ").concat(this.offset()));for(e=Math.min(e,this.message.length);;){var t=this.offset();if(t===e)break;if(t>e)throw Error("targetOffset ".concat(e," is at invalid UTF-16 code unit boundary"));if(this.bump(),this.isEOF())break}},e.prototype.bumpSpace=function(){for(;!this.isEOF()&&ne(this.char());)this.bump()},e.prototype.peek=function(){if(this.isEOF())return null;var e=this.char(),t=this.offset(),n=this.message.charCodeAt(t+(e>=65536?2:1));return null!=n?n:null},e}();function te(e){return e>=97&&e<=122||e>=65&&e<=90}function ne(e){return e>=9&&e<=13||32===e||133===e||e>=8206&&e<=8207||8232===e||8233===e}function re(e){return e>=33&&e<=35||36===e||e>=37&&e<=39||40===e||41===e||42===e||43===e||44===e||45===e||e>=46&&e<=47||e>=58&&e<=59||e>=60&&e<=62||e>=63&&e<=64||91===e||92===e||93===e||94===e||96===e||123===e||124===e||125===e||126===e||161===e||e>=162&&e<=165||166===e||167===e||169===e||171===e||172===e||174===e||176===e||177===e||182===e||187===e||191===e||215===e||247===e||e>=8208&&e<=8213||e>=8214&&e<=8215||8216===e||8217===e||8218===e||e>=8219&&e<=8220||8221===e||8222===e||8223===e||e>=8224&&e<=8231||e>=8240&&e<=8248||8249===e||8250===e||e>=8251&&e<=8254||e>=8257&&e<=8259||8260===e||8261===e||8262===e||e>=8263&&e<=8273||8274===e||8275===e||e>=8277&&e<=8286||e>=8592&&e<=8596||e>=8597&&e<=8601||e>=8602&&e<=8603||e>=8604&&e<=8607||8608===e||e>=8609&&e<=8610||8611===e||e>=8612&&e<=8613||8614===e||e>=8615&&e<=8621||8622===e||e>=8623&&e<=8653||e>=8654&&e<=8655||e>=8656&&e<=8657||8658===e||8659===e||8660===e||e>=8661&&e<=8691||e>=8692&&e<=8959||e>=8960&&e<=8967||8968===e||8969===e||8970===e||8971===e||e>=8972&&e<=8991||e>=8992&&e<=8993||e>=8994&&e<=9e3||9001===e||9002===e||e>=9003&&e<=9083||9084===e||e>=9085&&e<=9114||e>=9115&&e<=9139||e>=9140&&e<=9179||e>=9180&&e<=9185||e>=9186&&e<=9254||e>=9255&&e<=9279||e>=9280&&e<=9290||e>=9291&&e<=9311||e>=9472&&e<=9654||9655===e||e>=9656&&e<=9664||9665===e||e>=9666&&e<=9719||e>=9720&&e<=9727||e>=9728&&e<=9838||9839===e||e>=9840&&e<=10087||10088===e||10089===e||10090===e||10091===e||10092===e||10093===e||10094===e||10095===e||10096===e||10097===e||10098===e||10099===e||10100===e||10101===e||e>=10132&&e<=10175||e>=10176&&e<=10180||10181===e||10182===e||e>=10183&&e<=10213||10214===e||10215===e||10216===e||10217===e||10218===e||10219===e||10220===e||10221===e||10222===e||10223===e||e>=10224&&e<=10239||e>=10240&&e<=10495||e>=10496&&e<=10626||10627===e||10628===e||10629===e||10630===e||10631===e||10632===e||10633===e||10634===e||10635===e||10636===e||10637===e||10638===e||10639===e||10640===e||10641===e||10642===e||10643===e||10644===e||10645===e||10646===e||10647===e||10648===e||e>=10649&&e<=10711||10712===e||10713===e||10714===e||10715===e||e>=10716&&e<=10747||10748===e||10749===e||e>=10750&&e<=11007||e>=11008&&e<=11055||e>=11056&&e<=11076||e>=11077&&e<=11078||e>=11079&&e<=11084||e>=11085&&e<=11123||e>=11124&&e<=11125||e>=11126&&e<=11157||11158===e||e>=11159&&e<=11263||e>=11776&&e<=11777||11778===e||11779===e||11780===e||11781===e||e>=11782&&e<=11784||11785===e||11786===e||11787===e||11788===e||11789===e||e>=11790&&e<=11798||11799===e||e>=11800&&e<=11801||11802===e||11803===e||11804===e||11805===e||e>=11806&&e<=11807||11808===e||11809===e||11810===e||11811===e||11812===e||11813===e||11814===e||11815===e||11816===e||11817===e||e>=11818&&e<=11822||11823===e||e>=11824&&e<=11833||e>=11834&&e<=11835||e>=11836&&e<=11839||11840===e||11841===e||11842===e||e>=11843&&e<=11855||e>=11856&&e<=11857||11858===e||e>=11859&&e<=11903||e>=12289&&e<=12291||12296===e||12297===e||12298===e||12299===e||12300===e||12301===e||12302===e||12303===e||12304===e||12305===e||e>=12306&&e<=12307||12308===e||12309===e||12310===e||12311===e||12312===e||12313===e||12314===e||12315===e||12316===e||12317===e||e>=12318&&e<=12319||12320===e||12336===e||64830===e||64831===e||e>=65093&&e<=65094}function oe(e){e.forEach((function(e){if(delete e.location,f(e)||m(e))for(var t in e.options)delete e.options[t].location,oe(e.options[t].value);else u(e)&&d(e.style)||(l(e)||h(e))&&y(e.style)?delete e.style.location:g(e)&&oe(e.children)}))}function ie(e,t){void 0===t&&(t={}),t=(0,o.pi)({shouldParseSkeletons:!0,requiresOtherClause:!0},t);var n=new ee(e,t).parse();if(n.err){var i=SyntaxError(r[n.err.kind]);throw i.location=n.err.location,i.originalMessage=n.err.message,i}return(null==t?void 0:t.captureLocation)||oe(n.val),n.val}},7309:function(e,t,n){"use strict";n.d(t,{$6:function(){return h},OV:function(){return s},Qe:function(){return u},Rw:function(){return i},X9:function(){return l},bc:function(){return r},gb:function(){return c},wI:function(){return a}});var r,o=n(5542);!function(e){e.FORMAT_ERROR="FORMAT_ERROR",e.UNSUPPORTED_FORMATTER="UNSUPPORTED_FORMATTER",e.INVALID_CONFIG="INVALID_CONFIG",e.MISSING_DATA="MISSING_DATA",e.MISSING_TRANSLATION="MISSING_TRANSLATION"}(r||(r={}));var i=function(e){function t(n,r,o){var i=this,a=o?o instanceof Error?o:new Error(String(o)):void 0;return(i=e.call(this,"[@formatjs/intl Error ".concat(n,"] ").concat(r,"\n").concat(a?"\n".concat(a.message,"\n").concat(a.stack):""))||this).code=n,"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(i,t),i}return(0,o.ZT)(t,e),t}(Error),a=function(e){function t(t,n){return e.call(this,r.UNSUPPORTED_FORMATTER,t,n)||this}return(0,o.ZT)(t,e),t}(i),s=function(e){function t(t,n){return e.call(this,r.INVALID_CONFIG,t,n)||this}return(0,o.ZT)(t,e),t}(i),c=function(e){function t(t,n){return e.call(this,r.MISSING_DATA,t,n)||this}return(0,o.ZT)(t,e),t}(i),u=function(e){function t(t,n,o){return e.call(this,r.FORMAT_ERROR,"".concat(t,"\nLocale: ").concat(n,"\n"),o)||this}return(0,o.ZT)(t,e),t}(i),l=function(e){function t(t,n,r,o){var i=e.call(this,"".concat(t,"\nMessageID: ").concat(null==r?void 0:r.id,"\nDefault Message: ").concat(null==r?void 0:r.defaultMessage,"\nDescription: ").concat(null==r?void 0:r.description,"\n"),n,o)||this;return i.descriptor=r,i}return(0,o.ZT)(t,e),t}(u),h=function(e){function t(t,n){var o=e.call(this,r.MISSING_TRANSLATION,'Missing message: "'.concat(t.id,'" for locale "').concat(n,'", using ').concat(t.defaultMessage?"default message (".concat("string"==typeof t.defaultMessage?t.defaultMessage:t.defaultMessage.map((function(e){var t;return null!==(t=e.value)&&void 0!==t?t:JSON.stringify(e)})).join(),")"):"id"," as fallback."))||this;return o.descriptor=t,o}return(0,o.ZT)(t,e),t}(i)},3167:function(e,t,n){"use strict";n.d(t,{L6:function(){return s},Sn:function(){return u},TB:function(){return f},Z0:function(){return c},ax:function(){return h}});var r=n(5542),o=n(1875),i=n(8770),a=n(7309);function s(e,t,n){return void 0===n&&(n={}),t.reduce((function(t,r){return r in e?t[r]=e[r]:r in n&&(t[r]=n[r]),t}),{})}var c={formats:{},messages:{},timeZone:void 0,defaultLocale:"en",defaultFormats:{},fallbackOnEmptyString:!0,onError:function(e){0},onWarn:function(e){0}};function u(){return{dateTime:{},number:{},message:{},relativeTime:{},pluralRules:{},list:{},displayNames:{}}}function l(e){return{create:function(){return{get:function(t){return e[t]},set:function(t,n){e[t]=n}}}}}function h(e){void 0===e&&(e={dateTime:{},number:{},message:{},relativeTime:{},pluralRules:{},list:{},displayNames:{}});var t=Intl.RelativeTimeFormat,n=Intl.ListFormat,a=Intl.DisplayNames,s=(0,i.Z)((function(){for(var e,t=[],n=0;n0?new Intl.Locale(t[0]):new Intl.Locale("string"==typeof e?e:e[0])}},e.__parse=o.Qc,e.formats={number:{integer:{maximumFractionDigits:0},currency:{style:"currency"},percent:{style:"percent"}},date:{short:{month:"numeric",day:"numeric",year:"2-digit"},medium:{month:"short",day:"numeric",year:"numeric"},long:{month:"long",day:"numeric",year:"numeric"},full:{weekday:"long",month:"long",day:"numeric",year:"numeric"}},time:{short:{hour:"numeric",minute:"numeric"},medium:{hour:"numeric",minute:"numeric",second:"numeric"},long:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"},full:{hour:"numeric",minute:"numeric",second:"numeric",timeZoneName:"short"}}},e}()},1694:function(e,t,n){"use strict";n.d(t,{C8:function(){return a},HR:function(){return c},YR:function(){return s},jK:function(){return r},u_:function(){return i}});var r,o=n(5542);!function(e){e.MISSING_VALUE="MISSING_VALUE",e.INVALID_VALUE="INVALID_VALUE",e.MISSING_INTL_API="MISSING_INTL_API"}(r||(r={}));var i=function(e){function t(t,n,r){var o=e.call(this,t)||this;return o.code=n,o.originalMessage=r,o}return(0,o.ZT)(t,e),t.prototype.toString=function(){return"[formatjs Error: ".concat(this.code,"] ").concat(this.message)},t}(Error),a=function(e){function t(t,n,o,i){return e.call(this,'Invalid values for "'.concat(t,'": "').concat(n,'". Options are "').concat(Object.keys(o).join('", "'),'"'),r.INVALID_VALUE,i)||this}return(0,o.ZT)(t,e),t}(i),s=function(e){function t(t,n,o){return e.call(this,'Value for "'.concat(t,'" must be of type ').concat(n),r.INVALID_VALUE,o)||this}return(0,o.ZT)(t,e),t}(i),c=function(e){function t(t,n){return e.call(this,'The intl string context variable "'.concat(t,'" was not provided to the string "').concat(n,'"'),r.MISSING_VALUE,n)||this}return(0,o.ZT)(t,e),t}(i)},3528:function(e,t,n){"use strict";n.d(t,{FK:function(){return s},Gt:function(){return a},du:function(){return r}});var r,o=n(7846),i=n(1694);function a(e){return"function"==typeof e}function s(e,t,n,c,u,l,h){if(1===e.length&&(0,o.O4)(e[0]))return[{type:r.literal,value:e[0].value}];for(var f=[],m=0,p=e;m=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n},W=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t},V=function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return!1===t?String(e):String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")},X=function(e){var t=q(e,v.TITLE),n=q(e,j);if(n&&t)return n.replace(/%s/g,(function(){return Array.isArray(t)?t.join(""):t}));var r=q(e,P);return t||r||void 0},K=function(e){return q(e,H)||function(){}},Q=function(e,t){return t.filter((function(t){return void 0!==t[e]})).map((function(t){return t[e]})).reduce((function(e,t){return z({},e,t)}),{})},J=function(e,t){return t.filter((function(e){return void 0!==e[v.BASE]})).map((function(e){return e[v.BASE]})).reverse().reduce((function(t,n){if(!t.length)for(var r=Object.keys(n),o=0;o=0;n--){var r=e[n];if(r.hasOwnProperty(t))return r[t]}return null},ee=(r=Date.now(),function(e){var t=Date.now();t-r>16?(r=t,e(t)):setTimeout((function(){ee(e)}),0)}),te=function(e){return clearTimeout(e)},ne="undefined"!=typeof window?window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||ee:n.g.requestAnimationFrame||ee,re="undefined"!=typeof window?window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||te:n.g.cancelAnimationFrame||te,oe=function(e){return d&&"function"==typeof d.warn&&d.warn(e)},ie=null,ae=function(e,t){var n=e.baseTag,r=e.bodyAttributes,o=e.htmlAttributes,i=e.linkTags,a=e.metaTags,s=e.noscriptTags,c=e.onChangeClientState,u=e.scriptTags,l=e.styleTags,h=e.title,f=e.titleAttributes;ue(v.BODY,r),ue(v.HTML,o),ce(h,f);var m={baseTag:le(v.BASE,n),linkTags:le(v.LINK,i),metaTags:le(v.META,a),noscriptTags:le(v.NOSCRIPT,s),scriptTags:le(v.SCRIPT,u),styleTags:le(v.STYLE,l)},p={},g={};Object.keys(m).forEach((function(e){var t=m[e],n=t.newTags,r=t.oldTags;n.length&&(p[e]=n),r.length&&(g[e]=m[e].oldTags)})),t&&t(),c(e,p,g)},se=function(e){return Array.isArray(e)?e.join(""):e},ce=function(e,t){void 0!==e&&document.title!==e&&(document.title=se(e)),ue(v.TITLE,t)},ue=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute(x),o=r?r.split(","):[],i=[].concat(o),a=Object.keys(t),s=0;s=0;h--)n.removeAttribute(i[h]);o.length===i.length?n.removeAttribute(x):n.getAttribute(x)!==a.join(",")&&n.setAttribute(x,a.join(","))}},le=function(e,t){var n=document.head||document.querySelector(v.HEAD),r=n.querySelectorAll(e+"["+"data-react-helmet]"),o=Array.prototype.slice.call(r),i=[],a=void 0;return t&&t.length&&t.forEach((function(t){var n=document.createElement(e);for(var r in t)if(t.hasOwnProperty(r))if(r===_)n.innerHTML=t.innerHTML;else if(r===E)n.styleSheet?n.styleSheet.cssText=t.cssText:n.appendChild(document.createTextNode(t.cssText));else{var s=void 0===t[r]?"":t[r];n.setAttribute(r,s)}n.setAttribute(x,"true"),o.some((function(e,t){return a=t,n.isEqualNode(e)}))?o.splice(a,1):i.push(n)})),o.forEach((function(e){return e.parentNode.removeChild(e)})),i.forEach((function(e){return n.appendChild(e)})),{oldTags:o,newTags:i}},he=function(e){return Object.keys(e).reduce((function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r}),"")},fe=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.keys(e).reduce((function(t,n){return t[D[n]||n]=e[n],t}),t)},me=function(e,t,n){switch(e){case v.TITLE:return{toComponent:function(){return e=t.title,n=t.titleAttributes,(r={key:e})[x]=!0,o=fe(n,r),[m.createElement(v.TITLE,o,e)];var e,n,r,o},toString:function(){return function(e,t,n,r){var o=he(n),i=se(t);return o?"<"+e+' data-react-helmet="true" '+o+">"+V(i,r)+"":"<"+e+' data-react-helmet="true">'+V(i,r)+""}(e,t.title,t.titleAttributes,n)}};case y:case b:return{toComponent:function(){return fe(t)},toString:function(){return he(t)}};default:return{toComponent:function(){return function(e,t){return t.map((function(t,n){var r,o=((r={key:n})[x]=!0,r);return Object.keys(t).forEach((function(e){var n=D[e]||e;if(n===_||n===E){var r=t.innerHTML||t.cssText;o.dangerouslySetInnerHTML={__html:r}}else o[n]=t[e]})),m.createElement(e,o)}))}(e,t)},toString:function(){return function(e,t,n){return t.reduce((function(t,r){var o=Object.keys(r).filter((function(e){return!(e===_||e===E)})).reduce((function(e,t){var o=void 0===r[t]?t:t+'="'+V(r[t],n)+'"';return e?e+" "+o:o}),""),i=r.innerHTML||r.cssText||"",a=-1===F.indexOf(e);return t+"<"+e+' data-react-helmet="true" '+o+(a?"/>":">"+i+"")}),"")}(e,t,n)}}}},pe=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,o=e.htmlAttributes,i=e.linkTags,a=e.metaTags,s=e.noscriptTags,c=e.scriptTags,u=e.styleTags,l=e.title,h=void 0===l?"":l,f=e.titleAttributes;return{base:me(v.BASE,t,r),bodyAttributes:me(y,n,r),htmlAttributes:me(b,o,r),link:me(v.LINK,i,r),meta:me(v.META,a,r),noscript:me(v.NOSCRIPT,s,r),script:me(v.SCRIPT,c,r),style:me(v.STYLE,u,r),title:me(v.TITLE,{title:h,titleAttributes:f},r)}},ge=l()((function(e){return{baseTag:J([C,O],e),bodyAttributes:Q(y,e),defer:q(e,B),encode:q(e,R),htmlAttributes:Q(b,e),linkTags:$(v.LINK,[L,C],e),metaTags:$(v.META,[M,A,T,N,w],e),noscriptTags:$(v.NOSCRIPT,[_],e),onChangeClientState:K(e),scriptTags:$(v.SCRIPT,[S,_],e),styleTags:$(v.STYLE,[E],e),title:X(e),titleAttributes:Q(I,e)}}),(function(e){ie&&re(ie),e.defer?ie=ne((function(){ae(e,(function(){ie=null}))})):(ae(e),ie=null)}),pe)((function(){return null})),de=(o=ge,a=i=function(e){function t(){return Z(this,t),W(this,e.apply(this,arguments))}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),t.prototype.shouldComponentUpdate=function(e){return!f()(this.props,e)},t.prototype.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case v.SCRIPT:case v.NOSCRIPT:return{innerHTML:t};case v.STYLE:return{cssText:t}}throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")},t.prototype.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren,o=e.newChildProps,i=e.nestedChildren;return z({},r,((t={})[n.type]=[].concat(r[n.type]||[],[z({},o,this.mapNestedChildrenToProps(n,i))]),t))},t.prototype.mapObjectTypeChildren=function(e){var t,n,r=e.child,o=e.newProps,i=e.newChildProps,a=e.nestedChildren;switch(r.type){case v.TITLE:return z({},o,((t={})[r.type]=a,t.titleAttributes=z({},i),t));case v.BODY:return z({},o,{bodyAttributes:z({},i)});case v.HTML:return z({},o,{htmlAttributes:z({},i)})}return z({},o,((n={})[r.type]=z({},i),n))},t.prototype.mapArrayTypeChildrenToProps=function(e,t){var n=z({},t);return Object.keys(e).forEach((function(t){var r;n=z({},n,((r={})[t]=e[t],r))})),n},t.prototype.warnOnInvalidChildren=function(e,t){return!0},t.prototype.mapChildrenToProps=function(e,t){var n=this,r={};return m.Children.forEach(e,(function(e){if(e&&e.props){var o=e.props,i=o.children,a=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object.keys(e).reduce((function(t,n){return t[k[n]||n]=e[n],t}),t)}(Y(o,["children"]));switch(n.warnOnInvalidChildren(e,i),e.type){case v.LINK:case v.META:case v.NOSCRIPT:case v.SCRIPT:case v.STYLE:r=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:r,newChildProps:a,nestedChildren:i});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:a,nestedChildren:i})}}})),t=this.mapArrayTypeChildrenToProps(r,t)},t.prototype.render=function(){var e=this.props,t=e.children,n=Y(e,["children"]),r=z({},n);return t&&(r=this.mapChildrenToProps(t,r)),m.createElement(o,r)},U(t,null,[{key:"canUseDOM",set:function(e){o.canUseDOM=e}}]),t}(m.Component),i.propTypes={base:c().object,bodyAttributes:c().object,children:c().oneOfType([c().arrayOf(c().node),c().node]),defaultTitle:c().string,defer:c().bool,encodeSpecialCharacters:c().bool,htmlAttributes:c().object,link:c().arrayOf(c().object),meta:c().arrayOf(c().object),noscript:c().arrayOf(c().object),onChangeClientState:c().func,script:c().arrayOf(c().object),style:c().arrayOf(c().object),title:c().string,titleAttributes:c().object,titleTemplate:c().string},i.defaultProps={defer:!0,encodeSpecialCharacters:!0},i.peek=o.peek,i.rewind=function(){var e=o.rewind();return e||(e=pe({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}})),e},a);de.renderStatic=de.rewind,t.Z=de},6911:function(e,t,n){"use strict";n.d(t,{_y:function(){return a},zt:function(){return i}});var r=n(2784);n(9703);var o=r.createContext(null),i=(o.Consumer,o.Provider),a=o},6563:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(2784),o=n(6911),i=n(3087);function a(){var e=r.useContext(o._y);return(0,i.lq)(e),e}},3087:function(e,t,n){"use strict";n.d(t,{Z0:function(){return c},dt:function(){return u},lq:function(){return s},wU:function(){return l}});var r=n(5542),o=n(2784),i=n(8349),a=n(3167);function s(e){(0,i.kG)(e,"[React Intl] Could not find required `intl` object. needs to exist in the component ancestry.")}var c=(0,r.pi)((0,r.pi)({},a.Z0),{textComponent:o.Fragment});function u(e){return function(t){return e(o.Children.toArray(t))}}function l(e,t){if(e===t)return!0;if(!e||!t)return!1;var n=Object.keys(e),r=Object.keys(t),o=n.length;if(r.length!==o)return!1;for(var i=0;i for the\n // default locale.\n onError(new MissingTranslationError(messageDescriptor, locale));\n }\n\n if (defaultMessage) {\n try {\n var formatter = state.getMessageFormat(defaultMessage, defaultLocale, defaultFormats, opts);\n return formatter.format(values);\n } catch (e) {\n onError(new MessageFormatError(\"Error formatting default message for: \\\"\".concat(id, \"\\\", rendering default message verbatim\"), locale, messageDescriptor, e));\n return typeof defaultMessage === 'string' ? defaultMessage : id;\n }\n }\n\n return id;\n } // We have the translated message\n\n\n try {\n var formatter = state.getMessageFormat(message, locale, formats, __assign({\n formatters: state\n }, opts || {}));\n return formatter.format(values);\n } catch (e) {\n onError(new MessageFormatError(\"Error formatting message: \\\"\".concat(id, \"\\\", using \").concat(defaultMessage ? 'default message' : 'id', \" as fallback.\"), locale, messageDescriptor, e));\n }\n\n if (defaultMessage) {\n try {\n var formatter = state.getMessageFormat(defaultMessage, defaultLocale, defaultFormats, opts);\n return formatter.format(values);\n } catch (e) {\n onError(new MessageFormatError(\"Error formatting the default message for: \\\"\".concat(id, \"\\\", rendering message verbatim\"), locale, messageDescriptor, e));\n }\n }\n\n if (typeof message === 'string') {\n return message;\n }\n\n if (typeof defaultMessage === 'string') {\n return defaultMessage;\n }\n\n return id;\n};","import { getNamedFormat, filterProps } from './utils';\nimport { IntlError, IntlErrorCode } from './error';\nvar NUMBER_FORMAT_OPTIONS = ['style', 'currency', 'currencyDisplay', 'unit', 'unitDisplay', 'useGrouping', 'minimumIntegerDigits', 'minimumFractionDigits', 'maximumFractionDigits', 'minimumSignificantDigits', 'maximumSignificantDigits', // ES2020 NumberFormat\n'compactDisplay', 'currencyDisplay', 'currencySign', 'notation', 'signDisplay', 'unit', 'unitDisplay', 'numberingSystem'];\nexport function getFormatter(_a, getNumberFormat, options) {\n var locale = _a.locale,\n formats = _a.formats,\n onError = _a.onError;\n\n if (options === void 0) {\n options = {};\n }\n\n var format = options.format;\n var defaults = format && getNamedFormat(formats, 'number', format, onError) || {};\n var filteredOptions = filterProps(options, NUMBER_FORMAT_OPTIONS, defaults);\n return getNumberFormat(locale, filteredOptions);\n}\nexport function formatNumber(config, getNumberFormat, value, options) {\n if (options === void 0) {\n options = {};\n }\n\n try {\n return getFormatter(config, getNumberFormat, options).format(value);\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting number.', e));\n }\n\n return String(value);\n}\nexport function formatNumberToParts(config, getNumberFormat, value, options) {\n if (options === void 0) {\n options = {};\n }\n\n try {\n return getFormatter(config, getNumberFormat, options).formatToParts(value);\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting number.', e));\n }\n\n return [];\n}","import { getNamedFormat, filterProps } from './utils';\nimport { FormatError, ErrorCode } from 'intl-messageformat';\nimport { IntlFormatError } from './error';\nvar RELATIVE_TIME_FORMAT_OPTIONS = ['numeric', 'style'];\n\nfunction getFormatter(_a, getRelativeTimeFormat, options) {\n var locale = _a.locale,\n formats = _a.formats,\n onError = _a.onError;\n\n if (options === void 0) {\n options = {};\n }\n\n var format = options.format;\n var defaults = !!format && getNamedFormat(formats, 'relative', format, onError) || {};\n var filteredOptions = filterProps(options, RELATIVE_TIME_FORMAT_OPTIONS, defaults);\n return getRelativeTimeFormat(locale, filteredOptions);\n}\n\nexport function formatRelativeTime(config, getRelativeTimeFormat, value, unit, options) {\n if (options === void 0) {\n options = {};\n }\n\n if (!unit) {\n unit = 'second';\n }\n\n var RelativeTimeFormat = Intl.RelativeTimeFormat;\n\n if (!RelativeTimeFormat) {\n config.onError(new FormatError(\"Intl.RelativeTimeFormat is not available in this environment.\\nTry polyfilling it using \\\"@formatjs/intl-relativetimeformat\\\"\\n\", ErrorCode.MISSING_INTL_API));\n }\n\n try {\n return getFormatter(config, getRelativeTimeFormat, options).format(value, unit);\n } catch (e) {\n config.onError(new IntlFormatError('Error formatting relative time.', config.locale, e));\n }\n\n return String(value);\n}","import { __assign } from \"tslib\";\nimport { filterProps, getNamedFormat } from './utils';\nimport { IntlError, IntlErrorCode } from './error';\nvar DATE_TIME_FORMAT_OPTIONS = ['formatMatcher', 'timeZone', 'hour12', 'weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName', 'hourCycle', 'dateStyle', 'timeStyle', 'calendar', // 'dayPeriod',\n'numberingSystem', 'fractionalSecondDigits'];\nexport function getFormatter(_a, type, getDateTimeFormat, options) {\n var locale = _a.locale,\n formats = _a.formats,\n onError = _a.onError,\n timeZone = _a.timeZone;\n\n if (options === void 0) {\n options = {};\n }\n\n var format = options.format;\n\n var defaults = __assign(__assign({}, timeZone && {\n timeZone: timeZone\n }), format && getNamedFormat(formats, type, format, onError));\n\n var filteredOptions = filterProps(options, DATE_TIME_FORMAT_OPTIONS, defaults);\n\n if (type === 'time' && !filteredOptions.hour && !filteredOptions.minute && !filteredOptions.second && !filteredOptions.timeStyle && !filteredOptions.dateStyle) {\n // Add default formatting options if hour, minute, or second isn't defined.\n filteredOptions = __assign(__assign({}, filteredOptions), {\n hour: 'numeric',\n minute: 'numeric'\n });\n }\n\n return getDateTimeFormat(locale, filteredOptions);\n}\nexport function formatDate(config, getDateTimeFormat) {\n var _a = [];\n\n for (var _i = 2; _i < arguments.length; _i++) {\n _a[_i - 2] = arguments[_i];\n }\n\n var value = _a[0],\n _b = _a[1],\n options = _b === void 0 ? {} : _b;\n var date = typeof value === 'string' ? new Date(value || 0) : value;\n\n try {\n return getFormatter(config, 'date', getDateTimeFormat, options).format(date);\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting date.', e));\n }\n\n return String(date);\n}\nexport function formatTime(config, getDateTimeFormat) {\n var _a = [];\n\n for (var _i = 2; _i < arguments.length; _i++) {\n _a[_i - 2] = arguments[_i];\n }\n\n var value = _a[0],\n _b = _a[1],\n options = _b === void 0 ? {} : _b;\n var date = typeof value === 'string' ? new Date(value || 0) : value;\n\n try {\n return getFormatter(config, 'time', getDateTimeFormat, options).format(date);\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting time.', e));\n }\n\n return String(date);\n}\nexport function formatDateTimeRange(config, getDateTimeFormat) {\n var _a = [];\n\n for (var _i = 2; _i < arguments.length; _i++) {\n _a[_i - 2] = arguments[_i];\n }\n\n var from = _a[0],\n to = _a[1],\n _b = _a[2],\n options = _b === void 0 ? {} : _b;\n var timeZone = config.timeZone,\n locale = config.locale,\n onError = config.onError;\n var filteredOptions = filterProps(options, DATE_TIME_FORMAT_OPTIONS, timeZone ? {\n timeZone: timeZone\n } : {});\n\n try {\n return getDateTimeFormat(locale, filteredOptions).formatRange(from, to);\n } catch (e) {\n onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting date time range.', e));\n }\n\n return String(from);\n}\nexport function formatDateToParts(config, getDateTimeFormat) {\n var _a = [];\n\n for (var _i = 2; _i < arguments.length; _i++) {\n _a[_i - 2] = arguments[_i];\n }\n\n var value = _a[0],\n _b = _a[1],\n options = _b === void 0 ? {} : _b;\n var date = typeof value === 'string' ? new Date(value || 0) : value;\n\n try {\n return getFormatter(config, 'date', getDateTimeFormat, options).formatToParts(date); // TODO: remove this when https://github.com/microsoft/TypeScript/pull/50402 is merged\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting date.', e));\n }\n\n return [];\n}\nexport function formatTimeToParts(config, getDateTimeFormat) {\n var _a = [];\n\n for (var _i = 2; _i < arguments.length; _i++) {\n _a[_i - 2] = arguments[_i];\n }\n\n var value = _a[0],\n _b = _a[1],\n options = _b === void 0 ? {} : _b;\n var date = typeof value === 'string' ? new Date(value || 0) : value;\n\n try {\n return getFormatter(config, 'time', getDateTimeFormat, options).formatToParts(date); // TODO: remove this when https://github.com/microsoft/TypeScript/pull/50402 is merged\n } catch (e) {\n config.onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting time.', e));\n }\n\n return [];\n}","import { filterProps } from './utils';\nimport { IntlFormatError } from './error';\nimport { ErrorCode, FormatError } from 'intl-messageformat';\nvar PLURAL_FORMAT_OPTIONS = ['type'];\nexport function formatPlural(_a, getPluralRules, value, options) {\n var locale = _a.locale,\n onError = _a.onError;\n\n if (options === void 0) {\n options = {};\n }\n\n if (!Intl.PluralRules) {\n onError(new FormatError(\"Intl.PluralRules is not available in this environment.\\nTry polyfilling it using \\\"@formatjs/intl-pluralrules\\\"\\n\", ErrorCode.MISSING_INTL_API));\n }\n\n var filteredOptions = filterProps(options, PLURAL_FORMAT_OPTIONS);\n\n try {\n return getPluralRules(locale, filteredOptions).select(value);\n } catch (e) {\n onError(new IntlFormatError('Error formatting plural.', locale, e));\n }\n\n return 'other';\n}","import { __assign } from \"tslib\";\nimport { filterProps } from './utils';\nimport { FormatError, ErrorCode } from 'intl-messageformat';\nimport { IntlError, IntlErrorCode } from './error';\nvar LIST_FORMAT_OPTIONS = ['type', 'style'];\nvar now = Date.now();\n\nfunction generateToken(i) {\n return \"\".concat(now, \"_\").concat(i, \"_\").concat(now);\n}\n\nexport function formatList(opts, getListFormat, values, options) {\n if (options === void 0) {\n options = {};\n }\n\n var results = formatListToParts(opts, getListFormat, values, options).reduce(function (all, el) {\n var val = el.value;\n\n if (typeof val !== 'string') {\n all.push(val);\n } else if (typeof all[all.length - 1] === 'string') {\n all[all.length - 1] += val;\n } else {\n all.push(val);\n }\n\n return all;\n }, []);\n return results.length === 1 ? results[0] : results.length === 0 ? '' : results;\n}\nexport function formatListToParts(_a, getListFormat, values, options) {\n var locale = _a.locale,\n onError = _a.onError;\n\n if (options === void 0) {\n options = {};\n }\n\n var ListFormat = Intl.ListFormat;\n\n if (!ListFormat) {\n onError(new FormatError(\"Intl.ListFormat is not available in this environment.\\nTry polyfilling it using \\\"@formatjs/intl-listformat\\\"\\n\", ErrorCode.MISSING_INTL_API));\n }\n\n var filteredOptions = filterProps(options, LIST_FORMAT_OPTIONS);\n\n try {\n var richValues_1 = {};\n var serializedValues = values.map(function (v, i) {\n if (typeof v === 'object') {\n var id = generateToken(i);\n richValues_1[id] = v;\n return id;\n }\n\n return String(v);\n });\n return getListFormat(locale, filteredOptions).formatToParts(serializedValues).map(function (part) {\n return part.type === 'literal' ? part : __assign(__assign({}, part), {\n value: richValues_1[part.value] || part.value\n });\n });\n } catch (e) {\n onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting list.', e));\n } // @ts-ignore\n\n\n return values;\n}","import { filterProps } from './utils';\nimport { FormatError, ErrorCode } from 'intl-messageformat';\nimport { IntlErrorCode, IntlError } from './error';\nvar DISPLAY_NAMES_OPTONS = ['style', 'type', 'fallback'];\nexport function formatDisplayName(_a, getDisplayNames, value, options) {\n var locale = _a.locale,\n onError = _a.onError;\n var DisplayNames = Intl.DisplayNames;\n\n if (!DisplayNames) {\n onError(new FormatError(\"Intl.DisplayNames is not available in this environment.\\nTry polyfilling it using \\\"@formatjs/intl-displaynames\\\"\\n\", ErrorCode.MISSING_INTL_API));\n }\n\n var filteredOptions = filterProps(options, DISPLAY_NAMES_OPTONS);\n\n try {\n return getDisplayNames(locale, filteredOptions).of(value);\n } catch (e) {\n onError(new IntlError(IntlErrorCode.FORMAT_ERROR, 'Error formatting display name.', e));\n }\n}","import { __assign } from \"tslib\";\nimport { createFormatters, DEFAULT_INTL_CONFIG } from './utils';\nimport { InvalidConfigError, MissingDataError } from './error';\nimport { formatNumber, formatNumberToParts } from './number';\nimport { formatRelativeTime } from './relativeTime';\nimport { formatDate, formatDateToParts, formatTime, formatTimeToParts, formatDateTimeRange } from './dateTime';\nimport { formatPlural } from './plural';\nimport { formatMessage } from './message';\nimport { formatList, formatListToParts } from './list';\nimport { formatDisplayName } from './displayName';\n\nfunction messagesContainString(messages) {\n var firstMessage = messages ? messages[Object.keys(messages)[0]] : undefined;\n return typeof firstMessage === 'string';\n}\n\nfunction verifyConfigMessages(config) {\n if (config.onWarn && config.defaultRichTextElements && messagesContainString(config.messages || {})) {\n config.onWarn(\"[@formatjs/intl] \\\"defaultRichTextElements\\\" was specified but \\\"message\\\" was not pre-compiled. \\nPlease consider using \\\"@formatjs/cli\\\" to pre-compile your messages for performance.\\nFor more details see https://formatjs.io/docs/getting-started/message-distribution\");\n }\n}\n/**\n * Create intl object\n * @param config intl config\n * @param cache cache for formatter instances to prevent memory leak\n */\n\n\nexport function createIntl(config, cache) {\n var formatters = createFormatters(cache);\n\n var resolvedConfig = __assign(__assign({}, DEFAULT_INTL_CONFIG), config);\n\n var locale = resolvedConfig.locale,\n defaultLocale = resolvedConfig.defaultLocale,\n onError = resolvedConfig.onError;\n\n if (!locale) {\n if (onError) {\n onError(new InvalidConfigError(\"\\\"locale\\\" was not configured, using \\\"\".concat(defaultLocale, \"\\\" as fallback. See https://formatjs.io/docs/react-intl/api#intlshape for more details\")));\n } // Since there's no registered locale data for `locale`, this will\n // fallback to the `defaultLocale` to make sure things can render.\n // The `messages` are overridden to the `defaultProps` empty object\n // to maintain referential equality across re-renders. It's assumed\n // each contains a `defaultMessage` prop.\n\n\n resolvedConfig.locale = resolvedConfig.defaultLocale || 'en';\n } else if (!Intl.NumberFormat.supportedLocalesOf(locale).length && onError) {\n onError(new MissingDataError(\"Missing locale data for locale: \\\"\".concat(locale, \"\\\" in Intl.NumberFormat. Using default locale: \\\"\").concat(defaultLocale, \"\\\" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details\")));\n } else if (!Intl.DateTimeFormat.supportedLocalesOf(locale).length && onError) {\n onError(new MissingDataError(\"Missing locale data for locale: \\\"\".concat(locale, \"\\\" in Intl.DateTimeFormat. Using default locale: \\\"\").concat(defaultLocale, \"\\\" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details\")));\n }\n\n verifyConfigMessages(resolvedConfig);\n return __assign(__assign({}, resolvedConfig), {\n formatters: formatters,\n formatNumber: formatNumber.bind(null, resolvedConfig, formatters.getNumberFormat),\n formatNumberToParts: formatNumberToParts.bind(null, resolvedConfig, formatters.getNumberFormat),\n formatRelativeTime: formatRelativeTime.bind(null, resolvedConfig, formatters.getRelativeTimeFormat),\n formatDate: formatDate.bind(null, resolvedConfig, formatters.getDateTimeFormat),\n formatDateToParts: formatDateToParts.bind(null, resolvedConfig, formatters.getDateTimeFormat),\n formatTime: formatTime.bind(null, resolvedConfig, formatters.getDateTimeFormat),\n formatDateTimeRange: formatDateTimeRange.bind(null, resolvedConfig, formatters.getDateTimeFormat),\n formatTimeToParts: formatTimeToParts.bind(null, resolvedConfig, formatters.getDateTimeFormat),\n formatPlural: formatPlural.bind(null, resolvedConfig, formatters.getPluralRules),\n // @ts-expect-error TODO: will get to this later\n formatMessage: formatMessage.bind(null, resolvedConfig, formatters),\n // @ts-expect-error TODO: will get to this later\n $t: formatMessage.bind(null, resolvedConfig, formatters),\n formatList: formatList.bind(null, resolvedConfig, formatters.getListFormat),\n formatListToParts: formatListToParts.bind(null, resolvedConfig, formatters.getListFormat),\n formatDisplayName: formatDisplayName.bind(null, resolvedConfig, formatters.getDisplayNames)\n });\n}","/*\n * Copyright 2015, Yahoo Inc.\n * Copyrights licensed under the New BSD License.\n * See the accompanying LICENSE file for terms.\n */\nimport { __assign, __extends, __rest, __spreadArray } from \"tslib\";\nimport * as React from 'react';\nimport { Provider } from './injectIntl';\nimport { DEFAULT_INTL_CONFIG, invariantIntlContext, assignUniqueKeysToParts, shallowEqual } from '../utils';\nimport { formatMessage as coreFormatMessage, createIntl as coreCreateIntl, createIntlCache } from '@formatjs/intl';\nimport { isFormatXMLElementFn } from 'intl-messageformat';\n\nfunction processIntlConfig(config) {\n return {\n locale: config.locale,\n timeZone: config.timeZone,\n fallbackOnEmptyString: config.fallbackOnEmptyString,\n formats: config.formats,\n textComponent: config.textComponent,\n messages: config.messages,\n defaultLocale: config.defaultLocale,\n defaultFormats: config.defaultFormats,\n onError: config.onError,\n onWarn: config.onWarn,\n wrapRichTextChunksInFragment: config.wrapRichTextChunksInFragment,\n defaultRichTextElements: config.defaultRichTextElements\n };\n}\n\nfunction assignUniqueKeysToFormatXMLElementFnArgument(values) {\n if (!values) {\n return values;\n }\n\n return Object.keys(values).reduce(function (acc, k) {\n var v = values[k];\n acc[k] = isFormatXMLElementFn(v) ? assignUniqueKeysToParts(v) : v;\n return acc;\n }, {});\n}\n\nvar formatMessage = function formatMessage(config, formatters, descriptor, rawValues) {\n var rest = [];\n\n for (var _i = 4; _i < arguments.length; _i++) {\n rest[_i - 4] = arguments[_i];\n }\n\n var values = assignUniqueKeysToFormatXMLElementFnArgument(rawValues);\n var chunks = coreFormatMessage.apply(void 0, __spreadArray([config, formatters, descriptor, values], rest, false));\n\n if (Array.isArray(chunks)) {\n return React.Children.toArray(chunks);\n }\n\n return chunks;\n};\n/**\n * Create intl object\n * @param config intl config\n * @param cache cache for formatter instances to prevent memory leak\n */\n\n\nexport var createIntl = function createIntl(_a, cache) {\n var rawDefaultRichTextElements = _a.defaultRichTextElements,\n config = __rest(_a, [\"defaultRichTextElements\"]);\n\n var defaultRichTextElements = assignUniqueKeysToFormatXMLElementFnArgument(rawDefaultRichTextElements);\n var coreIntl = coreCreateIntl(__assign(__assign(__assign({}, DEFAULT_INTL_CONFIG), config), {\n defaultRichTextElements: defaultRichTextElements\n }), cache);\n var resolvedConfig = {\n locale: coreIntl.locale,\n timeZone: coreIntl.timeZone,\n fallbackOnEmptyString: coreIntl.fallbackOnEmptyString,\n formats: coreIntl.formats,\n defaultLocale: coreIntl.defaultLocale,\n defaultFormats: coreIntl.defaultFormats,\n messages: coreIntl.messages,\n onError: coreIntl.onError,\n defaultRichTextElements: defaultRichTextElements\n };\n return __assign(__assign({}, coreIntl), {\n // @ts-expect-error fix this\n formatMessage: formatMessage.bind(null, resolvedConfig, coreIntl.formatters),\n // @ts-expect-error fix this\n $t: formatMessage.bind(null, resolvedConfig, coreIntl.formatters)\n });\n};\n\nvar IntlProvider =\n/** @class */\nfunction (_super) {\n __extends(IntlProvider, _super);\n\n function IntlProvider() {\n var _this = _super !== null && _super.apply(this, arguments) || this;\n\n _this.cache = createIntlCache();\n _this.state = {\n cache: _this.cache,\n intl: createIntl(processIntlConfig(_this.props), _this.cache),\n prevConfig: processIntlConfig(_this.props)\n };\n return _this;\n }\n\n IntlProvider.getDerivedStateFromProps = function (props, _a) {\n var prevConfig = _a.prevConfig,\n cache = _a.cache;\n var config = processIntlConfig(props);\n\n if (!shallowEqual(prevConfig, config)) {\n return {\n intl: createIntl(config, cache),\n prevConfig: config\n };\n }\n\n return null;\n };\n\n IntlProvider.prototype.render = function () {\n invariantIntlContext(this.state.intl);\n return React.createElement(Provider, {\n value: this.state.intl\n }, this.props.children);\n };\n\n IntlProvider.displayName = 'IntlProvider';\n IntlProvider.defaultProps = DEFAULT_INTL_CONFIG;\n return IntlProvider;\n}(React.PureComponent);\n\nexport default IntlProvider;","import * as React from \"react\"\nimport { IntlProvider } from 'react-intl';\n\ntype IntlProps = {\n locale: string\n children: any\n}\n\nexport const Intl = (props: IntlProps) => {\n const { children, locale } = props\n let messages = require(\"../copy/en/en\").lang\n try {\n messages = require(\"../copy/\" + locale + \"/\" + locale).lang\n } catch (error) {\n // NOOP\n }\n return (\n \n {children}\n \n )\n}\n","\n// Generated during bootstapping via pathsOnSiteTracker.ts\n \nexport const allFiles = [\"/docs/handbook/contributing\",\n\"/docs/handbook/eclipse-oomph\",\n\"/docs/handbook/intellij\",\n\"/docs/handbook/developer-setup\",\n\"/docs/handbook/regression-tests\",\n\"/docs/handbook/running-benchmarks\",\n\"/docs/handbook/website-development\",\n\"/docs/handbook/arduino\",\n\"/docs/handbook/zephyr\",\n\"/docs/handbook/proof-import\",\n\"/docs/handbook/logical-execution-time\",\n\"/docs/handbook/related-work\",\n\"/docs/handbook/timing-analysis\",\n\"/docs/handbook/tools\",\n\"/docs/handbook/language-specification\",\n\"/docs/handbook/generic-types-interfaces-inheritance\",\n\"/docs/handbook/import-system\",\n\"/docs/handbook/reactors-on-patmos\",\n\"/docs/handbook/features\",\n\"/docs/handbook/containerized-execution\",\n\"/docs/handbook/expressions\",\n\"/docs/handbook/security\",\n\"/docs/handbook/target-declaration\",\n\"/docs/handbook/tracing\",\n\"/docs/handbook/code-extension\",\n\"/docs/handbook/command-line-tools\",\n\"/docs/handbook/epoch-ide\",\n\"/docs/handbook/troubleshooting\",\n\"/docs/handbook/a-first-reactor\",\n\"/docs/handbook/actions\",\n\"/docs/handbook/causality-loops\",\n\"/docs/handbook/composing-reactors\",\n\"/docs/handbook/deadlines\",\n\"/docs/handbook/distributed-execution\",\n\"/docs/handbook/extending-reactors\",\n\"/docs/handbook/generics\",\n\"/docs/handbook/inputs-and-outputs\",\n\"/docs/handbook/methods\",\n\"/docs/handbook/modal-models\",\n\"/docs/handbook/multiports-and-banks\",\n\"/docs/handbook/overview\",\n\"/docs/handbook/parameters-and-state-variables\",\n\"/docs/handbook/preambles\",\n\"/docs/handbook/reaction-declarations\",\n\"/docs/handbook/reactions\",\n\"/docs/handbook/superdense-time\",\n\"/docs/handbook/termination\",\n\"/docs/handbook/time-and-timers\",\n\"/docs/handbook/tutorial-video\",\n\"/docs/handbook/target-language-details\",\n\"/community\",\n\"/download\",\n\"/empty\",\n\"/\",\n\"/publications-and-presentations\",\n\"/docs/\",\n\"/docs/handbook/\",]","import * as React from \"react\"\nimport { GatsbyLinkProps, Link } from \"gatsby\"\nimport { allFiles } from \"../__generated__/allPages\"\n\n/** \n * Creates a which supports gradual migration, you provide a link to the english page and\n * if the page supports the same version but in your language, it opts for that.\n */\nexport const createIntlLink = (currentLocale: string) => {\n const paths = allFiles\n\n return (linkProps: GatsbyLinkProps<{}>) => {\n let to = linkProps.to\n\n // /thing -> /ja/thing\n // This occurs when we want URL compat with old site\n\n const localeVersion = \"/\" + currentLocale + to\n if (currentLocale !== \"en\" && paths.includes(localeVersion)) {\n to = localeVersion\n }\n\n // This effectively needs to be duplicated in gatsby-config.js too\n const blocklistIncludes = [\"/play\", \"sandbox\", \"/dev\"]\n const blocklisted = blocklistIncludes.find(blocked => to.includes(blocked))\n\n if (blocklisted) {\n // @ts-ignore\n return \n } else {\n // @ts-ignore\n return \n }\n }\n}\n\n\n","import React, { useEffect } from \"react\"\nimport { withPrefix } from \"gatsby\"\n\nimport \"./TopNav.scss\"\nimport { useIntl } from \"react-intl\";\nimport { createIntlLink } from \"../IntlLink\";\nimport { setupStickyNavigation } from \"./stickyNavigation\";\n\nexport type Props = {\n lang: string\n}\n\nimport { navCopy } from \"../../copy/en/nav\"\nimport { createInternational } from \"../../lib/createInternational\"\n\nexport const SiteNav = (props: Props) => {\n const i = createInternational(useIntl())\n const IntlLink = createIntlLink(props.lang)\n const loadDocSearch = () => {\n const isDev = document.location.host.includes('localhost')\n let customHandleSelected;\n\n if (isDev) {\n customHandleSelected = (input, event, suggestion, datasetNumber, context) => {\n const urlToOpen = suggestion.url.replace(\"www.lf-lang.org\", \"localhost:8000\").replace(\"https\", \"http\")\n window.open(urlToOpen)\n }\n }\n\n // @ts-ignore - this comes from the script above\n docsearch({\n appId: \"U5IWIJW31Z\",\n apiKey: '0e594ee25f8bd7d73ae16a1af7076554',\n indexName: 'lf-lang',\n inputSelector: '.search input',\n handleSelected: customHandleSelected,\n });\n }\n // This extra bit of mis-direction ensures that non-essential code runs after\n // the page is loaded\n useEffect(() => {\n setupStickyNavigation()\n\n // @ts-ignore - this comes from the script above\n if (window.docsearch) {\n loadDocSearch();\n }\n if (document.getElementById(\"algolia-search\")) return\n\n const searchScript = document.createElement('script');\n searchScript.id = \"algolia-search\"\n const searchCSS = document.createElement('link');\n\n searchScript.src = withPrefix(\"/js/docsearch.js\");\n searchScript.async = true;\n searchScript.onload = () => {\n // @ts-ignore - this comes from the script above\n if (window.docsearch) {\n loadDocSearch();\n\n searchCSS.rel = 'stylesheet';\n searchCSS.href = withPrefix('/css/docsearch.css');\n searchCSS.type = 'text/css';\n document.body.appendChild(searchCSS);\n\n document.getElementById(\"search-form\")?.classList.add(\"search-enabled\")\n }\n }\n\n document.body.appendChild(searchScript);\n }, []);\n return (\n
\n {i(\"skip_to_content\")}\n\n
\n
\n\n \n \n \n \n \n \n\n \n\n
\n
\n
\n
\n
\n \n \n \n
\n
\n
\n
\n
\n\n
\n
\n )\n}\n","export function setupStickyNavigation() {\n const nav = document.getElementById(\"top-menu\")\n if (!nav) throw new Error(\"Didn't find a nav\")\n\n const sideButton = document.getElementById(\"small-device-button-sidebar\")\n let previousY = 9999\n\n const updateNav = () => {\n // iOS scrolls to make sure the viewport fits, don't hide the input then\n const hasKeyboardFocus =\n document.activeElement &&\n (document.activeElement.nodeName === \"INPUT\" ||\n document.activeElement.nodeName === \"TEXTAREA\")\n\n if (hasKeyboardFocus) {\n return\n }\n\n const showNav = () => {\n nav.classList.add(\"down\")\n nav.classList.remove(\"up\")\n sideButton?.classList.add(\"hidden\")\n }\n\n const hideNav = () => {\n nav.classList.add(\"up\")\n nav.classList.remove(\"down\")\n sideButton?.classList.remove(\"hidden\")\n }\n\n const goingUp = window.pageYOffset > 1 && window.pageYOffset > previousY\n previousY = window.pageYOffset\n\n if (goingUp) {\n showNav()\n } else {\n hideNav()\n }\n }\n\n // Non-blocking nav change\n document.removeEventListener(\"scroll\", updateNav, {\n capture: true,\n passive: true,\n } as any)\n\n document.addEventListener(\"scroll\", updateNav, {\n capture: true,\n passive: true,\n })\n}\n","import * as React from \"react\"\nimport { useState } from \"react\"\nimport { hasLocalStorage } from \"../../lib/hasLocalStorage\"\n\nimport { useIntl } from \"react-intl\";\n\nimport { footerCopy } from \"../../copy/en/footer\"\nimport { createInternational } from \"../../lib/createInternational\"\n\nconst makeDark = () => {\n document.documentElement.classList.remove(\"light-theme\")\n document.documentElement.classList.add(\"dark-theme\")\n}\n\nconst makeLight = () => {\n document.documentElement.classList.remove(\"dark-theme\")\n document.documentElement.classList.add(\"light-theme\")\n}\n\nconst switchFont = (newStyle: string, old?: string) => {\n if (old) document.documentElement.classList.remove(\"font-\" + old)\n document.documentElement.classList.add(\"font-\" + newStyle)\n}\n\nexport const Customize = () => {\n const i = createInternational(useIntl())\n const systemIsDark = typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches\n const customThemeOverride = hasLocalStorage && localStorage.getItem(\"force-color-theme\") || \"system\"\n const [darkModeValue, setDarkMode] = useState(customThemeOverride)\n\n const customFontOverride = hasLocalStorage && localStorage.getItem(\"force-font\") || \"cascadia\"\n const [fontValue, setFont] = useState(customFontOverride)\n\n // Localstorage: force-dark, force-light, undefined \n // ->\n // CSS Body class: theme-dark, theme-light, theme-dark | theme-light \n\n const handleThemeChange = (event: React.ChangeEvent) => {\n if (event.target.value === \"system\") {\n systemIsDark ? makeDark() : makeLight()\n hasLocalStorage && localStorage.removeItem(\"force-color-theme\")\n } else if (event.target.value === \"force-light\") {\n makeLight()\n hasLocalStorage && localStorage.setItem(\"force-color-theme\", \"force-light\")\n } else if (event.target.value === \"force-dark\") {\n makeDark()\n hasLocalStorage && localStorage.setItem(\"force-color-theme\", \"force-dark\")\n }\n\n setDarkMode(event.target.value)\n }\n\n\n // Localstorage: undefined, cascadia, cascadia-ligatures, consolas, ...\n // ->\n // CSS Body class: font-cascadia, font-cascadia, font-cascadia-ligatures | font-consolas, ...\n\n const handleFontChange = (event: React.ChangeEvent) => {\n localStorage.setItem(\"force-font\", event.target.value)\n switchFont(event.target.value, customFontOverride)\n setFont(event.target.value)\n\n }\n\n\n return (\n
\n
\n

{i(\"footer_customize\")}

\n \n\n \n
\n
\n )\n}\n","import * as React from \"react\"\nimport { useEffect } from \"react\"\n\nimport \"./SiteFooter.scss\"\nimport { createIntlLink } from \"../IntlLink\"\nimport { whenEscape } from \"../../lib/whenEscape\"\nimport { Customize } from \"./SiteFooter-Customize\"\n\nexport type Props = {\n lang: string\n suppressCustomization?: true\n suppressDocRecommendations?: true\n}\n\nconst useLinguaFrancaLinks = [\n {\n title: \"Get Started\",\n url: \"/docs/handbook/overview\",\n },\n {\n title: \"Download\",\n url: \"/download\",\n },\n {\n title: \"Why Lingua Franca\",\n url: \"/\",\n },\n {\n title: \"Publications\",\n url: \"/publications-and-presentations\",\n },\n]\n\nconst communityLinks = [\n {\n title: \"Get Help\",\n url: \"/community\",\n },\n {\n title: \"GitHub Repo\",\n url: \"https://github.com/lf-lang/lingua-franca\",\n },\n {\n title: \"@thelflang\",\n url: \"https://twitter.com/thelflang\",\n },\n {\n title: \"Web Repo\",\n url: \"https://github.com/lf-lang/website-lingua-franca\",\n },\n {\n title: \"Zulip\",\n url: \"https://zulip.lf-lang.org\",\n },\n]\n\nconst faviconForURL = (url: string) => {\n switch (url) {\n case \"https://github.com/lf-lang/website-lingua-franca\":\n case \"https://github.com/lf-lang/lingua-franca\":\n return (\n \n \n \n )\n case \"https://twitter.com/thelflang\":\n return (\n \n \n \n )\n case \"https://zulip.lf-lang.org\":\n return (\n \n \n \n \n )\n }\n}\n\nexport const SiteFooter = (props: Props) => {\n const normalLinks = useLinguaFrancaLinks.filter(\n l => !l.url.includes(\"#show-examples\")\n )\n\n const Link = createIntlLink(props.lang)\n\n const hideDocs = props.suppressDocRecommendations\n return (\n
\n {props.suppressCustomization ? null : }\n\n
\n
\n\n \n \n \n

Made with ♥ in Berkeley, Dallas, Dresden, Kiel, and Seoul\n

\n

\n © 2019-{new Date().getFullYear()} The Lingua Franca Team\n
\n

\n
\n\n
\n

Using Lingua Franca

\n
    \n {normalLinks.map(page => (\n
  • \n {page.title}\n
  • \n ))}\n
\n
\n\n
\n

Community

\n
    \n {communityLinks.map(page => {\n const favicon = faviconForURL(page.url)\n const favSpan = favicon ? ({favicon}) : null\n return (\n
  • \n \n {favSpan}\n {page.title}\n \n
  • \n )\n })}\n
\n
\n
\n\n
\n )\n}\n","import * as React from \"react\"\nimport { Helmet } from \"react-helmet\";\n\nexport type SeoProps = {\n title: string\n description: string\n ogTags?: { [key: string]: string }\n}\n\nexport const HeadSEO = (props: SeoProps) => {\n\n const ogTags = {\n ...props.ogTags,\n \"og:title\": props.title,\n \"og:description\": props.description,\n \"twitter:site\": \"thelflang\",\n }\n\n // Skip search engine indexing on the staging site, this is changed by running:\n // yarn workspace lingua-franca setup-staging\n const staging = false;\n\n if (staging) {\n ogTags[\"robots\"] = \"noindex\"\n }\n\n // do we want localized pages to be the english version?\n //{seo.url && }\n\n // TODO: a lot of pages should have this\n // \n\n // TODO: Maybe on prod we can generate an image for each file\n // \n\n return (\n <>\n \n \n {\n Object.keys(ogTags).map(k => )\n }\n \n \n )\n}\n","import * as React from \"react\"\nimport { SiteNav, Props } from \"./layout/TopNav\"\nimport { SiteFooter } from \"./layout/SiteFooter\"\nimport { SeoProps, HeadSEO } from \"./HeadSEO\";\nimport \"./layout/main.scss\"\nimport { Helmet } from \"react-helmet\";\nimport { withPrefix } from \"gatsby\";\n\ntype LayoutProps = SeoProps & Props & {\n lang: string,\n children: any\n suppressCustomization?: true\n suppressDocRecommendations?: true\n}\nexport const Layout = (props: LayoutProps) => {\n return (\n <>\n \n {/* Should be a NOOP for anything but edge, and much older browsers */}\n
Skip to main content

Publications

View our publications and presentations.

GitHub

Found a bug, or want to provide feedback? Tell us on GitHub.

Twitter

Stay up to date. Follow us on Twitter @thelflang!

Zulip

Have questions, or want to chat with other users? Join the conversation on Zulip.

Active Contributors

logo of Peter Donovan
Peter Donovan
Student Assistant at UC Berkeley.
🇺🇸
logo of Clément Fournier
Clément Fournier
Master student at TU Dresden.
🇩🇪
logo of Erling Rennemo Jellum
Erling Rennemo Jellum
PhD Candidate at Norwegian University of Science and Technology.
🇳🇴
Website
logo of Byeonggil Jun
Byeonggil Jun
Undergraduate student at Hanyang University.
🇰🇷
logo of Dongha Kim
Dongha Kim
Ph.D. student at Arizona State University.
🇺🇸
logo of Hokeun Kim
Hokeun Kim
Assistant Professor at Arizona State University.
🇺🇸
Website
logo of Edward A. Lee
Edward A. Lee
Professor in the Graduate School at UC Berkeley.
🇺🇸
Website
logo of Shaokai Lin
Shaokai Lin
Graduate Student at UC Berkeley.
🇺🇸
Website
logo of Marten Lohstroh
Marten Lohstroh
Postdoctoral researcher at UC Berkeley.
🇺🇸
Website Twitter
logo of Johannes Hayeß
Johannes Hayeß
Master's Student at TU Dresden.
🇩🇪
logo of Christian Menard
Christian Menard
Graduate Student at TU Dresden.
🇩🇪
Website
logo of Alexander Schulz-Rosengarten
Alexander Schulz-Rosengarten
Graduate student at Kiel University.
🇩🇪

Past Contributors

logo of Soroush Bateni
Soroush Bateni
Software Engineer at Apple.
🇺🇸
Website
logo of Matt Chorlian
Matt Chorlian
Applied Math and CS student at UC Berkeley.
🇺🇸
logo of Anirudh Rengarajan
Anirudh Rengarajan
Software Engineer at Bloomberg.
🇺🇸
logo of Martin Schoeberl
Martin Schoeberl
Professor at TU Denmark.
🇩🇰
logo of Matt Weber
Matt Weber
Software Engineer at Anyscale.
🇺🇸
logo of Hou Seng (Steven) Wong
Hou Seng (Steven) Wong
Software Development Engineer at Amazon AWS.
🇺🇸
\ No newline at end of file +
Skip to main content

Publications

View our publications and presentations.

GitHub

Found a bug, or want to provide feedback? Tell us on GitHub.

Twitter

Stay up to date. Follow us on Twitter @thelflang!

Zulip

Have questions, or want to chat with other users? Join the conversation on Zulip.

Active Contributors

logo of Peter Donovan
Peter Donovan
Student Assistant at UC Berkeley.
🇺🇸
logo of Clément Fournier
Clément Fournier
Master student at TU Dresden.
🇩🇪
logo of Erling Rennemo Jellum
Erling Rennemo Jellum
PhD Candidate at Norwegian University of Science and Technology.
🇳🇴
Website
logo of Byeonggil Jun
Byeonggil Jun
Undergraduate student at Hanyang University.
🇰🇷
logo of Dongha Kim
Dongha Kim
Ph.D. student at Arizona State University.
🇺🇸
logo of Hokeun Kim
Hokeun Kim
Assistant Professor at Arizona State University.
🇺🇸
Website
logo of Edward A. Lee
Edward A. Lee
Professor in the Graduate School at UC Berkeley.
🇺🇸
Website
logo of Shaokai Lin
Shaokai Lin
Graduate Student at UC Berkeley.
🇺🇸
Website
logo of Marten Lohstroh
Marten Lohstroh
Postdoctoral researcher at UC Berkeley.
🇺🇸
Website Twitter
logo of Johannes Hayeß
Johannes Hayeß
Master's Student at TU Dresden.
🇩🇪
logo of Christian Menard
Christian Menard
Graduate Student at TU Dresden.
🇩🇪
Website
logo of Alexander Schulz-Rosengarten
Alexander Schulz-Rosengarten
Graduate student at Kiel University.
🇩🇪

Past Contributors

logo of Soroush Bateni
Soroush Bateni
Software Engineer at Apple.
🇺🇸
Website
logo of Matt Chorlian
Matt Chorlian
Applied Math and CS student at UC Berkeley.
🇺🇸
logo of Anirudh Rengarajan
Anirudh Rengarajan
Software Engineer at Bloomberg.
🇺🇸
logo of Martin Schoeberl
Martin Schoeberl
Professor at TU Denmark.
🇩🇰
logo of Matt Weber
Matt Weber
Software Engineer at Anyscale.
🇺🇸
logo of Hou Seng (Steven) Wong
Hou Seng (Steven) Wong
Software Development Engineer at Amazon AWS.
🇺🇸
\ No newline at end of file diff --git a/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js b/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js similarity index 52% rename from component---src-templates-pages-download-tsx-581d01e80dae64748c16.js rename to component---src-templates-pages-download-tsx-0fc7218df25b40189519.js index c86356988..f5975dd68 100644 --- a/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js +++ b/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js @@ -1,2 +1,2 @@ -"use strict";(self.webpackChunklingua_franca=self.webpackChunklingua_franca||[]).push([[248],{569:function(e,t,l){l.r(t);var a=l(2784),n=l(2077),r=l(3314),o=function(e){return a.createElement(r.A,{title:"How to set up Lingua Franca",description:"Use Lingua Franca",lang:e.pageContext.lang},a.createElement("div",{className:"raised main-content-block"},a.createElement("h1",null,"Download and Install Lingua Franca"),a.createElement("p",null,"The Lingua Franca toolchain requires Java 17 (",a.createElement("a",{href:"https://www.oracle.com/java/technologies/downloads/"},"download from Oracle"),"). Each target language may have additional requirements (see the ",a.createElement("a",{href:"/docs/handbook/target-language-details#requirements"},"Target Language Details")," page and select your target language)."),a.createElement("p",null,"You can use Lingua Franca:",a.createElement("ul",null,a.createElement("li",null,a.createElement("a",{href:"#vscode"},"in Visual Studio Code, using our extension")),a.createElement("li",null,a.createElement("a",{href:"#download-epoch"},"using Epoch, an Eclipse-based IDE")),a.createElement("li",null,a.createElement("a",{href:"#download-cl"},"using the command line"))),"You can also spin up one of our pre-configured Cloud-based dev environments:",a.createElement("br",null),a.createElement("table",null,a.createElement("tr",null,a.createElement("td",null,a.createElement("a",{href:"https://gitpod.io/new#https://github.com/lf-lang/playground-lingua-franca/tree/main"},a.createElement("img",{src:"https://gitpod.io/button/open-in-gitpod.svg",alt:"Open in GitPod"}))),a.createElement("td",null,"  "),a.createElement("td",null,a.createElement("a",{href:"https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json"},a.createElement("img",{src:"https://github.com/codespaces/badge.svg",alt:"Open in GitHub Codespaces"})),a.createElement("br",null)))),"Have a look at ",a.createElement("a",{href:"https://github.com/lf-lang/playground-lingua-franca"},"the Lingua Franca playground")," for more details.")),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"vscode"},"Visual Studio Code"),a.createElement("p",null,"Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below. See the ",a.createElement("a",{href:"/docs/handbook/code-extension"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Marketplace"),a.createElement("p",null,"The Lingua Franca extension is available on the ",a.createElement("a",{href:"https://marketplace.visualstudio.com/items?itemName=lf-lang.vscode-lingua-franca"},"Visual Studio Marketplace")," and the ",a.createElement("a",{href:"https://open-vsx.org/extension/lf-lang/vscode-lingua-franca"},"VSX Registry"),". To install the extension, open VS Code, launch Quick Open (",a.createElement("kbd",null,"Ctrl")," + ",a.createElement("kbd",null,"P"),") and enter:",a.createElement("p",null,a.createElement("code",null,"ext install lf-lang.vscode-lingua-franca"))),a.createElement("p",null,"Alternatively, you can run the following command in your terminal:",a.createElement("p",null,a.createElement("code",null,"code --install-extension lf-lang.vscode-lingua-franca"))),a.createElement("p",null,'To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the "Switch to Pre-Release Version" button.')),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/vscode-lingua-franca"},"Lingua Franca VS Code GitHub repository")," for build instructions."))),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"download-epoch"},"Epoch IDE"),a.createElement("p",null,"There are multiple options available for installing Epoch as listed below. See the ",a.createElement("a",{href:"docs/handbook/epoch-ide"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Install Script"),a.createElement("p",null,"Run the following command in your terminal to install the latest release (on Windows, use WSL):",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s epoch"))),a.createElement("p",null,"You can also install the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s epoch nightly"))),a.createElement("p",null,"You can use the ",a.createElement("code",null,"--prefix=")," argument to change the default install location.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"AUR"),a.createElement("p",null,"There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with ",a.createElement("code",null,"yay"),":",a.createElement("p",null,a.createElement("code",null,"yay -S epoch-bin")),"or for the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"yay -S epoch-nightly-bin")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Manual Download"),a.createElement("p",null,"Regular and nightly release builds of Epoch can be downloaded from the ",a.createElement("a",{href:"https://github.com/lf-lang/epoch/releases"},"Epoch release page"),". Download the archive that matches your OS and architecture, and extract the contents."),a.createElement("p",null,"MacOS requires extra steps before being able to execute the app:",a.createElement("p",null,a.createElement("code",null,"xattr -cr Epoch.app")),"To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:",a.createElement("p",null,a.createElement("code",null,"open -a Epoch.app")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/epoch"},"Epoch GitHub repository")," for build instructions."))),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"download-cl"},"CLI Tools"),a.createElement("p",null,"There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the ",a.createElement("a",{href:"docs/handbook/command-line-tools"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Install Script"),a.createElement("p",null,"Run the following command in your terminal to install the latest release (on Windows, use WSL):",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s cli"))),a.createElement("p",null,"You can also install the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s cli nightly"))),a.createElement("p",null,"You can use the ",a.createElement("code",null,"--prefix=")," argument to change the default install location.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"AUR"),a.createElement("p",null,"There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with ",a.createElement("code",null,"yay"),":",a.createElement("p",null,a.createElement("code",null,"yay -S lf-cli-bin")),"or for the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"yay -S lf-cli-nightly-bin")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Manual Download"),a.createElement("p",null,"Regular and nightly release builds of the command line tools can be downloaded from the ",a.createElement("a",{href:"https://github.com/lf-lang/lingua-franca/releases"},"Lingua Franca release page"),". Download the archive that matches your OS and architecture, and extract the contents.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/lingua-franca"},"Lingua Franca GitHub repository")," for build instructions."),a.createElement("p",null,"If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup ",a.createElement("a",{href:"/docs/handbook/developer-setup"},"here"),"."))))};t.default=function(e){return a.createElement(n.R,{locale:e.pageContext.lang},a.createElement(o,e))}}}]); -//# sourceMappingURL=component---src-templates-pages-download-tsx-581d01e80dae64748c16.js.map \ No newline at end of file +"use strict";(self.webpackChunklingua_franca=self.webpackChunklingua_franca||[]).push([[248],{569:function(e,t,l){l.r(t);var a=l(2784),n=l(2077),r=l(3314),o=function(e){return a.createElement(r.A,{title:"How to set up Lingua Franca",description:"Use Lingua Franca",lang:e.pageContext.lang},a.createElement("div",{className:"raised main-content-block"},a.createElement("h1",null,"Download and Install Lingua Franca"),a.createElement("p",null,"The Lingua Franca toolchain requires Java 17 (",a.createElement("a",{href:"https://www.oracle.com/java/technologies/downloads/"},"download from Oracle"),"). Each target language may have additional requirements (see the ",a.createElement("a",{href:"/docs/handbook/target-language-details#requirements"},"Target Language Details")," page and select your target language)."),a.createElement("p",null,"You can use Lingua Franca:",a.createElement("ul",null,a.createElement("li",null,a.createElement("a",{href:"#vscode"},"in Visual Studio Code, using our extension")),a.createElement("li",null,a.createElement("a",{href:"#download-epoch"},"using Epoch, an Eclipse-based IDE")),a.createElement("li",null,a.createElement("a",{href:"#download-cl"},"using the command line"))),"You can also spin up one of our pre-configured Cloud-based dev environments:",a.createElement("br",null),a.createElement("table",null,a.createElement("tr",null,a.createElement("td",null,a.createElement("a",{href:"https://gitpod.io/new#https://github.com/lf-lang/playground-lingua-franca/tree/main"},a.createElement("img",{src:"https://gitpod.io/button/open-in-gitpod.svg",alt:"Open in GitPod"}))),a.createElement("td",null,"  "),a.createElement("td",null,a.createElement("a",{href:"https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json"},a.createElement("img",{src:"https://github.com/codespaces/badge.svg",alt:"Open in GitHub Codespaces"})),a.createElement("br",null)))),"Have a look at ",a.createElement("a",{href:"https://github.com/lf-lang/playground-lingua-franca"},"the Lingua Franca playground")," for more details.")),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"vscode"},"Visual Studio Code"),a.createElement("p",null,"Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below. See the ",a.createElement("a",{href:"/docs/handbook/code-extension"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Marketplace"),a.createElement("p",null,"The Lingua Franca extension is available on the ",a.createElement("a",{href:"https://marketplace.visualstudio.com/items?itemName=lf-lang.vscode-lingua-franca"},"Visual Studio Marketplace")," and the ",a.createElement("a",{href:"https://open-vsx.org/extension/lf-lang/vscode-lingua-franca"},"VSX Registry"),". To install the extension, open VS Code, launch Quick Open (",a.createElement("kbd",null,"Ctrl")," + ",a.createElement("kbd",null,"P"),") and enter:",a.createElement("p",null,a.createElement("code",null,"ext install lf-lang.vscode-lingua-franca"))),a.createElement("p",null,"Alternatively, you can run the following command in your terminal:",a.createElement("p",null,a.createElement("code",null,"code --install-extension lf-lang.vscode-lingua-franca"))),a.createElement("p",null,'To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the "Switch to Pre-Release Version" button.')),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/vscode-lingua-franca"},"Lingua Franca VS Code GitHub repository")," for build instructions."))),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"download-epoch"},"Epoch IDE"),a.createElement("p",null,"There are multiple options available for installing Epoch as listed below. See the ",a.createElement("a",{href:"docs/handbook/epoch-ide"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Install Script"),a.createElement("p",null,"Run the following command in your terminal to install the latest release (on Windows, use WSL):",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s epoch"))),a.createElement("p",null,"You can also install the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s epoch nightly"))),a.createElement("p",null,"You can use the ",a.createElement("code",null,"--prefix=")," argument to change the default install location."),a.createElement("p",null,"The default prefix is ",a.createElement("code",null,"/usr/local/bin")," on a Mac and ",a.createElement("code",null,"~/.local/bin")," on Linux and WSL. You may not have write access to this directory by default, in which case, if you still want to use the default prefix, you can replace ",a.createElement("code",null,"sh")," with ",a.createElement("code",null,"sudo sh")," in the above commands.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"AUR"),a.createElement("p",null,"There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with ",a.createElement("code",null,"yay"),":",a.createElement("p",null,a.createElement("code",null,"yay -S epoch-bin")),"or for the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"yay -S epoch-nightly-bin")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Manual Download"),a.createElement("p",null,"Regular and nightly release builds of Epoch can be downloaded from the ",a.createElement("a",{href:"https://github.com/lf-lang/epoch/releases"},"Epoch release page"),". Download the archive that matches your OS and architecture, and extract the contents."),a.createElement("p",null,"MacOS requires extra steps before being able to execute the app:",a.createElement("p",null,a.createElement("code",null,"xattr -cr Epoch.app")),"To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:",a.createElement("p",null,a.createElement("code",null,"open -a Epoch.app")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/epoch"},"Epoch GitHub repository")," for build instructions."))),a.createElement("div",{className:"raised main-content-block"},a.createElement("h2",{id:"download-cl"},"CLI Tools"),a.createElement("p",null,"There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the ",a.createElement("a",{href:"docs/handbook/command-line-tools"},"handbook")," for usage instructions."),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Install Script"),a.createElement("p",null,"Run the following command in your terminal to install the latest release (on Windows, use WSL):",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s cli"))),a.createElement("p",null,"You can also install the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"curl -Ls https://install.lf-lang.org | sh -s cli nightly"))),a.createElement("p",null,"You can use the ",a.createElement("code",null,"--prefix=")," argument to change the default install location.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"AUR"),a.createElement("p",null,"There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with ",a.createElement("code",null,"yay"),":",a.createElement("p",null,a.createElement("code",null,"yay -S lf-cli-bin")),"or for the nightly pre-release:",a.createElement("p",null,a.createElement("code",null,"yay -S lf-cli-nightly-bin")))),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"Manual Download"),a.createElement("p",null,"Regular and nightly release builds of the command line tools can be downloaded from the ",a.createElement("a",{href:"https://github.com/lf-lang/lingua-franca/releases"},"Lingua Franca release page"),". Download the archive that matches your OS and architecture, and extract the contents.")),a.createElement("div",{style:{borderTop:"1px lightgray solid"}},a.createElement("h3",null,"From Source"),a.createElement("p",null,"Please refer to the ",a.createElement("a",{href:"https://github.com/lf-lang/lingua-franca"},"Lingua Franca GitHub repository")," for build instructions."),a.createElement("p",null,"If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup ",a.createElement("a",{href:"/docs/handbook/developer-setup"},"here"),"."))))};t.default=function(e){return a.createElement(n.R,{locale:e.pageContext.lang},a.createElement(o,e))}}}]); +//# sourceMappingURL=component---src-templates-pages-download-tsx-0fc7218df25b40189519.js.map \ No newline at end of file diff --git a/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js.map b/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js.map new file mode 100644 index 000000000..8bb0bc17b --- /dev/null +++ b/component---src-templates-pages-download-tsx-0fc7218df25b40189519.js.map @@ -0,0 +1 @@ +{"version":3,"file":"component---src-templates-pages-download-tsx-0fc7218df25b40189519.js","mappings":"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","sources":["webpack://lingua-franca/./src/templates/pages/download.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { Intl } from \"../../components/Intl\"\nimport { Layout } from \"../../components/layout\"\n\ntype Props = {\n pageContext: any\n b: NewableFunction\n}\n\nconst Index: React.FC = (props) => {\n\n return \n
\n

Download and Install Lingua Franca

\n

\n The Lingua Franca toolchain requires Java 17 (download from Oracle).\n Each target language may have additional requirements (see the Target Language Details page and select your target language).\n

\n

\n You can use Lingua Franca:\n

\n You can also spin up one of our pre-configured Cloud-based dev environments:
\n \n \n \n \n \n \n
\n \"Open\n \n   \n \n \"Open
\n
\n Have a look at the Lingua Franca playground for more details. \n

\n
\n
\n

Visual Studio Code

\n

Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below.\n See the handbook for usage instructions.\n

\n
\n

Marketplace

\n

The Lingua Franca extension is available on the Visual Studio Marketplace and the VSX Registry. To install the extension, open VS Code, launch Quick Open (Ctrl + P) and enter:\n

ext install lf-lang.vscode-lingua-franca

\n

\n Alternatively, you can run the following command in your terminal:\n

code --install-extension lf-lang.vscode-lingua-franca

\n

To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the \"Switch to Pre-Release Version\" button.

\n
\n
\n

From Source

\n

Please refer to the Lingua Franca VS Code GitHub repository for build instructions.

\n
\n
\n\n
\n

Epoch IDE

\n

There are multiple options available for installing Epoch as listed below. See the handbook for usage instructions.

\n
\n

Install Script

\n

Run the following command in your terminal to install the latest release (on Windows, use WSL):\n

curl -Ls https://install.lf-lang.org | sh -s epoch

\n

\n

You can also install the nightly pre-release:\n

curl -Ls https://install.lf-lang.org | sh -s epoch nightly

\n

\n

You can use the --prefix=<path> argument to change the default install location.

\n

The default prefix is /usr/local/bin on a Mac and ~/.local/bin on Linux and WSL.\n You may not have write access to this directory by default, in which case, if you still want to use the default prefix,\n you can replace sh with sudo sh in the above commands.

\n
\n
\n

AUR

\n

There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:\n

yay -S epoch-bin

\n or for the nightly pre-release:\n

yay -S epoch-nightly-bin

\n

\n
\n
\n

Manual Download

\n

Regular and nightly release builds of Epoch can be downloaded from the Epoch release page. Download the archive that matches your OS and architecture, and extract the contents.

\n

MacOS requires extra steps before being able to execute the app:\n

xattr -cr Epoch.app

\n To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:\n

open -a Epoch.app

\n

\n
\n
\n

From Source

\n

Please refer to the Epoch GitHub repository for build instructions.

\n
\n
\n\n
\n

CLI Tools

\n

There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the handbook for usage instructions.

\n
\n

Install Script

\n

Run the following command in your terminal to install the latest release (on Windows, use WSL):\n

curl -Ls https://install.lf-lang.org | sh -s cli

\n

\n

You can also install the nightly pre-release:\n

curl -Ls https://install.lf-lang.org | sh -s cli nightly

\n

\n

You can use the --prefix=<path> argument to change the default install location.

\n
\n
\n

AUR

\n

There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:\n

yay -S lf-cli-bin

\n or for the nightly pre-release:\n

yay -S lf-cli-nightly-bin

\n

\n
\n
\n

Manual Download

\n

Regular and nightly release builds of the command line tools can be downloaded from the Lingua Franca release page. Download the archive that matches your OS and architecture, and extract the contents.

\n
\n
\n

From Source

\n

Please refer to the Lingua Franca GitHub repository for build instructions.

\n

If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup here.

\n
\n
\n\n
\n}\n\nexport default (props: Props) => \n"],"names":["Index","props","React","Layout","title","description","lang","pageContext","className","href","src","alt","id","style","borderTop","Intl","locale"],"sourceRoot":""} \ No newline at end of file diff --git a/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js.map b/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js.map deleted file mode 100644 index 4eee3b5c6..000000000 --- a/component---src-templates-pages-download-tsx-581d01e80dae64748c16.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"component---src-templates-pages-download-tsx-581d01e80dae64748c16.js","mappings":"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","sources":["webpack://lingua-franca/./src/templates/pages/download.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { Intl } from \"../../components/Intl\"\nimport { Layout } from \"../../components/layout\"\n\ntype Props = {\n pageContext: any\n b: NewableFunction\n}\n\nconst Index: React.FC = (props) => {\n\n return \n
\n

Download and Install Lingua Franca

\n

\n The Lingua Franca toolchain requires Java 17 (download from Oracle).\n Each target language may have additional requirements (see the Target Language Details page and select your target language).\n

\n

\n You can use Lingua Franca:\n

\n You can also spin up one of our pre-configured Cloud-based dev environments:
\n \n \n \n \n \n \n
\n \"Open\n \n   \n \n \"Open
\n
\n Have a look at the Lingua Franca playground for more details. \n

\n
\n
\n

Visual Studio Code

\n

Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below.\n See the handbook for usage instructions.\n

\n
\n

Marketplace

\n

The Lingua Franca extension is available on the Visual Studio Marketplace and the VSX Registry. To install the extension, open VS Code, launch Quick Open (Ctrl + P) and enter:\n

ext install lf-lang.vscode-lingua-franca

\n

\n Alternatively, you can run the following command in your terminal:\n

code --install-extension lf-lang.vscode-lingua-franca

\n

To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the \"Switch to Pre-Release Version\" button.

\n
\n
\n

From Source

\n

Please refer to the Lingua Franca VS Code GitHub repository for build instructions.

\n
\n
\n\n
\n

Epoch IDE

\n

There are multiple options available for installing Epoch as listed below. See the handbook for usage instructions.

\n
\n

Install Script

\n

Run the following command in your terminal to install the latest release (on Windows, use WSL):\n

curl -Ls https://install.lf-lang.org | sh -s epoch

\n

\n

You can also install the nightly pre-release:\n

curl -Ls https://install.lf-lang.org | sh -s epoch nightly

\n

\n

You can use the --prefix=<path> argument to change the default install location.

\n
\n
\n

AUR

\n

There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:\n

yay -S epoch-bin

\n or for the nightly pre-release:\n

yay -S epoch-nightly-bin

\n

\n
\n
\n

Manual Download

\n

Regular and nightly release builds of Epoch can be downloaded from the Epoch release page. Download the archive that matches your OS and architecture, and extract the contents.

\n

MacOS requires extra steps before being able to execute the app:\n

xattr -cr Epoch.app

\n To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:\n

open -a Epoch.app

\n

\n
\n
\n

From Source

\n

Please refer to the Epoch GitHub repository for build instructions.

\n
\n
\n\n
\n

CLI Tools

\n

There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the handbook for usage instructions.

\n
\n

Install Script

\n

Run the following command in your terminal to install the latest release (on Windows, use WSL):\n

curl -Ls https://install.lf-lang.org | sh -s cli

\n

\n

You can also install the nightly pre-release:\n

curl -Ls https://install.lf-lang.org | sh -s cli nightly

\n

\n

You can use the --prefix=<path> argument to change the default install location.

\n
\n
\n

AUR

\n

There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:\n

yay -S lf-cli-bin

\n or for the nightly pre-release:\n

yay -S lf-cli-nightly-bin

\n

\n
\n
\n

Manual Download

\n

Regular and nightly release builds of the command line tools can be downloaded from the Lingua Franca release page. Download the archive that matches your OS and architecture, and extract the contents.

\n
\n
\n

From Source

\n

Please refer to the Lingua Franca GitHub repository for build instructions.

\n

If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup here.

\n
\n
\n\n
\n}\n\nexport default (props: Props) => \n"],"names":["Index","props","React","Layout","title","description","lang","pageContext","className","href","src","alt","id","style","borderTop","Intl","locale"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/handbook/a-first-reactor/index.html b/docs/handbook/a-first-reactor/index.html index 14fe0a223..772389b65 100644 --- a/docs/handbook/a-first-reactor/index.html +++ b/docs/handbook/a-first-reactor/index.html @@ -210,4 +210,4 @@

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (17)
SBSoroush Bateni  (4)
CMChristian Menard  (3)
PDPeter Donovan  (2)
EALEdward A. Lee  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file + # Single-line Python-style comment.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (17)
SBSoroush Bateni  (4)
CMChristian Menard  (3)
PDPeter Donovan  (2)
EALEdward A. Lee  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/actions/index.html b/docs/handbook/actions/index.html index ad16661fe..e18d07278 100644 --- a/docs/handbook/actions/index.html +++ b/docs/handbook/actions/index.html @@ -425,4 +425,4 @@

Just like for inputs, this can be done in the Python target with a.is_present, where a is the name of the action. Just like for inputs, this can be done in the C++ target with a.is_present(), where a is the name of the action. Just like for inputs, this can be done in the TypeScript target with a != undefined, where a is the name of the action. -Just like for inputs, this can be done in the Rust target with ctx.is_present(a), where a is the name of the action.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (16)
SBSoroush Bateni  (12)
EALEdward A. Lee  (2)
PDPeter Donovan  (2)

Last updated: Oct 19, 2023

 

\ No newline at end of file +Just like for inputs, this can be done in the Rust target with ctx.is_present(a), where a is the name of the action.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (16)
SBSoroush Bateni  (12)
EALEdward A. Lee  (2)
PDPeter Donovan  (2)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/arduino/index.html b/docs/handbook/arduino/index.html index d3e46fa1c..70dec37ff 100644 --- a/docs/handbook/arduino/index.html +++ b/docs/handbook/arduino/index.html @@ -168,4 +168,4 @@

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
ARAnirudh Rengarajan  (4)
MLMarten Lohstroh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

arduino-cli upload -p PORT -b FQBN .

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
ARAnirudh Rengarajan  (4)
MLMarten Lohstroh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/causality-loops/index.html b/docs/handbook/causality-loops/index.html index fba4c3a35..6bbbfb3f8 100644 --- a/docs/handbook/causality-loops/index.html +++ b/docs/handbook/causality-loops/index.html @@ -470,4 +470,4 @@

Lingua Franca diagram -

There is no longer any causality loop.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (4)
EALEdward A. Lee  (1)
PDPeter Donovan  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

There is no longer any causality loop.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (4)
EALEdward A. Lee  (1)
PDPeter Donovan  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/code-extension/index.html b/docs/handbook/code-extension/index.html index 8c4b69696..8cfd28610 100644 --- a/docs/handbook/code-extension/index.html +++ b/docs/handbook/code-extension/index.html @@ -98,4 +98,4 @@

Notes

For Python Users

-

Users who edit LF programs with a Python target will benefit the most from Python linting by installing Pylint 2.12.2 or later.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (2)
PDPeter Donovan  (2)
CMChristian Menard  (1)
EALEdward A. Lee  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Users who edit LF programs with a Python target will benefit the most from Python linting by installing Pylint 2.12.2 or later.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (2)
PDPeter Donovan  (2)
CMChristian Menard  (1)
EALEdward A. Lee  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/command-line-tools/index.html b/docs/handbook/command-line-tools/index.html index 15b9b4596..853a0f6d1 100644 --- a/docs/handbook/command-line-tools/index.html +++ b/docs/handbook/command-line-tools/index.html @@ -88,4 +88,4 @@

To see the options that can be given to lfc, get help:

$ lfc --help
-

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (3)
Eeal  (3)
PDPeter Donovan  (2)
CMChristian Menard  (1)
JJakio815  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (3)
Eeal  (3)
PDPeter Donovan  (2)
CMChristian Menard  (1)
JJakio815  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/composing-reactors/index.html b/docs/handbook/composing-reactors/index.html index 3bb3ed78d..4ef0d39a9 100644 --- a/docs/handbook/composing-reactors/index.html +++ b/docs/handbook/composing-reactors/index.html @@ -338,4 +338,4 @@

-

In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender (unless fast is set to true), so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed-Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (25)
ARAnirudh Rengarajan  (3)
PDPeter Donovan  (2)
SBSoroush Bateni  (2)
EALEdward A. Lee  (1)
MLMarten Lohstroh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender (unless fast is set to true), so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed-Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (25)
ARAnirudh Rengarajan  (3)
PDPeter Donovan  (2)
SBSoroush Bateni  (2)
EALEdward A. Lee  (1)
MLMarten Lohstroh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/containerized-execution/index.html b/docs/handbook/containerized-execution/index.html index 9fb3fc257..c497b7025 100644 --- a/docs/handbook/containerized-execution/index.html +++ b/docs/handbook/containerized-execution/index.html @@ -136,4 +136,4 @@

Using docker compose up

For a federated Lingua Franca program, once you use lfc to compile Foo.lf, on top of the docker-compose.yml for the reactors, an additional docker-compose.yml will be generated for the RTI and placed in src-gen/RTI.

To run the federated program, open two terminals. In the first terminal, go to src-gen/RTI and use docker compose up to build and run the containerized RTI. Wait until the RTI is booted up. Then, open a second terminal and cd to the top level folder of the program (this is the folder that contains one folder for each of the federated reactors). You should see a docker-compose.yml there. Run docker compose up to build and run the containers.

-

Once the program finished executing, run docker compose down in both the folder that contains the docker-compose.yml for the RTI as well as the folder that contains the docker-compose.yml for the reactors to remove the containers.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (3)
PDPeter Donovan  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Once the program finished executing, run docker compose down in both the folder that contains the docker-compose.yml for the RTI as well as the folder that contains the docker-compose.yml for the reactors to remove the containers.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (3)
PDPeter Donovan  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/contributing/index.html b/docs/handbook/contributing/index.html index eeba5d34a..f60abd23e 100644 --- a/docs/handbook/contributing/index.html +++ b/docs/handbook/contributing/index.html @@ -78,4 +78,4 @@ display: block !important; }

Contributing

The preferred way to contribute to Lingua Franca is to issue pull requests through GitHub. -See the Contributing document for more details.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (3)
CMChristian Menard  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +See the Contributing document for more details.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (3)
CMChristian Menard  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/deadlines/index.html b/docs/handbook/deadlines/index.html index df7f9ba96..83751eb61 100644 --- a/docs/handbook/deadlines/index.html +++ b/docs/handbook/deadlines/index.html @@ -279,4 +279,4 @@

This is a (rather trivial) example of an anytime computation. Such computations proceed to improve results until time runs out and then produce the most improved result.

The arguments to the lf_check_deadline are the self struct and a boolean that indicates whether the deadline violation handler should be invoked upon detecting a deadline violation. Because the argument is true above, the handler is invoked and Stopped counting is printed.

-

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
SBSoroush Bateni  (5)
EALEdward A. Lee  (2)
PDPeter Donovan  (2)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
SBSoroush Bateni  (5)
EALEdward A. Lee  (2)
PDPeter Donovan  (2)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/developer-setup/index.html b/docs/handbook/developer-setup/index.html index 9a7182c66..9b65b379a 100644 --- a/docs/handbook/developer-setup/index.html +++ b/docs/handbook/developer-setup/index.html @@ -110,4 +110,4 @@

Currently, we provide two IDEs that support Lingua Franca programs. Their source code is located in external repositories. We have a Lingua Franca extension for VS code and an Eclipse based IDE called Epoch. -Please refer to the READMEs for build instructions.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
CMChristian Menard  (10)
PDPeter Donovan  (4)
MLMarten Lohstroh  (3)
Eeal  (2)
EALEdward A. Lee  (1)
HKHokeun Kim  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +Please refer to the READMEs for build instructions.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
CMChristian Menard  (10)
PDPeter Donovan  (4)
MLMarten Lohstroh  (3)
Eeal  (2)
EALEdward A. Lee  (1)
HKHokeun Kim  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/distributed-execution/index.html b/docs/handbook/distributed-execution/index.html index cc5b55b03..8bfa4027d 100644 --- a/docs/handbook/distributed-execution/index.html +++ b/docs/handbook/distributed-execution/index.html @@ -543,4 +543,4 @@

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
HKHokeun Kim  (13)
Eeal  (12)
PDPeter Donovan  (3)
EALEdward A. Lee  (2)
SBSoroush Bateni  (2)
JJakio815  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
HKHokeun Kim  (13)
Eeal  (12)
PDPeter Donovan  (3)
EALEdward A. Lee  (2)
SBSoroush Bateni  (2)
JJakio815  (1)
ARAnirudh Rengarajan  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/eclipse-oomph/index.html b/docs/handbook/eclipse-oomph/index.html index 0e5c3c509..6d4cbcfb2 100644 --- a/docs/handbook/eclipse-oomph/index.html +++ b/docs/handbook/eclipse-oomph/index.html @@ -308,4 +308,4 @@

-

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (4)
YYiweiIvy  (3)
PDPeter Donovan  (2)
ARAnirudh Rengarajan  (2)
AHAnees Hlaleh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (4)
YYiweiIvy  (3)
PDPeter Donovan  (2)
ARAnirudh Rengarajan  (2)
AHAnees Hlaleh  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/epoch-ide/index.html b/docs/handbook/epoch-ide/index.html index 3ee665da8..5cc3bf641 100644 --- a/docs/handbook/epoch-ide/index.html +++ b/docs/handbook/epoch-ide/index.html @@ -79,4 +79,4 @@ }

Epoch IDE

Epoch is a standalone application based on Eclipse that provides a syntax-directed editor, compiler, and diagram synthesis tool for Lingua Franca programs.

Usage

-

By default, Epoch is set to “Build Automatically” in the Project menu. This means that the LF code generator and compiler will be invoked every time you change a file and whenever you open a new project (on all files in the project). Many people prefer to turn this feature off and invoke the code generator by hand by clicking on the gear icon at the upper left.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (6)
Eeal  (5)
PDPeter Donovan  (2)
CMChristian Menard  (1)
EALEdward A. Lee  (1)
SLShaokai Lin  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

By default, Epoch is set to “Build Automatically” in the Project menu. This means that the LF code generator and compiler will be invoked every time you change a file and whenever you open a new project (on all files in the project). Many people prefer to turn this feature off and invoke the code generator by hand by clicking on the gear icon at the upper left.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (6)
Eeal  (5)
PDPeter Donovan  (2)
CMChristian Menard  (1)
EALEdward A. Lee  (1)
SLShaokai Lin  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/expressions/index.html b/docs/handbook/expressions/index.html index 34f1e37e4..f6cfb7ab3 100644 --- a/docs/handbook/expressions/index.html +++ b/docs/handbook/expressions/index.html @@ -183,4 +183,4 @@

FIXME: Rust

-

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
EALEdward A. Lee  (4)
PDPeter Donovan  (1)
MLMarten Lohstroh  (1)
HKHokeun Kim  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
EALEdward A. Lee  (4)
PDPeter Donovan  (1)
MLMarten Lohstroh  (1)
HKHokeun Kim  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/extending-reactors/index.html b/docs/handbook/extending-reactors/index.html index b0a430cd3..c7379a418 100644 --- a/docs/handbook/extending-reactors/index.html +++ b/docs/handbook/extending-reactors/index.html @@ -155,4 +155,4 @@

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
SBSoroush Bateni  (3)
EALEdward A. Lee  (1)
PDPeter Donovan  (1)
Rrevol-xut  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (6)
SBSoroush Bateni  (3)
EALEdward A. Lee  (1)
PDPeter Donovan  (1)
Rrevol-xut  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/features/index.html b/docs/handbook/features/index.html index 1090afebc..e9580b9ac 100644 --- a/docs/handbook/features/index.html +++ b/docs/handbook/features/index.html @@ -112,4 +112,4 @@ N -

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (7)
Hhousengw  (2)
ARAnirudh Rengarajan  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file +

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
MLMarten Lohstroh  (7)
Hhousengw  (2)
ARAnirudh Rengarajan  (1)
SBSoroush Bateni  (1)

Last updated: Oct 19, 2023

 

\ No newline at end of file diff --git a/docs/handbook/generic-types-interfaces-inheritance/index.html b/docs/handbook/generic-types-interfaces-inheritance/index.html index d3c2891e6..03f603678 100644 --- a/docs/handbook/generic-types-interfaces-inheritance/index.html +++ b/docs/handbook/generic-types-interfaces-inheritance/index.html @@ -98,4 +98,4 @@

Reactions are inherited in the order of declaration; and
  • Equally-named ports and actions between subclass and superclass must also be equally typed.
  • -

    Example

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (7)
    PDPeter Donovan  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Example

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (7)
    PDPeter Donovan  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/generics/index.html b/docs/handbook/generics/index.html index fa6a46a2f..5e53fac31 100644 --- a/docs/handbook/generics/index.html +++ b/docs/handbook/generics/index.html @@ -173,4 +173,4 @@

    This reactor could be instantiated for example like this:

    g = new Generic<float, int, bool>()
     
    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (2)
    EALEdward A. Lee  (1)
    CMChristian Menard  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (2)
    EALEdward A. Lee  (1)
    CMChristian Menard  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/import-system/index.html b/docs/handbook/import-system/index.html index 6c55d4803..607cadefa 100644 --- a/docs/handbook/import-system/index.html +++ b/docs/handbook/import-system/index.html @@ -146,4 +146,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (10)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +}

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (10)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/index.html b/docs/handbook/index.html index a80ab4eec..b40edea56 100644 --- a/docs/handbook/index.html +++ b/docs/handbook/index.html @@ -73,4 +73,4 @@ const customFontOverride = hasLocalStorage && localStorage.getItem("force-font") || "cascadia" document.documentElement.classList.add('font-' + customFontOverride) })() -
    Skip to main content

    Lingua Franca Documentation

    \ No newline at end of file +
    Skip to main content

    Lingua Franca Documentation

    \ No newline at end of file diff --git a/docs/handbook/inputs-and-outputs/index.html b/docs/handbook/inputs-and-outputs/index.html index c4af1b067..f488537c3 100644 --- a/docs/handbook/inputs-and-outputs/index.html +++ b/docs/handbook/inputs-and-outputs/index.html @@ -249,4 +249,4 @@

    mutable input <name> -

    This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-specific reference documentation has more details about how this works.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (15)
    SBSoroush Bateni  (6)
    CMChristian Menard  (2)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-specific reference documentation has more details about how this works.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (15)
    SBSoroush Bateni  (6)
    CMChristian Menard  (2)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/intellij/index.html b/docs/handbook/intellij/index.html index 66fee9179..a0fdb618b 100644 --- a/docs/handbook/intellij/index.html +++ b/docs/handbook/intellij/index.html @@ -265,4 +265,4 @@

    Integration Tests

    -

    You can also run the integration test from IntelliJ. You will find the targetTest and singleTest tasks in the Gradle tab under org.lflang > Tasks > other. Make sure to add a run configuration as shown above and append -Ptarget=...' to the targetTest command or -DsingleTest=... to your singleTest command to specify the target (e.g., C) or the specific test that you would like to run.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    PDPeter Donovan  (5)
    MLMarten Lohstroh  (4)
    Eeal  (3)
    GGuangyu-Joshua-Feng  (2)
    CMChristian Menard  (2)
    EALEdward A. Lee  (2)
    ARAnirudh Rengarajan  (2)
    1+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    You can also run the integration test from IntelliJ. You will find the targetTest and singleTest tasks in the Gradle tab under org.lflang > Tasks > other. Make sure to add a run configuration as shown above and append -Ptarget=...' to the targetTest command or -DsingleTest=... to your singleTest command to specify the target (e.g., C) or the specific test that you would like to run.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    PDPeter Donovan  (5)
    MLMarten Lohstroh  (4)
    Eeal  (3)
    GGuangyu-Joshua-Feng  (2)
    CMChristian Menard  (2)
    EALEdward A. Lee  (2)
    ARAnirudh Rengarajan  (2)
    1+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/language-specification/index.html b/docs/handbook/language-specification/index.html index c9730c0da..aa6b81395 100644 --- a/docs/handbook/language-specification/index.html +++ b/docs/handbook/language-specification/index.html @@ -611,4 +611,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (3)
    PDPeter Donovan  (2)
    Eeal  (2)
    ARAnirudh Rengarajan  (2)
    HKHokeun Kim  (1)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (3)
    PDPeter Donovan  (2)
    Eeal  (2)
    ARAnirudh Rengarajan  (2)
    HKHokeun Kim  (1)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/logical-execution-time/index.html b/docs/handbook/logical-execution-time/index.html index f5abad5ff..b87eb3d19 100644 --- a/docs/handbook/logical-execution-time/index.html +++ b/docs/handbook/logical-execution-time/index.html @@ -77,4 +77,4 @@ nav#sidebar > ul > li.closed ul { display: block !important; } -

    Logical Execution Time

    FIXME

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Logical Execution Time

    FIXME

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/methods/index.html b/docs/handbook/methods/index.html index 16cef4763..4bac8f1a3 100644 --- a/docs/handbook/methods/index.html +++ b/docs/handbook/methods/index.html @@ -169,4 +169,4 @@ and receives one integer argument, which it uses to increment foo. Both methods are visible in all reactions of the reactor. In this example, the reaction to startup calls both methods in order to read and modify its state.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (2)
    Eeal  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (2)
    Eeal  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/modal-models/index.html b/docs/handbook/modal-models/index.html index 4ff376c0c..298064a67 100644 --- a/docs/handbook/modal-models/index.html +++ b/docs/handbook/modal-models/index.html @@ -209,4 +209,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    ASAlexander Schulz-Rosengarten  (5)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (2)
    ASAlexander SR  (2)
    MLMarten Lohstroh  (1)
    BBen  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    ASAlexander Schulz-Rosengarten  (5)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (2)
    ASAlexander SR  (2)
    MLMarten Lohstroh  (1)
    BBen  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/multiports-and-banks/index.html b/docs/handbook/multiports-and-banks/index.html index f2b3785f1..6089e8f81 100644 --- a/docs/handbook/multiports-and-banks/index.html +++ b/docs/handbook/multiports-and-banks/index.html @@ -994,4 +994,4 @@

    The interleaved keyword is not supported by CCppPythonTypeScriptRust.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (10)
    SBSoroush Bateni  (6)
    PDPeter Donovan  (3)
    EALEdward A. Lee  (1)
    JJakio815  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (10)
    SBSoroush Bateni  (6)
    PDPeter Donovan  (3)
    EALEdward A. Lee  (1)
    JJakio815  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/overview/index.html b/docs/handbook/overview/index.html index 1306bc97e..eeb61d3ee 100644 --- a/docs/handbook/overview/index.html +++ b/docs/handbook/overview/index.html @@ -132,4 +132,4 @@

    We could also use a big hammer: model the LF program as timed automata and do model checking (e.g., UupAal)

    To Do List

    -

    Lingua Franca is a work in progress. See our project page for an overview of ongoing and future work.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (33)
    OTOrta Therox  (13)
    MLMarten Lohstroh  (6)
    ARAnirudh Rengarajan  (3)
    PDPeter Donovan  (2)
    MSMartin Schoeberl  (2)
    Eeal  (1)
    13+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is a work in progress. See our project page for an overview of ongoing and future work.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (33)
    OTOrta Therox  (13)
    MLMarten Lohstroh  (6)
    ARAnirudh Rengarajan  (3)
    PDPeter Donovan  (2)
    MSMartin Schoeberl  (2)
    Eeal  (1)
    13+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/parameters-and-state-variables/index.html b/docs/handbook/parameters-and-state-variables/index.html index 9365882de..5d6591d63 100644 --- a/docs/handbook/parameters-and-state-variables/index.html +++ b/docs/handbook/parameters-and-state-variables/index.html @@ -245,4 +245,4 @@

    reset state <name> = <value>

    When this is done, if the state variable or the reactor is within a mode of a modal reactor, then when the mode is entered via a reset transition, the state variable will be reset to its initial value. For details, see the Modal Reactors section.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (25)
    SBSoroush Bateni  (3)
    ARAnirudh Rengarajan  (3)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (25)
    SBSoroush Bateni  (3)
    ARAnirudh Rengarajan  (3)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/preambles/index.html b/docs/handbook/preambles/index.html index df6e8d6ed..d6f140440 100644 --- a/docs/handbook/preambles/index.html +++ b/docs/handbook/preambles/index.html @@ -411,4 +411,4 @@

    FIXME: Add preamble example.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (8)
    SBSoroush Bateni  (5)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (3)
    CMChristian Menard  (1)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (8)
    SBSoroush Bateni  (5)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (3)
    CMChristian Menard  (1)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/proof-import/index.html b/docs/handbook/proof-import/index.html index 9cb00ea43..873302a0b 100644 --- a/docs/handbook/proof-import/index.html +++ b/docs/handbook/proof-import/index.html @@ -469,4 +469,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (16)
    CMChristian Menard  (15)
    PDPeter Donovan  (1)
    Eeal  (1)
    ARAnirudh Rengarajan  (1)
    CFClément Fournier  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (16)
    CMChristian Menard  (15)
    PDPeter Donovan  (1)
    Eeal  (1)
    ARAnirudh Rengarajan  (1)
    CFClément Fournier  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/reaction-declarations/index.html b/docs/handbook/reaction-declarations/index.html index 3566d0d6e..f3762e5da 100644 --- a/docs/handbook/reaction-declarations/index.html +++ b/docs/handbook/reaction-declarations/index.html @@ -189,4 +189,4 @@

    The CCppPythonTypeScriptRust target does not currently support reaction declarations.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (3)
    MLMarten Lohstroh  (3)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (3)
    MLMarten Lohstroh  (3)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/reactions/index.html b/docs/handbook/reactions/index.html index 2ef23659e..712d294d9 100644 --- a/docs/handbook/reactions/index.html +++ b/docs/handbook/reactions/index.html @@ -549,4 +549,4 @@

    The CCppPythonTypeScriptRust target does not currently support reaction declarations.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (6)
    SBSoroush Bateni  (6)
    Eeal  (6)
    EALEdward A. Lee  (4)
    PDPeter Donovan  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (6)
    SBSoroush Bateni  (6)
    Eeal  (6)
    EALEdward A. Lee  (4)
    PDPeter Donovan  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/reactors-on-patmos/index.html b/docs/handbook/reactors-on-patmos/index.html index 16fc84112..6d125f860 100644 --- a/docs/handbook/reactors-on-patmos/index.html +++ b/docs/handbook/reactors-on-patmos/index.html @@ -152,4 +152,4 @@

    Patmos Reference Handbook.

    Note, that the WCET analysis of a reaction does only include the code of the reaction function, not the cache miss cost of calling the function from -the scheduler or the cache miss cost when returning to the scheduler.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (1)
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +the scheduler or the cache miss cost when returning to the scheduler.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (1)
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/regression-tests/index.html b/docs/handbook/regression-tests/index.html index 9140ce37c..04d63be54 100644 --- a/docs/handbook/regression-tests/index.html +++ b/docs/handbook/regression-tests/index.html @@ -146,4 +146,4 @@

    core subproject, the html report will be located in build/reports/html/index.html. Note that this report will only reflect the coverage of the test that have actually executed.

    Continuous Integration

    -

    Each push or pull request will trigger all tests to be run on GitHub Actions. It’s configuration can be found here.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (2)
    PDPeter Donovan  (2)
    Eeal  (2)
    MLMarten Lohstroh  (1)
    EALEdward A. Lee  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Each push or pull request will trigger all tests to be run on GitHub Actions. It’s configuration can be found here.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    CMChristian Menard  (2)
    PDPeter Donovan  (2)
    Eeal  (2)
    MLMarten Lohstroh  (1)
    EALEdward A. Lee  (1)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/related-work/index.html b/docs/handbook/related-work/index.html index 703a9bfd8..22f9a1d66 100644 --- a/docs/handbook/related-work/index.html +++ b/docs/handbook/related-work/index.html @@ -142,4 +142,4 @@

    RADLER framework from SRI, which is based on a publish-and-subscribe architecture similar to ROS. It introduces some timing constructs such as periodic execution and scheduling constraints, but it makes no effort to be deterministic.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (19)
    MLMarten Lohstroh  (3)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (19)
    MLMarten Lohstroh  (3)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/running-benchmarks/index.html b/docs/handbook/running-benchmarks/index.html index 2eebdf8a8..b6580d170 100644 --- a/docs/handbook/running-benchmarks/index.html +++ b/docs/handbook/running-benchmarks/index.html @@ -237,4 +237,4 @@

    pings: ["-D", "count=<value>"]

    This is very similar to the C++ configuration. However, the C target of LF currently does not support overriding of parameter values at runtime. Therefore, all parameters need to be provided as arguments to the code generator and the benchmark needs to provide corresponding cog directives.

    -

    New benchmarks can be simply added by replicating this example and adjusting the precise configuration values and parameters to the specific benchmark.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (3)
    PDPeter Donovan  (2)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    New benchmarks can be simply added by replicating this example and adjusting the precise configuration values and parameters to the specific benchmark.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (3)
    PDPeter Donovan  (2)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/security/index.html b/docs/handbook/security/index.html index da001fde3..171f51ba3 100644 --- a/docs/handbook/security/index.html +++ b/docs/handbook/security/index.html @@ -96,4 +96,4 @@

    sudo make install

    If you would like to go back to non-AUTH mode, you would have to remove all contents of the build folder.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    JJakio815  (7)
    D(KDongha (Jake) Kim  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    JJakio815  (7)
    D(KDongha (Jake) Kim  (2)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/superdense-time/index.html b/docs/handbook/superdense-time/index.html index 4768a3ab4..5d2f68310 100644 --- a/docs/handbook/superdense-time/index.html +++ b/docs/handbook/superdense-time/index.html @@ -305,4 +305,4 @@

    Alignment of Logical and Physical Times

    -

    Recall that in Lingua Franca, logical time “chases” physical time, invoking reactions at a physical time close to their logical time. For that purpose, the microstep is ignored.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (9)
    Eeal  (4)
    EALEdward A. Lee  (1)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Recall that in Lingua Franca, logical time “chases” physical time, invoking reactions at a physical time close to their logical time. For that purpose, the microstep is ignored.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (9)
    Eeal  (4)
    EALEdward A. Lee  (1)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/target-declaration/index.html b/docs/handbook/target-declaration/index.html index a4962e298..15ddda74d 100644 --- a/docs/handbook/target-declaration/index.html +++ b/docs/handbook/target-declaration/index.html @@ -641,4 +641,4 @@

    =} } -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (7)
    EALEdward A. Lee  (6)
    JJakio815  (4)
    CMChristian Menard  (4)
    HKHokeun Kim  (3)
    PDPeter Donovan  (2)
    JHJohannes Hayeß  (2)
    3+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (7)
    EALEdward A. Lee  (6)
    JJakio815  (4)
    CMChristian Menard  (4)
    HKHokeun Kim  (3)
    PDPeter Donovan  (2)
    JHJohannes Hayeß  (2)
    3+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/target-language-details/index.html b/docs/handbook/target-language-details/index.html index ebe00bff3..9b044a742 100644 --- a/docs/handbook/target-language-details/index.html +++ b/docs/handbook/target-language-details/index.html @@ -2699,4 +2699,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (20)
    Eeal  (16)
    PDPeter Donovan  (12)
    SBSoroush Bateni  (10)
    SSteven  (4)
    ERJErling Rennemo Jellum  (1)
    MLMarten Lohstroh  (1)
    1+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +````

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (20)
    Eeal  (16)
    PDPeter Donovan  (12)
    SBSoroush Bateni  (10)
    SSteven  (4)
    ERJErling Rennemo Jellum  (1)
    MLMarten Lohstroh  (1)
    1+

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/termination/index.html b/docs/handbook/termination/index.html index 7f9458b5b..5275d0a05 100644 --- a/docs/handbook/termination/index.html +++ b/docs/handbook/termination/index.html @@ -112,4 +112,4 @@

    For federated programs, each federate and the RTI catches external signals to shut down in an orderly way.

    When a federate gets such an external signal (e.g. control-C), it sends a RESIGN message to the RTI and an EOF (end of file) on each socket connection to another federate. It then closes all sockets and shuts down. The RTI and all other federates should continue running until some other termination condition occurs.

    When the RTI gets such an external signal (e.g. control-C), it broadcasts a STOP_REQUEST message to all federates, waits for their replies (with a timeout in case the federate or the network has failed), chooses the maximum timestamp s on the replies, broadcasts a STOP_GRANTED message to all federates with payload s, and waits for LOGICAL_TIME_COMPLETE messages as above.

    -

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (5)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (5)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/time-and-timers/index.html b/docs/handbook/time-and-timers/index.html index 0608acda9..12a8c1d63 100644 --- a/docs/handbook/time-and-timers/index.html +++ b/docs/handbook/time-and-timers/index.html @@ -557,4 +557,4 @@

    shutdown reaction.

    -

    The shutdown trigger typically occurs at microstep 0, but may occur at a larger microstep. See Superdense Time and Termination.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (15)
    SBSoroush Bateni  (8)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (2)
    HKHokeun Kim  (1)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    The shutdown trigger typically occurs at microstep 0, but may occur at a larger microstep. See Superdense Time and Termination.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (15)
    SBSoroush Bateni  (8)
    EALEdward A. Lee  (3)
    PDPeter Donovan  (2)
    HKHokeun Kim  (1)
    Rrevol-xut  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/timing-analysis/index.html b/docs/handbook/timing-analysis/index.html index 0ad10cc2e..0984d2c2b 100644 --- a/docs/handbook/timing-analysis/index.html +++ b/docs/handbook/timing-analysis/index.html @@ -132,4 +132,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MSMartin Schoeberl  (7)
    EALEdward A. Lee  (7)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Preemption can be avoided when there are enough cores (or hardware threads in PRET) available to execute r1 and r3 concurrently.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MSMartin Schoeberl  (7)
    EALEdward A. Lee  (7)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/tools/index.html b/docs/handbook/tools/index.html index e89f7b33e..b6e07d7de 100644 --- a/docs/handbook/tools/index.html +++ b/docs/handbook/tools/index.html @@ -87,4 +87,4 @@ +--------+ err | +----------+ gen err +----------+ | | Language Server | +-------------------------------------+ -

    If the LF compiler encounters any syntax errors, it will report them to the editor (the language client). If the LF code compiles, the output will be sent to the target compiler. If the target compiler reports any errors, these, too, will be reported to the editor via the language server. The tricky part is to match target language errors to LF source locations; the language server will have to do some bookkeeping.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (7)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    If the LF compiler encounters any syntax errors, it will report them to the editor (the language client). If the LF code compiles, the output will be sent to the target compiler. If the target compiler reports any errors, these, too, will be reported to the editor via the language server. The tricky part is to match target language errors to LF source locations; the language server will have to do some bookkeeping.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    MLMarten Lohstroh  (7)
    ARAnirudh Rengarajan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/tracing/index.html b/docs/handbook/tracing/index.html index 8e75d413b..dcd4d5416 100644 --- a/docs/handbook/tracing/index.html +++ b/docs/handbook/tracing/index.html @@ -347,4 +347,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (4)
    Eeal  (3)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    ARAnirudh Rengarajan  (2)
    CJChadlia Jerad  (1)
    ERJErling Rennemo Jellum  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    SBSoroush Bateni  (4)
    Eeal  (3)
    EALEdward A. Lee  (2)
    PDPeter Donovan  (2)
    ARAnirudh Rengarajan  (2)
    CJChadlia Jerad  (1)
    ERJErling Rennemo Jellum  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/troubleshooting/index.html b/docs/handbook/troubleshooting/index.html index d48d8248f..47e8c494e 100644 --- a/docs/handbook/troubleshooting/index.html +++ b/docs/handbook/troubleshooting/index.html @@ -91,4 +91,4 @@

    Epoch and the Visual Studio Code extension use the same environment as the command-line tools is to invoke them on the command line. For example, on a Mac, you can invoke Epoch and Visual Studio Code as follows:

    $ open -a epoch
     $ code .
    -

    This way, the tools inherit the environment from the shell from which you invoke them. Often, that environment is quite different from what you get if, for example, you invoke the tools by double clicking on their icons.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (4)
    CMChristian Menard  (1)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    This way, the tools inherit the environment from the shell from which you invoke them. Often, that environment is quite different from what you get if, for example, you invoke the tools by double clicking on their icons.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eeal  (4)
    CMChristian Menard  (1)
    PDPeter Donovan  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/tutorial-video/index.html b/docs/handbook/tutorial-video/index.html index 2069e8f0d..5e693ec9b 100644 --- a/docs/handbook/tutorial-video/index.html +++ b/docs/handbook/tutorial-video/index.html @@ -342,4 +342,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    PDPeter Donovan  (1)
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    PDPeter Donovan  (1)
    Eeal  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/website-development/index.html b/docs/handbook/website-development/index.html index ea8d872a8..fc9483f4a 100644 --- a/docs/handbook/website-development/index.html +++ b/docs/handbook/website-development/index.html @@ -81,4 +81,4 @@

    First, for simple changes to the website, such as fixing typos, the easiest way is to scroll to the bottom of the page, follow the link at the bottom to send a pull request, edit the resulting page, and issue a pull request.

    For more elaborate changes, including adding new pages, you will need to clone the GitHub repository. You can then set up your local clone to provide a local copy of the website at http://localhost:8000 by following the instructions in the README file. This way, you can test your changes before issuing a pull request.

    Editing the Handbook

    -

    The handbook is the most updated part of the website and it includes quite a bit of infrastructure to support writing pages that describe features in any or all of the target languages. The root of the handbook pages in the packages/documentation part of the repo. That directory has a useful README file that describes the structure and provides instructions for inserting code examples in any target language and target-specific text within a body of target-independent text.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +

    The handbook is the most updated part of the website and it includes quite a bit of infrastructure to support writing pages that describe features in any or all of the target languages. The root of the handbook pages in the packages/documentation part of the repo. That directory has a useful README file that describes the structure and provides instructions for inserting code examples in any target language and target-specific text within a body of target-independent text.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    EALEdward A. Lee  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/handbook/zephyr/index.html b/docs/handbook/zephyr/index.html index 75614d4ec..518778a5c 100644 --- a/docs/handbook/zephyr/index.html +++ b/docs/handbook/zephyr/index.html @@ -233,4 +233,4 @@

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eerlingrj  (11)
    Eerling  (4)
    ERJErling Rennemo Jellum  (1)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file +Please refer to the Getting Started section to purge the system of old Zephyr installations.

    Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

    Contributors to this page:
    Eerlingrj  (11)
    Eerling  (4)
    ERJErling Rennemo Jellum  (1)
    MLMarten Lohstroh  (1)

    Last updated: Oct 19, 2023

     

    \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 31adea5f1..2291072f6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -73,4 +73,4 @@ const customFontOverride = hasLocalStorage && localStorage.getItem("force-font") || "cascadia" document.documentElement.classList.add('font-' + customFontOverride) })() -
    Skip to main content

    Lingua Franca Documentation

    Resources

    Overview of the project.

    We also have pdf versions for the following languages:
    c

    cpp

    py

    ts

    rs

    Tools

    Tools for developing Lingua Franca programs.

    Embedded Platforms

    Documentation for developing Lingua Franca on Embedded Platforms.

    Developer

    Information for developers of the Lingua Franca language and tools.

    Learning Resources

    \ No newline at end of file +
    Skip to main content

    Lingua Franca Documentation

    Resources

    Overview of the project.

    We also have pdf versions for the following languages:
    c

    cpp

    py

    ts

    rs

    Tools

    Tools for developing Lingua Franca programs.

    Embedded Platforms

    Documentation for developing Lingua Franca on Embedded Platforms.

    Developer

    Information for developers of the Lingua Franca language and tools.

    Learning Resources

    \ No newline at end of file diff --git a/download/index.html b/download/index.html index a4cf1328d..29a032332 100644 --- a/download/index.html +++ b/download/index.html @@ -73,4 +73,4 @@ const customFontOverride = hasLocalStorage && localStorage.getItem("force-font") || "cascadia" document.documentElement.classList.add('font-' + customFontOverride) })() -
    Skip to main content

    Download and Install Lingua Franca

    The Lingua Franca toolchain requires Java 17 (download from Oracle). Each target language may have additional requirements (see the Target Language Details page and select your target language).

    You can use Lingua Franca:

    You can also spin up one of our pre-configured Cloud-based dev environments:
    Open in GitPod  Open in GitHub Codespaces
    Have a look at the Lingua Franca playground for more details.

    Visual Studio Code

    Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below. See the handbook for usage instructions.

    Marketplace

    The Lingua Franca extension is available on the Visual Studio Marketplace and the VSX Registry. To install the extension, open VS Code, launch Quick Open (Ctrl + P) and enter:

    ext install lf-lang.vscode-lingua-franca

    Alternatively, you can run the following command in your terminal:

    code --install-extension lf-lang.vscode-lingua-franca

    To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the "Switch to Pre-Release Version" button.

    From Source

    Please refer to the Lingua Franca VS Code GitHub repository for build instructions.

    Epoch IDE

    There are multiple options available for installing Epoch as listed below. See the handbook for usage instructions.

    Install Script

    Run the following command in your terminal to install the latest release (on Windows, use WSL):

    curl -Ls https://install.lf-lang.org | sh -s epoch

    You can also install the nightly pre-release:

    curl -Ls https://install.lf-lang.org | sh -s epoch nightly

    You can use the --prefix=<path> argument to change the default install location.

    AUR

    There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:

    yay -S epoch-bin

    or for the nightly pre-release:

    yay -S epoch-nightly-bin

    Manual Download

    Regular and nightly release builds of Epoch can be downloaded from the Epoch release page. Download the archive that matches your OS and architecture, and extract the contents.

    MacOS requires extra steps before being able to execute the app:

    xattr -cr Epoch.app

    To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:

    open -a Epoch.app

    From Source

    Please refer to the Epoch GitHub repository for build instructions.

    CLI Tools

    There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the handbook for usage instructions.

    Install Script

    Run the following command in your terminal to install the latest release (on Windows, use WSL):

    curl -Ls https://install.lf-lang.org | sh -s cli

    You can also install the nightly pre-release:

    curl -Ls https://install.lf-lang.org | sh -s cli nightly

    You can use the --prefix=<path> argument to change the default install location.

    AUR

    There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:

    yay -S lf-cli-bin

    or for the nightly pre-release:

    yay -S lf-cli-nightly-bin

    Manual Download

    Regular and nightly release builds of the command line tools can be downloaded from the Lingua Franca release page. Download the archive that matches your OS and architecture, and extract the contents.

    From Source

    Please refer to the Lingua Franca GitHub repository for build instructions.

    If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup here.

    \ No newline at end of file +
    Skip to main content

    Download and Install Lingua Franca

    The Lingua Franca toolchain requires Java 17 (download from Oracle). Each target language may have additional requirements (see the Target Language Details page and select your target language).

    You can use Lingua Franca:

    You can also spin up one of our pre-configured Cloud-based dev environments:
    Open in GitPod  Open in GitHub Codespaces
    Have a look at the Lingua Franca playground for more details.

    Visual Studio Code

    Our Visual Studio Code extension can be installed via the Marketplace or built from source, as detailed below. See the handbook for usage instructions.

    Marketplace

    The Lingua Franca extension is available on the Visual Studio Marketplace and the VSX Registry. To install the extension, open VS Code, launch Quick Open (Ctrl + P) and enter:

    ext install lf-lang.vscode-lingua-franca

    Alternatively, you can run the following command in your terminal:

    code --install-extension lf-lang.vscode-lingua-franca

    To use the nightly pre-release of the extension instead of the latest release, find the Lingua Franca extension in the Extensions tab and click on the "Switch to Pre-Release Version" button.

    From Source

    Please refer to the Lingua Franca VS Code GitHub repository for build instructions.

    Epoch IDE

    There are multiple options available for installing Epoch as listed below. See the handbook for usage instructions.

    Install Script

    Run the following command in your terminal to install the latest release (on Windows, use WSL):

    curl -Ls https://install.lf-lang.org | sh -s epoch

    You can also install the nightly pre-release:

    curl -Ls https://install.lf-lang.org | sh -s epoch nightly

    You can use the --prefix=<path> argument to change the default install location.

    The default prefix is /usr/local/bin on a Mac and ~/.local/bin on Linux and WSL. You may not have write access to this directory by default, in which case, if you still want to use the default prefix, you can replace sh with sudo sh in the above commands.

    AUR

    There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:

    yay -S epoch-bin

    or for the nightly pre-release:

    yay -S epoch-nightly-bin

    Manual Download

    Regular and nightly release builds of Epoch can be downloaded from the Epoch release page. Download the archive that matches your OS and architecture, and extract the contents.

    MacOS requires extra steps before being able to execute the app:

    xattr -cr Epoch.app

    To install, drag the Epoch.app file to your Applications folder. You can then invoke the app as follows:

    open -a Epoch.app

    From Source

    Please refer to the Epoch GitHub repository for build instructions.

    CLI Tools

    There are multiple options available for installing the Lingua Franca compiler and other command line tools, as listed below. See the handbook for usage instructions.

    Install Script

    Run the following command in your terminal to install the latest release (on Windows, use WSL):

    curl -Ls https://install.lf-lang.org | sh -s cli

    You can also install the nightly pre-release:

    curl -Ls https://install.lf-lang.org | sh -s cli nightly

    You can use the --prefix=<path> argument to change the default install location.

    AUR

    There are binary packages available in the Arch user repository, which you can install using your favorite AUR helper. For instance, with yay:

    yay -S lf-cli-bin

    or for the nightly pre-release:

    yay -S lf-cli-nightly-bin

    Manual Download

    Regular and nightly release builds of the command line tools can be downloaded from the Lingua Franca release page. Download the archive that matches your OS and architecture, and extract the contents.

    From Source

    Please refer to the Lingua Franca GitHub repository for build instructions.

    If you'd like to contribute to Lingua Franca, you can find details about the recommended developer setup here.

    \ No newline at end of file diff --git a/empty/index.html b/empty/index.html index a62a1787e..0e1c7e279 100644 --- a/empty/index.html +++ b/empty/index.html @@ -73,4 +73,4 @@ const customFontOverride = hasLocalStorage && localStorage.getItem("force-font") || "cascadia" document.documentElement.classList.add('font-' + customFontOverride) })() -
    Skip to main content

    This page is intentionally left blank

    \ No newline at end of file +
    Skip to main content

    This page is intentionally left blank

    \ No newline at end of file diff --git a/index.html b/index.html index 403d1a420..afdeb4863 100644 --- a/index.html +++ b/index.html @@ -73,4 +73,4 @@ const customFontOverride = hasLocalStorage && localStorage.getItem("force-font") || "cascadia" document.documentElement.classList.add('font-' + customFontOverride) })() -
    Skip to main content

    Lingua Franca is a polyglot coordination language for reactive, concurrent, and time-sensitive applications.

    Lingua Franca (LF) is a polyglot coordination language built to bring deterministic reactive concurrency and time to mainstream target programming languages (currently C, C++, Python, TypeScript, and Rust). LF is supported by a runtime system that is capable of concurrent and distributed execution of reactive programs that are deployable on the Cloud, the Edge, and even on bare-iron embedded platforms.

    A Lingua Franca program specifies the interactions between components called reactors. The logic of each reactor is written in plain target code. A code generator synthesizes one or more programs in the target language, which are then compiled using standard tool chains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.

    What is Lingua Franca?

    Reactor-oriented

    Reactors are reactive and composable concurrent software components with inputs, outputs, and local state.

    Concurrent

    Reactions to events are concurrent unless there is an explicit dependency between them.

    Deterministic

    Lingua Franca programs are deterministic by default and therefore easy to test.

    \ No newline at end of file +
    Skip to main content

    Lingua Franca is a polyglot coordination language for reactive, concurrent, and time-sensitive applications.

    Lingua Franca (LF) is a polyglot coordination language built to bring deterministic reactive concurrency and time to mainstream target programming languages (currently C, C++, Python, TypeScript, and Rust). LF is supported by a runtime system that is capable of concurrent and distributed execution of reactive programs that are deployable on the Cloud, the Edge, and even on bare-iron embedded platforms.

    A Lingua Franca program specifies the interactions between components called reactors. The logic of each reactor is written in plain target code. A code generator synthesizes one or more programs in the target language, which are then compiled using standard tool chains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.

    What is Lingua Franca?

    Reactor-oriented

    Reactors are reactive and composable concurrent software components with inputs, outputs, and local state.

    Concurrent

    Reactions to events are concurrent unless there is an explicit dependency between them.

    Deterministic

    Lingua Franca programs are deterministic by default and therefore easy to test.

    \ No newline at end of file diff --git a/page-data/app-data.json b/page-data/app-data.json index 7d6d49c38..c10b3cfe4 100644 --- a/page-data/app-data.json +++ b/page-data/app-data.json @@ -1 +1 @@ -{"webpackCompilationHash":"a9c6f420d9716c3fe73c"} +{"webpackCompilationHash":"e400b84b9178d6101427"} diff --git a/page-data/docs/handbook/a-first-reactor/page-data.json b/page-data/docs/handbook/a-first-reactor/page-data.json index 4ef1e9b51..50df7b57c 100644 --- a/page-data/docs/handbook/a-first-reactor/page-data.json +++ b/page-data/docs/handbook/a-first-reactor/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/a-first-reactor","result":{"data":{"markdownRemark":{"id":"49ea27af-cb5f-5558-96bd-ecfcb618bbd1","excerpt":"$page-showing-target$\nSee the requirements for using this target. Minimal Example A minimal but complete Lingua Franca file with one reactor is this: $start…","html":"

    $page-showing-target$\nSee the requirements for using this target.

    \n

    Minimal Example

    \n

    A minimal but complete Lingua Franca file with one reactor is this:

    \n

    $start(HelloWorld)$

    \n
    target C\nmain reactor {\n  reaction(startup) {=\n    printf("Hello World.\\n");\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  reaction(startup) {=\n    std::cout << "Hello World." << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  reaction(startup) {=\n    print("Hello World.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  reaction(startup) {=\n    console.log("Hello World.")\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  reaction(startup) {=\n    println!("Hello World.");\n  =}\n}\n
    \n

    $end(HelloWorld)$

    \n

    Every Lingua Franca program begins with a target declaration that specifies the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca code generator.

    \n

    Every LF program also has a $main$ [ or $federated$]{federated} reactor, which is the top level of a hierarchy of contained and interconnected reactors. The above simple example has no contained reactors.

    \n

    The $main$ reactor above has a single $reaction$, which is triggered by the $startup$ trigger. This trigger causes the reaction to execute at the start of the program. The body of the reaction, delimited by {= ... =}, is ordinary $target-language$ code which, as we will see, has access to a number of functions and variables specific to Lingua Franca.

    \n

    Examples

    \n

    Examples of Lingua Franca programs can be found in the Lingua Franca Playground.

    \n

    The regression tests have a rich set of examples that illustrate every feature of the language.

    \n

    Structure of an LF Project

    \n

    The Lingua Franca tools assume that LF programs are put into a file with a .lf extension that is stored somewhere within a directory called src. To compile and run the above example, choose a project root directory, create a src directory within that, and put the above code into a file called, say, src/HelloWorld.lf. You can compile the code on the command line, within Visual Studio Code, or within the Epoch IDE. On the command line this will look like this:

    \n
        > lfc src/HelloWorld.lf\n    ... output from the code generator and compiler ...
    \n
    \n

    After this completes, two additional directories will have been created within\nthe project root, bin and src-gen. The bin directory has an\nexecutable file called HelloWorld. Executing that file will result, not\nsurprisingly, in printing “Hello World”. The generated source files will be\nin a subdirectory called HelloWorld within src-gen.

    \n
    \n
    \n

    After this completes, an additional src-gen directory will have been created within the project root. The generated code will be in subdirectory called HelloWorld within src-gen. The output from the code generator will include instructions for executing the generated code:

    \n
    #####################################\nTo run the generated program, use:\n\n    node ...path-to-project.../src-gen/Minimal/dist/Minimal.js\n\n#####################################\n
    \n
    #####################################\nTo run the generated program, use:\n\n    python3 ...path-to-project.../src-gen/Minimal/Minimal.py\n\n#####################################\n
    \n
    \n

    Reactor Block

    \n

    A $reactor$ is a software component that reacts to input events, timer events, and internal events. It has private state variables that are not visible to any other reactor. Its reactions can consist of altering its own state, sending messages to other reactors, or affecting the environment through some kind of actuation or side effect (e.g., printing a message, as in the above HelloWorld example).

    \n

    The general structure of a reactor definition is as follows:

    \n
    \n
    [main or federated] reactor <class-name> [(<parameters>)] {\n    input <name>: <type>\n    output <name>: <type>\n    state <name>: <type> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>[: <type>]\n    physical action <name>[: <type>]\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n
    \n
    [main] reactor <class-name> [(<parameters>)] {\n    input <name>: <type>\n    output <name>: <type>\n    state <name>: <type> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>[: <type>]\n    physical action <name>[: <type>]\n    [const] method <name>(<parameters>):<type> {= ... body ...=}\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n
    \n
    [main or federated] reactor <class-name> [(<parameters>)] {\n    input <name>\n    output <name>\n    state <name> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>\n    physical action <name>\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n

    Contents within square brackets are optional, contents within <...> are user-defined, and each line may appear zero or more times, as explained in the next pages. Parameters, inputs, outputs, timers, actions, and contained reactors all have names, and the names are required to be distinct from one another.

    \n

    If the $reactor$ keyword is preceded by $main$[ or $federated$]{federated}, then this reactor will be instantiated and run by the generated code.

    \n

    Any number of reactors may be defined in one file, and a $main$[ or $federated$]{federated} reactor need not be given a name, but if it is given a name, then that name must match the file name.

    \n

    Reactors may extend other reactors, inheriting their properties, and a file may import reactors from other files. If an imported LF file contains a $main$[ or $federated$]{federated} reactor, that reactor is ignored (it will not be imported). This makes it easy to create a library of reusable reactors that each come with a test case or demonstration in the form of a main reactor.

    \n

    Comments

    \n

    Lingua Franca files can have C/C++/Java-style comments and/or Python-style comments. All of the following are valid comments:

    \n
        // Single-line C-style comment.\n    /*\n     * Multi-line C-style comment.\n     */\n    # Single-line Python-style comment.
    ","headings":[{"value":"Minimal Example","depth":2},{"value":"Examples","depth":2},{"value":"Structure of an LF Project","depth":2},{"value":"Reactor Block","depth":2},{"value":"Comments","depth":2}],"frontmatter":{"permalink":"/docs/handbook/a-first-reactor","title":"A First Reactor","oneline":"Writing your first Lingua Franca reactor.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","permalink":"/docs/handbook/inputs-and-outputs"}}}},"pageContext":{"id":"1-a-first-reactor","slug":"/docs/handbook/a-first-reactor","repoPath":"/packages/documentation/copy/en/topics/A First Reactor.md","nextID":"dcdc6b32-76b0-570a-a6f8-23bb570863c7","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/a-first-reactor","result":{"data":{"markdownRemark":{"id":"49ea27af-cb5f-5558-96bd-ecfcb618bbd1","excerpt":"$page-showing-target$\nSee the requirements for using this target. Minimal Example A minimal but complete Lingua Franca file with one reactor is this: $start…","html":"

    $page-showing-target$\nSee the requirements for using this target.

    \n

    Minimal Example

    \n

    A minimal but complete Lingua Franca file with one reactor is this:

    \n

    $start(HelloWorld)$

    \n
    target C\nmain reactor {\n  reaction(startup) {=\n    printf("Hello World.\\n");\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  reaction(startup) {=\n    std::cout << "Hello World." << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  reaction(startup) {=\n    print("Hello World.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  reaction(startup) {=\n    console.log("Hello World.")\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  reaction(startup) {=\n    println!("Hello World.");\n  =}\n}\n
    \n

    $end(HelloWorld)$

    \n

    Every Lingua Franca program begins with a target declaration that specifies the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca code generator.

    \n

    Every LF program also has a $main$ [ or $federated$]{federated} reactor, which is the top level of a hierarchy of contained and interconnected reactors. The above simple example has no contained reactors.

    \n

    The $main$ reactor above has a single $reaction$, which is triggered by the $startup$ trigger. This trigger causes the reaction to execute at the start of the program. The body of the reaction, delimited by {= ... =}, is ordinary $target-language$ code which, as we will see, has access to a number of functions and variables specific to Lingua Franca.

    \n

    Examples

    \n

    Examples of Lingua Franca programs can be found in the Lingua Franca Playground.

    \n

    The regression tests have a rich set of examples that illustrate every feature of the language.

    \n

    Structure of an LF Project

    \n

    The Lingua Franca tools assume that LF programs are put into a file with a .lf extension that is stored somewhere within a directory called src. To compile and run the above example, choose a project root directory, create a src directory within that, and put the above code into a file called, say, src/HelloWorld.lf. You can compile the code on the command line, within Visual Studio Code, or within the Epoch IDE. On the command line this will look like this:

    \n
        > lfc src/HelloWorld.lf\n    ... output from the code generator and compiler ...
    \n
    \n

    After this completes, two additional directories will have been created within\nthe project root, bin and src-gen. The bin directory has an\nexecutable file called HelloWorld. Executing that file will result, not\nsurprisingly, in printing “Hello World”. The generated source files will be\nin a subdirectory called HelloWorld within src-gen.

    \n
    \n
    \n

    After this completes, an additional src-gen directory will have been created within the project root. The generated code will be in subdirectory called HelloWorld within src-gen. The output from the code generator will include instructions for executing the generated code:

    \n
    #####################################\nTo run the generated program, use:\n\n    node ...path-to-project.../src-gen/Minimal/dist/Minimal.js\n\n#####################################\n
    \n
    #####################################\nTo run the generated program, use:\n\n    python3 ...path-to-project.../src-gen/Minimal/Minimal.py\n\n#####################################\n
    \n
    \n

    Reactor Block

    \n

    A $reactor$ is a software component that reacts to input events, timer events, and internal events. It has private state variables that are not visible to any other reactor. Its reactions can consist of altering its own state, sending messages to other reactors, or affecting the environment through some kind of actuation or side effect (e.g., printing a message, as in the above HelloWorld example).

    \n

    The general structure of a reactor definition is as follows:

    \n
    \n
    [main or federated] reactor <class-name> [(<parameters>)] {\n    input <name>: <type>\n    output <name>: <type>\n    state <name>: <type> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>[: <type>]\n    physical action <name>[: <type>]\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n
    \n
    [main] reactor <class-name> [(<parameters>)] {\n    input <name>: <type>\n    output <name>: <type>\n    state <name>: <type> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>[: <type>]\n    physical action <name>[: <type>]\n    [const] method <name>(<parameters>):<type> {= ... body ...=}\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n
    \n
    [main or federated] reactor <class-name> [(<parameters>)] {\n    input <name>\n    output <name>\n    state <name> [= <value>]\n    timer <name>([<offset>[, <period>]])\n    logical action <name>\n    physical action <name>\n    reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n    <instance-name> = new <class-name>([<parameter-assignments>])\n    <port-name> [, ...] -> <port-name> [, ...] [after <delay>]\n}\n
    \n
    \n

    Contents within square brackets are optional, contents within <...> are user-defined, and each line may appear zero or more times, as explained in the next pages. Parameters, inputs, outputs, timers, actions, and contained reactors all have names, and the names are required to be distinct from one another.

    \n

    If the $reactor$ keyword is preceded by $main$[ or $federated$]{federated}, then this reactor will be instantiated and run by the generated code.

    \n

    Any number of reactors may be defined in one file, and a $main$[ or $federated$]{federated} reactor need not be given a name, but if it is given a name, then that name must match the file name.

    \n

    Reactors may extend other reactors, inheriting their properties, and a file may import reactors from other files. If an imported LF file contains a $main$[ or $federated$]{federated} reactor, that reactor is ignored (it will not be imported). This makes it easy to create a library of reusable reactors that each come with a test case or demonstration in the form of a main reactor.

    \n

    Comments

    \n

    Lingua Franca files can have C/C++/Java-style comments and/or Python-style comments. All of the following are valid comments:

    \n
        // Single-line C-style comment.\n    /*\n     * Multi-line C-style comment.\n     */\n    # Single-line Python-style comment.
    ","headings":[{"value":"Minimal Example","depth":2},{"value":"Examples","depth":2},{"value":"Structure of an LF Project","depth":2},{"value":"Reactor Block","depth":2},{"value":"Comments","depth":2}],"frontmatter":{"permalink":"/docs/handbook/a-first-reactor","title":"A First Reactor","oneline":"Writing your first Lingua Franca reactor.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","permalink":"/docs/handbook/inputs-and-outputs"}}}},"pageContext":{"id":"1-a-first-reactor","slug":"/docs/handbook/a-first-reactor","repoPath":"/packages/documentation/copy/en/topics/A First Reactor.md","nextID":"dcdc6b32-76b0-570a-a6f8-23bb570863c7","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/actions/page-data.json b/page-data/docs/handbook/actions/page-data.json index 47beebcf2..e1c8bb5de 100644 --- a/page-data/docs/handbook/actions/page-data.json +++ b/page-data/docs/handbook/actions/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/actions","result":{"data":{"markdownRemark":{"id":"dd46179b-1189-5c1f-a60a-37e302c85efc","excerpt":"$page-showing-target$ Action Declaration An action declaration has one of the following forms: The min_delay, min_spacing, and policy are all optional. If only…","html":"

    $page-showing-target$

    \n

    Action Declaration

    \n

    An action declaration has one of the following forms:

    \n
      logical action <name>(<min_delay>, <min_spacing>, <policy>)\n  physical action <name>(<min_delay>, <min_spacing>, <policy>)\n
    \n

    The min_delay, min_spacing, and policy are all optional. If only one argument is given in parentheses, then it is interpreted as an min_delay, if two are given, then they are interpreted as min_delay and min_spacing. The min_delay and min_spacing are time values. The policy argument is a string that can be one of the following: \"defer\" (the default), \"drop\", or \"replace\". Note that the quotation marks are needed.

    \n
    \n

    If the action is to carry a payload, then a type must be given as well:

    \n
      logical action <name>(<min_delay>, <min_spacing>, <policy>):<type>\n  physical action <name>(<min_delay>, <min_spacing>, <policy>):<type>\n
    \n
    \n

    Logical Actions

    \n

    Timers are useful to trigger reactions once or periodically. Actions are used to trigger reactions more irregularly. An action, like an output or input port, can carry data, but unlike a port, an action is visible only within the reactor that defines it.

    \n

    There are two kinds of actions, logical and physical. A $logical$ $action$ is used by a reactor to schedule a trigger at a fixed logical time interval d into the future. The time interval d, which is called a delay, is relative to the logical time t at which the scheduling occurs. If a reaction executes at logical time t and schedules an action a with delay d, then any reaction that is triggered by a will be invoked at logical time t + d. For example, the following reaction schedules something (printing the current elapsed logical time) 200 msec after an input x arrives:

    \n

    $start(Schedule)$

    \n
    target C;\nreactor Schedule {\n  input x:int;\n  logical action a;\n  reaction(x) -> a {=\n    lf_schedule(a, MSEC(200));\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp;\nreactor Schedule {\n  input x:int;\n  logical action a;\n  reaction(x) -> a {=\n    a.schedule(200ms);\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time << "  nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python;\nreactor Schedule {\n  input x;\n  logical action a;\n  reaction(x) -> a {=\n    a.schedule(MSEC(200))\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nreactor Schedule {\n  input x:number\n  logical action a\n  reaction(x) -> a {=\n    actions.a.schedule(TimeValue.nsecs(200), null)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} after start.`)\n  =}\n}\n
    \n
    target Rust;\nreactor Schedule {\n  input x:u32;\n  logical action a;\n  reaction(x) -> a {=\n    ctx.schedule(a, after!(200 ms));\n  =}\n  reaction(a) {=\n    printf("\n      Action triggered at logical time {} nsec after start.",\n      ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Schedule)$

    \n\"Lingua\n

    Here, the delay is specified in the call to schedule within the target language code. Notice that in the diagram, a logical action is shown as a triangle with an L. Logical actions are always scheduled within a reaction of the reactor that declares the action.

    \n

    The time argument is required to be non-negative. If it is zero, then the action will be scheduled one microstep later. See Superdense Time.

    \n
    \n

    The arguments to the lf_schedule() function are the action named a and a time. The action a has to be declared as an effect of the reaction in order to reference it in the call to lf_schedule(). If you fail to declare it as an effect (after the -> in the reaction signature), then you will get an error message.

    \n

    The time argument to the lf_schedule() function has data type interval_t, which, with the exception of some embedded platforms, is a C int64_t. A collection of convenience macros is provided like the MSEC macro above to specify time values in a more readable way. The provided macros are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these, e.g. WEEKS(2).

    \n

    An action may have a data type, in which case, a variant of the lf_schedule() function can be used to specify a payload, a data value that is carried from where the lf_schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n
    \n

    An action may have a data type, in which case, a variant of the schedule() function can be used to specify a payload, a data value that is carried from where the schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n
    \n

    The arguments to the a.schedule() method is a time. The action a has to be\ndeclared as an effect of the reaction in order to reference it in the body of\nthe reaction. If you fail to declare it as an effect (after the -> in the\nreaction signature), then you will get a runtime error message.

    \n

    The time argument to the a.schedule() method expects an integer. A collection\nof convenience functions is provided like the MSEC function above to specify\ntime values in a more readable way. The provided functions are NSEC, USEC\n(for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may\nalso use the plural of any of these, e.g. WEEKS(2).

    \n

    An action may carry data, in which case, the payload data value is just given as a second argument to the .schedule() method. See the Target Language Details.

    \n
    \n
    \n

    The schedule() method of an action takes two arguments, a TimeValue and an (optional) payload. If a payload is given and a type is given for the action, then the type of the payload must match the type of the action. See the Target Language Details for details.

    \n
    \n
    \n

    FIXME

    \n

    An action may have a data type, in which case, a variant of the schedule() function can be used to specify a payload, a data value that is carried from where the schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n

    Physical Actions

    \n

    A $physical$ $action$ is used to schedule reactions at logical times determined by the local physical clock. If a physical action with delay d is scheduled at physical time T, then the logical time assigned to the event is T + d. For example, the following reactor schedules the physical action p to trigger at a logical time equal to the physical time at which the input x arrives:

    \n

    $start(Physical)$

    \n
    target C;\nreactor Physical {\n  input x:int;\n  physical action a;\n  reaction(x) -> a {=\n    lf_schedule(a, 0);\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp;\nreactor Physical {\n  input x:int;\n  physical action a;\n  reaction(x) -> a {=\n    a.schedule(0ms);\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time << " nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python;\nreactor Physical {\n  input x;\n  physical action a;\n  reaction(x) -> a {=\n    a.schedule(0)\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nreactor Physical {\n  input x:int\n  physical action a\n  reaction(x) -> a {=\n    actions.a.schedule(TimeValue.zero(), null)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} nsec after start.`)\n  =}\n}\n
    \n
    target Rust;\nreactor Physical {\n  input x:u32;\n  physical action a;\n  reaction(x) -> a {=\n    let phys_action = a.clone();\n    ctx.spawn_physical_thread(move |link| {\n      link.schedule(&phys_action, Asap).unwrap();\n    });\n  =}\n  reaction(a) {=\n    println!(\n      "Action triggered at logical time {} nsec after start.",\n      ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Physical)$

    \n\"Lingua\n

    If you drive this with a timer, using for example the following structure:

    \n\"Lingua\n

    then running the program will yield an output something like this:

    \n
    Action triggered at logical time 201491000 nsec after start.\nAction triggered at logical time 403685000 nsec after start.\nAction triggered at logical time 603669000 nsec after start.\n...
    \n

    Here, logical time is lagging physical time by a few milliseconds. Note that, unless the fast option is given, logical time t chases physical time T, so t < T. Hence, the event being scheduled in the reaction to input x is assured of being in the future in logical time.

    \n

    Whereas logical actions are required to be scheduled within a reaction of the reactor that declares the action, physical actions can be scheduled by code that is outside the Lingua Franca system. For example, some other thread or a callback function may call schedule(), passing it a physical action. For example:

    \n

    $start(Asynchronous)$

    \n
    target C {\n  keepalive: true  // Do not exit when event queue is empty.\n}\npreamble {=\n  #include "platform.h" // Defines lf_sleep() and thread functions.\n=}\nmain reactor {\n  preamble {=\n    // Schedule an event roughly every 200 msec.\n    void* external(void* a) {\n      while (true) {\n        lf_sleep(MSEC(200));\n        lf_schedule(a, 0);\n      }\n    }\n  =}\n  state thread_id: lf_thread_t = 0\n  physical action a(100 msec): int\n  reaction(startup) -> a {=\n    // Start a thread to schedule physical actions.\n    lf_thread_create(&self->thread_id, &external, a);\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  private preamble {=\n    #include <thread>\n  =}\n  state thread: std::thread\n  physical action a: int\n  reaction(startup) -> a {=\n    // Start a thread to schedule physical actions.\n    thread = std::thread([&]{\n      while (true) {\n        std::this_thread::sleep_for(200ms);\n        // the value that we give it really doesn't matter\n        // but we the action should is scheduled for 100ms into the future\n        a.schedule(0, 100ms);\n      }\n    });\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_physical_time();\n    std::cout << "Action triggered at logical time" << elapsed_time <<"nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  preamble {=\n    import time\n    import threading\n    # Schedule an event roughly every 200 msec.\n    def external(self, a):\n      while (True):\n        self.time.sleep(0.2)\n        a.schedule(0)\n  =}\n  state thread\n  physical action a(100 msec)\n  reaction(startup) -> a {=\n    # Start a thread to schedule physical actions.\n    self.thread = self.threading.Thread(target=self.external, args=(a,))\n    self.thread.start()\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  physical action a(100 msec): number\n  reaction(startup) -> a {=\n    // Have asynchronous callback schedule physical action.\n    setTimeout(() => {\n      actions.a.schedule(TimeValue.zero(), 0)\n    }, 200)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} nsec after start.`)\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  state start_time: Instant = {= Instant::now() =}\n  physical action a(100 msec): u32\n  reaction(startup) -> a {=\n    let phys_action = a.clone(); // clone to move it into other thread\n    // Start a thread to schedule physical actions.\n    ctx.spawn_physical_thread(move |link| {\n      loop {\n        std::thread::sleep(Duration::from_millis(200));\n        link.schedule_physical(&phys_action, Asap).unwrap();\n      }\n    });\n  =}\n  reaction(a) {=\n    let elapsed_time = self.start_time.elapsed();\n    println!("Action triggered at logical time {} nsecs after start.", elapsed_time.as_nanos());\n  =}\n}\n
    \n

    $end(Asynchronous)$

    \n\"Lingua\n

    Physical actions are the mechanism for obtaining input from the outside world. Because they are assigned a logical time derived from the physical clock, their logical time can be interpreted as a measure of the time at which some external event occurred.

    \n
    \n

    In the above example, at $startup$, the main reactor creates an external thread that schedules a physical action roughly every 200 msec.

    \n

    First, the file-level $preamble$ has #include \"platform.h\", which includes the declarations for functions it uses, lf_sleep and lf_thread_create (see Libraries Available to Programmers).

    \n

    Second, the thread uses a function lf_sleep(), which abstracts platform-specific mechanisms for stalling the thread for a specified amount of time, and lf_thread_create(), which abstracts platform-specific mechanisms for creating threads.

    \n

    The external function executed by the thread is defined in a reactor-level $preamble$ section. See Preambles.

    \n
    \n

    Triggering Time for Actions

    \n

    An action will trigger at a logical time that depends on the arguments given to the schedule function, the <min_delay>, <min_spacing>, and <policy> arguments in the action declaration, and whether the action is physical or logical.

    \n

    For a $logical$ action a, the tag assigned to the event resulting from a call to schedule() is computed as follows. First, let t be the current logical time. For a logical action, t is just the logical time at which the reaction calling schedule() is called. The preliminary time of the action is then just t + <min_delay> + <offset>. This preliminary time may be further modified, as explained below.

    \n

    For a physical action, the preliminary time is similar, except that t is replaced by the current physical time T when schedule() is called.

    \n

    If a <min_spacing> has been declared, then it gives a minimum logical time\ninterval between the tags of two subsequently scheduled events. If the\npreliminary time is closer than <min_spacing> to the time of the previously\nscheduled event (if there is one), then <policy> (if supported by the target)\ndetermines how the minimum spacing constraint is enforced.

    \n
    \n

    The <policy> is one of the following:

    \n
      \n
    • \"defer\": (the default) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is t_prev, then the tag of the new event simply becomes t_prev + <min_spacing>.
    • \n
    • \"drop\": The new event is dropped and schedule() returns without having modified the event queue.
    • \n
    • \"replace\": The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default \"defer\" policy is applied.
    • \n
    \n

    Note that while the \"defer\" policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.

    \n
    \n
    \n
    \n

    The <policy> argument is currently not supported.

    \n
    \n
    \n

    Testing an Action for Presence

    \n

    When a reaction is triggered by more than one action or by an action and an input, it may be necessary to test within the reaction whether the action is present.\nJust like for inputs, this can be done in the C target with a->is_present, where a is the name of the action.\nJust like for inputs, this can be done in the Python target with a.is_present, where a is the name of the action.\nJust like for inputs, this can be done in the C++ target with a.is_present(), where a is the name of the action.\nJust like for inputs, this can be done in the TypeScript target with a != undefined, where a is the name of the action.\nJust like for inputs, this can be done in the Rust target with ctx.is_present(a), where a is the name of the action.

    ","headings":[{"value":"Action Declaration","depth":2},{"value":"Logical Actions","depth":2},{"value":"Physical Actions","depth":2},{"value":"Triggering Time for Actions","depth":2},{"value":"Testing an Action for Presence","depth":2}],"frontmatter":{"permalink":"/docs/handbook/actions","title":"Actions","oneline":"Actions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","permalink":"/docs/handbook/extending-reactors"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","permalink":"/docs/handbook/superdense-time"}}}},"pageContext":{"id":"1-actions","slug":"/docs/handbook/actions","repoPath":"/packages/documentation/copy/en/topics/Actions.md","previousID":"d4ade317-2d91-582b-8cca-37743461f687","nextID":"1986cb1b-8feb-57bc-8088-50548df2f061","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/actions","result":{"data":{"markdownRemark":{"id":"dd46179b-1189-5c1f-a60a-37e302c85efc","excerpt":"$page-showing-target$ Action Declaration An action declaration has one of the following forms: The min_delay, min_spacing, and policy are all optional. If only…","html":"

    $page-showing-target$

    \n

    Action Declaration

    \n

    An action declaration has one of the following forms:

    \n
      logical action <name>(<min_delay>, <min_spacing>, <policy>)\n  physical action <name>(<min_delay>, <min_spacing>, <policy>)\n
    \n

    The min_delay, min_spacing, and policy are all optional. If only one argument is given in parentheses, then it is interpreted as an min_delay, if two are given, then they are interpreted as min_delay and min_spacing. The min_delay and min_spacing are time values. The policy argument is a string that can be one of the following: \"defer\" (the default), \"drop\", or \"replace\". Note that the quotation marks are needed.

    \n
    \n

    If the action is to carry a payload, then a type must be given as well:

    \n
      logical action <name>(<min_delay>, <min_spacing>, <policy>):<type>\n  physical action <name>(<min_delay>, <min_spacing>, <policy>):<type>\n
    \n
    \n

    Logical Actions

    \n

    Timers are useful to trigger reactions once or periodically. Actions are used to trigger reactions more irregularly. An action, like an output or input port, can carry data, but unlike a port, an action is visible only within the reactor that defines it.

    \n

    There are two kinds of actions, logical and physical. A $logical$ $action$ is used by a reactor to schedule a trigger at a fixed logical time interval d into the future. The time interval d, which is called a delay, is relative to the logical time t at which the scheduling occurs. If a reaction executes at logical time t and schedules an action a with delay d, then any reaction that is triggered by a will be invoked at logical time t + d. For example, the following reaction schedules something (printing the current elapsed logical time) 200 msec after an input x arrives:

    \n

    $start(Schedule)$

    \n
    target C;\nreactor Schedule {\n  input x:int;\n  logical action a;\n  reaction(x) -> a {=\n    lf_schedule(a, MSEC(200));\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp;\nreactor Schedule {\n  input x:int;\n  logical action a;\n  reaction(x) -> a {=\n    a.schedule(200ms);\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time << "  nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python;\nreactor Schedule {\n  input x;\n  logical action a;\n  reaction(x) -> a {=\n    a.schedule(MSEC(200))\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nreactor Schedule {\n  input x:number\n  logical action a\n  reaction(x) -> a {=\n    actions.a.schedule(TimeValue.nsecs(200), null)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} after start.`)\n  =}\n}\n
    \n
    target Rust;\nreactor Schedule {\n  input x:u32;\n  logical action a;\n  reaction(x) -> a {=\n    ctx.schedule(a, after!(200 ms));\n  =}\n  reaction(a) {=\n    printf("\n      Action triggered at logical time {} nsec after start.",\n      ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Schedule)$

    \n\"Lingua\n

    Here, the delay is specified in the call to schedule within the target language code. Notice that in the diagram, a logical action is shown as a triangle with an L. Logical actions are always scheduled within a reaction of the reactor that declares the action.

    \n

    The time argument is required to be non-negative. If it is zero, then the action will be scheduled one microstep later. See Superdense Time.

    \n
    \n

    The arguments to the lf_schedule() function are the action named a and a time. The action a has to be declared as an effect of the reaction in order to reference it in the call to lf_schedule(). If you fail to declare it as an effect (after the -> in the reaction signature), then you will get an error message.

    \n

    The time argument to the lf_schedule() function has data type interval_t, which, with the exception of some embedded platforms, is a C int64_t. A collection of convenience macros is provided like the MSEC macro above to specify time values in a more readable way. The provided macros are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these, e.g. WEEKS(2).

    \n

    An action may have a data type, in which case, a variant of the lf_schedule() function can be used to specify a payload, a data value that is carried from where the lf_schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n
    \n

    An action may have a data type, in which case, a variant of the schedule() function can be used to specify a payload, a data value that is carried from where the schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n
    \n

    The arguments to the a.schedule() method is a time. The action a has to be\ndeclared as an effect of the reaction in order to reference it in the body of\nthe reaction. If you fail to declare it as an effect (after the -> in the\nreaction signature), then you will get a runtime error message.

    \n

    The time argument to the a.schedule() method expects an integer. A collection\nof convenience functions is provided like the MSEC function above to specify\ntime values in a more readable way. The provided functions are NSEC, USEC\n(for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may\nalso use the plural of any of these, e.g. WEEKS(2).

    \n

    An action may carry data, in which case, the payload data value is just given as a second argument to the .schedule() method. See the Target Language Details.

    \n
    \n
    \n

    The schedule() method of an action takes two arguments, a TimeValue and an (optional) payload. If a payload is given and a type is given for the action, then the type of the payload must match the type of the action. See the Target Language Details for details.

    \n
    \n
    \n

    FIXME

    \n

    An action may have a data type, in which case, a variant of the schedule() function can be used to specify a payload, a data value that is carried from where the schedule() function is called to the reaction that is triggered by the action. See the Target Language Details.

    \n
    \n

    Physical Actions

    \n

    A $physical$ $action$ is used to schedule reactions at logical times determined by the local physical clock. If a physical action with delay d is scheduled at physical time T, then the logical time assigned to the event is T + d. For example, the following reactor schedules the physical action p to trigger at a logical time equal to the physical time at which the input x arrives:

    \n

    $start(Physical)$

    \n
    target C;\nreactor Physical {\n  input x:int;\n  physical action a;\n  reaction(x) -> a {=\n    lf_schedule(a, 0);\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp;\nreactor Physical {\n  input x:int;\n  physical action a;\n  reaction(x) -> a {=\n    a.schedule(0ms);\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time << " nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python;\nreactor Physical {\n  input x;\n  physical action a;\n  reaction(x) -> a {=\n    a.schedule(0)\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nreactor Physical {\n  input x:int\n  physical action a\n  reaction(x) -> a {=\n    actions.a.schedule(TimeValue.zero(), null)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} nsec after start.`)\n  =}\n}\n
    \n
    target Rust;\nreactor Physical {\n  input x:u32;\n  physical action a;\n  reaction(x) -> a {=\n    let phys_action = a.clone();\n    ctx.spawn_physical_thread(move |link| {\n      link.schedule(&phys_action, Asap).unwrap();\n    });\n  =}\n  reaction(a) {=\n    println!(\n      "Action triggered at logical time {} nsec after start.",\n      ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Physical)$

    \n\"Lingua\n

    If you drive this with a timer, using for example the following structure:

    \n\"Lingua\n

    then running the program will yield an output something like this:

    \n
    Action triggered at logical time 201491000 nsec after start.\nAction triggered at logical time 403685000 nsec after start.\nAction triggered at logical time 603669000 nsec after start.\n...
    \n

    Here, logical time is lagging physical time by a few milliseconds. Note that, unless the fast option is given, logical time t chases physical time T, so t < T. Hence, the event being scheduled in the reaction to input x is assured of being in the future in logical time.

    \n

    Whereas logical actions are required to be scheduled within a reaction of the reactor that declares the action, physical actions can be scheduled by code that is outside the Lingua Franca system. For example, some other thread or a callback function may call schedule(), passing it a physical action. For example:

    \n

    $start(Asynchronous)$

    \n
    target C {\n  keepalive: true  // Do not exit when event queue is empty.\n}\npreamble {=\n  #include "platform.h" // Defines lf_sleep() and thread functions.\n=}\nmain reactor {\n  preamble {=\n    // Schedule an event roughly every 200 msec.\n    void* external(void* a) {\n      while (true) {\n        lf_sleep(MSEC(200));\n        lf_schedule(a, 0);\n      }\n    }\n  =}\n  state thread_id: lf_thread_t = 0\n  physical action a(100 msec): int\n  reaction(startup) -> a {=\n    // Start a thread to schedule physical actions.\n    lf_thread_create(&self->thread_id, &external, a);\n  =}\n  reaction(a) {=\n    interval_t elapsed_time = lf_time_logical_elapsed();\n    printf("Action triggered at logical time %lld nsec after start.\\n", elapsed_time);\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  private preamble {=\n    #include <thread>\n  =}\n  state thread: std::thread\n  physical action a: int\n  reaction(startup) -> a {=\n    // Start a thread to schedule physical actions.\n    thread = std::thread([&]{\n      while (true) {\n        std::this_thread::sleep_for(200ms);\n        // the value that we give it really doesn't matter\n        // but we the action should is scheduled for 100ms into the future\n        a.schedule(0, 100ms);\n      }\n    });\n  =}\n  reaction(a) {=\n    auto elapsed_time = get_physical_time();\n    std::cout << "Action triggered at logical time" << elapsed_time <<"nsec after start." << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  preamble {=\n    import time\n    import threading\n    # Schedule an event roughly every 200 msec.\n    def external(self, a):\n      while (True):\n        self.time.sleep(0.2)\n        a.schedule(0)\n  =}\n  state thread\n  physical action a(100 msec)\n  reaction(startup) -> a {=\n    # Start a thread to schedule physical actions.\n    self.thread = self.threading.Thread(target=self.external, args=(a,))\n    self.thread.start()\n  =}\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"Action triggered at logical time {elapsed_time} nsec after start.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  physical action a(100 msec): number\n  reaction(startup) -> a {=\n    // Have asynchronous callback schedule physical action.\n    setTimeout(() => {\n      actions.a.schedule(TimeValue.zero(), 0)\n    }, 200)\n  =}\n  reaction(a) {=\n    console.log(`Action triggered at logical time ${util.getElapsedLogicalTime()} nsec after start.`)\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  state start_time: Instant = {= Instant::now() =}\n  physical action a(100 msec): u32\n  reaction(startup) -> a {=\n    let phys_action = a.clone(); // clone to move it into other thread\n    // Start a thread to schedule physical actions.\n    ctx.spawn_physical_thread(move |link| {\n      loop {\n        std::thread::sleep(Duration::from_millis(200));\n        link.schedule_physical(&phys_action, Asap).unwrap();\n      }\n    });\n  =}\n  reaction(a) {=\n    let elapsed_time = self.start_time.elapsed();\n    println!("Action triggered at logical time {} nsecs after start.", elapsed_time.as_nanos());\n  =}\n}\n
    \n

    $end(Asynchronous)$

    \n\"Lingua\n

    Physical actions are the mechanism for obtaining input from the outside world. Because they are assigned a logical time derived from the physical clock, their logical time can be interpreted as a measure of the time at which some external event occurred.

    \n
    \n

    In the above example, at $startup$, the main reactor creates an external thread that schedules a physical action roughly every 200 msec.

    \n

    First, the file-level $preamble$ has #include \"platform.h\", which includes the declarations for functions it uses, lf_sleep and lf_thread_create (see Libraries Available to Programmers).

    \n

    Second, the thread uses a function lf_sleep(), which abstracts platform-specific mechanisms for stalling the thread for a specified amount of time, and lf_thread_create(), which abstracts platform-specific mechanisms for creating threads.

    \n

    The external function executed by the thread is defined in a reactor-level $preamble$ section. See Preambles.

    \n
    \n

    Triggering Time for Actions

    \n

    An action will trigger at a logical time that depends on the arguments given to the schedule function, the <min_delay>, <min_spacing>, and <policy> arguments in the action declaration, and whether the action is physical or logical.

    \n

    For a $logical$ action a, the tag assigned to the event resulting from a call to schedule() is computed as follows. First, let t be the current logical time. For a logical action, t is just the logical time at which the reaction calling schedule() is called. The preliminary time of the action is then just t + <min_delay> + <offset>. This preliminary time may be further modified, as explained below.

    \n

    For a physical action, the preliminary time is similar, except that t is replaced by the current physical time T when schedule() is called.

    \n

    If a <min_spacing> has been declared, then it gives a minimum logical time\ninterval between the tags of two subsequently scheduled events. If the\npreliminary time is closer than <min_spacing> to the time of the previously\nscheduled event (if there is one), then <policy> (if supported by the target)\ndetermines how the minimum spacing constraint is enforced.

    \n
    \n

    The <policy> is one of the following:

    \n
      \n
    • \"defer\": (the default) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is t_prev, then the tag of the new event simply becomes t_prev + <min_spacing>.
    • \n
    • \"drop\": The new event is dropped and schedule() returns without having modified the event queue.
    • \n
    • \"replace\": The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default \"defer\" policy is applied.
    • \n
    \n

    Note that while the \"defer\" policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.

    \n
    \n
    \n
    \n

    The <policy> argument is currently not supported.

    \n
    \n
    \n

    Testing an Action for Presence

    \n

    When a reaction is triggered by more than one action or by an action and an input, it may be necessary to test within the reaction whether the action is present.\nJust like for inputs, this can be done in the C target with a->is_present, where a is the name of the action.\nJust like for inputs, this can be done in the Python target with a.is_present, where a is the name of the action.\nJust like for inputs, this can be done in the C++ target with a.is_present(), where a is the name of the action.\nJust like for inputs, this can be done in the TypeScript target with a != undefined, where a is the name of the action.\nJust like for inputs, this can be done in the Rust target with ctx.is_present(a), where a is the name of the action.

    ","headings":[{"value":"Action Declaration","depth":2},{"value":"Logical Actions","depth":2},{"value":"Physical Actions","depth":2},{"value":"Triggering Time for Actions","depth":2},{"value":"Testing an Action for Presence","depth":2}],"frontmatter":{"permalink":"/docs/handbook/actions","title":"Actions","oneline":"Actions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","permalink":"/docs/handbook/extending-reactors"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","permalink":"/docs/handbook/superdense-time"}}}},"pageContext":{"id":"1-actions","slug":"/docs/handbook/actions","repoPath":"/packages/documentation/copy/en/topics/Actions.md","previousID":"d4ade317-2d91-582b-8cca-37743461f687","nextID":"1986cb1b-8feb-57bc-8088-50548df2f061","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/arduino/page-data.json b/page-data/docs/handbook/arduino/page-data.json index 440c6c20a..ecca0aa73 100644 --- a/page-data/docs/handbook/arduino/page-data.json +++ b/page-data/docs/handbook/arduino/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/arduino","result":{"data":{"markdownRemark":{"id":"77295cb2-a818-5f3d-9d3c-9ad07392edf2","excerpt":"Overview To run Lingua Franca on an Arduino-compatible microcontroller, you can use the\nC target with the platform target property set to arduino. The Lingua…","html":"

    Overview

    \n

    To run Lingua Franca on an Arduino-compatible microcontroller, you can use the\nC target with the platform target property set to arduino. The Lingua\nFranca compiler will then not output ordinary C code in a CMake project, but\ngenerate a .ino sketch that can be compiled and deployed using arduino-cli.\nTo flash the compiled sketch onto a board, you need specify a Fully Qualified\nBoard Name (FQBN) as well as the port to which your board is connected. On this\npage, we explain exactly how to do this.

    \n

    Prerequisites

    \n
      \n
    • You need a development system that runs on macOS or Linux (there is currently no Windows support).
    • \n
    • Arduino CLI must be installed on your development system. Confirm that your installation works by running:
    • \n
    \n
    arduino-cli version
    \n

    Writing a simple Arduino Program

    \n

    The most basic Arduino program (Blink) can be defined in LF like so:

    \n
    target C {\n  platform: "Arduino"\n}\n\nmain reactor Blink {\n  timer t1(0, 1 sec)\n  timer t2(500 msec, 1 sec)\n  reaction(startup) {=\n    pinMode(LED_BUILTIN, OUTPUT);\n  =}\n\n  reaction (t1) {=\n    digitalWrite(LED_BUILTIN, HIGH);\n  =}\n\n  reaction (t2) {=\n    digitalWrite(LED_BUILTIN, LOW);\n  =}\n}\n
    \n

    The key difference between writing ordinary Arduino code and writing LF code is\nthat there is no setup() and loop() function. In LF, execution is\nevent-driven, with reactive code being triggered by events, such as the\nexpiration of a timer. Those pieces of reactive code, specified as reactions,\ncan invoke Arduino library functions, just like one would do in the definition\nof an Arduino loop(). For any setup that might be needed at the beginning of\nexecution, a reaction triggered by the built-in startup trigger can be used.

    \n

    Platform Options

    \n

    The platform property can also be used so specify more details. Along with name: \"arduino\",\nyou can specify which board, port, and baud-rate you are using. If you want the program\nto be flashed onto the board automatically after compilation, specify flash: true.\nHere a target declaration that specifies all of these options:

    \n
    target C {\n  platform: {\n    name: "arduino",\n    board: "arduino:avr:uno",\n    port: "/dev/ttyd2",\n    baud-rate: 115200,\n    flash: true\n  }\n}\n
    \n

    The board is necessary for building and the port is necessary for flashing.

    \n

    Baud rate of the serial port

    \n

    All Arduino boards have at least one serial port (also known as a UART or\nUSART), and some have several. By default, Lingua Franca will assume a default\nbaud rate of 9600. This parameter is tunable by adjusting the baud-rate\nparameter in platform options.

    \n

    Building

    \n

    In order to have arduino-cli compile the generated sketch file, a board must\nbe specified. If no board is set, lfc will run in no-compile mode, meaning\nit will not invoke the target compiler. Whether specified as a target property\nor given directly to arduino-cli, you need an FQBN. You can find the FQBN of\nthe board that you have plugged in using the following command:

    \n

    arduino-cli board list

    \n

    To see the list of all FQBN’s installed on your device (all FQBNs supported by the libraries you have installed), run the following command:

    \n

    arduino-cli board listall

    \n

    You likely need to install support for your board first, which you can do using the following command:

    \n

    arduino-cli core install arduino:[BOARD_FAMILY]

    \n

    The common board families include avr, megaAVR, sam, samd, and mbed.

    \n

    If you specify your FQBN under board in the platform target property, lfc will automatically invoke arduino-cli on the generated sketch. To invoke arduino-cli manually

    \n
      \n
    • \n

      for unthreaded programs (most arduino flavors), run:\narduino-cli compile -b [FQBN] --build-property compiler.c.extra_flags=\"-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\"

      \n
    • \n
    • \n

      for threaded programs (arduino:mbed boards), run:\narduino-cli compile -b [FQBN] --build-property compiler.c.extra_flags=\"-DLF_THREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"-DLF_THREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\"

      \n
    • \n
    \n

    Flashing

    \n

    Arduino’s can be flashed via USB. To find the port oto which your device is connected, run the following command:

    \n

    arduino-cli board list

    \n

    You can either use arduino-cli to flash the code onto your device after the sketch file has been built, or you can set flash:true and provide a port in your Lingua Franca file. To flash manually using arduino-cli, enter the subdirectory of src-gen in which the generated sketch file is located and run:

    \n

    arduino-cli upload -p PORT -b FQBN .

    ","headings":[{"value":"Overview","depth":1},{"value":"Prerequisites","depth":2},{"value":"Writing a simple Arduino Program","depth":1},{"value":"Platform Options","depth":2},{"value":"Baud rate of the serial port","depth":3},{"value":"Building","depth":1},{"value":"Flashing","depth":1}],"frontmatter":{"permalink":"/docs/handbook/arduino","title":"Arduino","oneline":"Developing LF Programs on Arduino.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Zephyr","oneline":"Developing LF Programs for Zephyr RTOS.","permalink":"/docs/handbook/zephyr"}}}},"pageContext":{"id":"4-arduino","slug":"/docs/handbook/arduino","repoPath":"/packages/documentation/copy/en/embedded/Arduino.md","nextID":"3f41a520-2093-55ff-a5e7-36ec8f69920a","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/arduino","result":{"data":{"markdownRemark":{"id":"77295cb2-a818-5f3d-9d3c-9ad07392edf2","excerpt":"Overview To run Lingua Franca on an Arduino-compatible microcontroller, you can use the\nC target with the platform target property set to arduino. The Lingua…","html":"

    Overview

    \n

    To run Lingua Franca on an Arduino-compatible microcontroller, you can use the\nC target with the platform target property set to arduino. The Lingua\nFranca compiler will then not output ordinary C code in a CMake project, but\ngenerate a .ino sketch that can be compiled and deployed using arduino-cli.\nTo flash the compiled sketch onto a board, you need specify a Fully Qualified\nBoard Name (FQBN) as well as the port to which your board is connected. On this\npage, we explain exactly how to do this.

    \n

    Prerequisites

    \n
      \n
    • You need a development system that runs on macOS or Linux (there is currently no Windows support).
    • \n
    • Arduino CLI must be installed on your development system. Confirm that your installation works by running:
    • \n
    \n
    arduino-cli version
    \n

    Writing a simple Arduino Program

    \n

    The most basic Arduino program (Blink) can be defined in LF like so:

    \n
    target C {\n  platform: "Arduino"\n}\n\nmain reactor Blink {\n  timer t1(0, 1 sec)\n  timer t2(500 msec, 1 sec)\n  reaction(startup) {=\n    pinMode(LED_BUILTIN, OUTPUT);\n  =}\n\n  reaction (t1) {=\n    digitalWrite(LED_BUILTIN, HIGH);\n  =}\n\n  reaction (t2) {=\n    digitalWrite(LED_BUILTIN, LOW);\n  =}\n}\n
    \n

    The key difference between writing ordinary Arduino code and writing LF code is\nthat there is no setup() and loop() function. In LF, execution is\nevent-driven, with reactive code being triggered by events, such as the\nexpiration of a timer. Those pieces of reactive code, specified as reactions,\ncan invoke Arduino library functions, just like one would do in the definition\nof an Arduino loop(). For any setup that might be needed at the beginning of\nexecution, a reaction triggered by the built-in startup trigger can be used.

    \n

    Platform Options

    \n

    The platform property can also be used so specify more details. Along with name: \"arduino\",\nyou can specify which board, port, and baud-rate you are using. If you want the program\nto be flashed onto the board automatically after compilation, specify flash: true.\nHere a target declaration that specifies all of these options:

    \n
    target C {\n  platform: {\n    name: "arduino",\n    board: "arduino:avr:uno",\n    port: "/dev/ttyd2",\n    baud-rate: 115200,\n    flash: true\n  }\n}\n
    \n

    The board is necessary for building and the port is necessary for flashing.

    \n

    Baud rate of the serial port

    \n

    All Arduino boards have at least one serial port (also known as a UART or\nUSART), and some have several. By default, Lingua Franca will assume a default\nbaud rate of 9600. This parameter is tunable by adjusting the baud-rate\nparameter in platform options.

    \n

    Building

    \n

    In order to have arduino-cli compile the generated sketch file, a board must\nbe specified. If no board is set, lfc will run in no-compile mode, meaning\nit will not invoke the target compiler. Whether specified as a target property\nor given directly to arduino-cli, you need an FQBN. You can find the FQBN of\nthe board that you have plugged in using the following command:

    \n

    arduino-cli board list

    \n

    To see the list of all FQBN’s installed on your device (all FQBNs supported by the libraries you have installed), run the following command:

    \n

    arduino-cli board listall

    \n

    You likely need to install support for your board first, which you can do using the following command:

    \n

    arduino-cli core install arduino:[BOARD_FAMILY]

    \n

    The common board families include avr, megaAVR, sam, samd, and mbed.

    \n

    If you specify your FQBN under board in the platform target property, lfc will automatically invoke arduino-cli on the generated sketch. To invoke arduino-cli manually

    \n
      \n
    • \n

      for unthreaded programs (most arduino flavors), run:\narduino-cli compile -b [FQBN] --build-property compiler.c.extra_flags=\"-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\"

      \n
    • \n
    • \n

      for threaded programs (arduino:mbed boards), run:\narduino-cli compile -b [FQBN] --build-property compiler.c.extra_flags=\"-DLF_THREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"-DLF_THREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10\"

      \n
    • \n
    \n

    Flashing

    \n

    Arduino’s can be flashed via USB. To find the port oto which your device is connected, run the following command:

    \n

    arduino-cli board list

    \n

    You can either use arduino-cli to flash the code onto your device after the sketch file has been built, or you can set flash:true and provide a port in your Lingua Franca file. To flash manually using arduino-cli, enter the subdirectory of src-gen in which the generated sketch file is located and run:

    \n

    arduino-cli upload -p PORT -b FQBN .

    ","headings":[{"value":"Overview","depth":1},{"value":"Prerequisites","depth":2},{"value":"Writing a simple Arduino Program","depth":1},{"value":"Platform Options","depth":2},{"value":"Baud rate of the serial port","depth":3},{"value":"Building","depth":1},{"value":"Flashing","depth":1}],"frontmatter":{"permalink":"/docs/handbook/arduino","title":"Arduino","oneline":"Developing LF Programs on Arduino.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Zephyr","oneline":"Developing LF Programs for Zephyr RTOS.","permalink":"/docs/handbook/zephyr"}}}},"pageContext":{"id":"4-arduino","slug":"/docs/handbook/arduino","repoPath":"/packages/documentation/copy/en/embedded/Arduino.md","nextID":"3f41a520-2093-55ff-a5e7-36ec8f69920a","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/causality-loops/page-data.json b/page-data/docs/handbook/causality-loops/page-data.json index e3ab40533..00a4e51ce 100644 --- a/page-data/docs/handbook/causality-loops/page-data.json +++ b/page-data/docs/handbook/causality-loops/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/causality-loops","result":{"data":{"markdownRemark":{"id":"2ca49a4a-51a0-5d4e-b91d-fc76ff59751a","excerpt":"Cycles The interconnection pattern for a collection of reactors can form a cycle, but some care is required. Consider the following example: $start(Cycle)$ $end…","html":"

    Cycles

    \n

    The interconnection pattern for a collection of reactors can form a cycle, but some care is required. Consider the following example:

    \n

    $start(Cycle)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(x) {=\n    # ... something here ...\n  =}\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n

    $end(Cycle)$

    \n

    This program yields the following diagram:

    \n\"Lingua\n

    The diagram highlights a causality loop in the program. At each tag, in reactor B, the first reaction has to execute before the second if it is enabled, a precedence indicated with the red dashed arrow. But the first can’t execute until the reaction of A has executed, and that reaction cannot execute until the second reaction B has executed. There is no way to satisfy these requirements, so the tools refuse to generated code.

    \n

    Cycles with Delays

    \n

    One way to break the causality loop and get an executable program is to introduce a logical delay into the loop, as shown below:

    \n

    $start(CycleWithDelay)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(x) {=\n    # ... something here ...\n  =}\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x after 0\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n

    $end(CycleWithDelay)$

    \n\"Lingua\n

    Here, we have used a delay of 0, which results in a delay of one microstep. We could equally well have specified a positive time value.

    \n

    Reaction Order

    \n

    Frequently, a program will have such cycles, but you don’t want a logical delay in the loop. To get a cycle without logical delays, the reactions need to be reordered, as shown below:

    \n

    $start(CycleReordered)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n  reaction(x) {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n

    $end(CycleReordered)$

    \n\"Lingua\n

    There is no longer any causality loop.

    ","headings":[{"value":"Cycles","depth":2},{"value":"Cycles with Delays","depth":2},{"value":"Reaction Order","depth":2}],"frontmatter":{"permalink":"/docs/handbook/causality-loops","title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Methods","oneline":"Methods in Lingua Franca.","permalink":"/docs/handbook/methods"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","permalink":"/docs/handbook/extending-reactors"}}}},"pageContext":{"id":"1-causality-loops","slug":"/docs/handbook/causality-loops","repoPath":"/packages/documentation/copy/en/topics/Causality Loops.md","previousID":"c2fc2a0b-a389-5c48-9cb0-c079bf2e6034","nextID":"d4ade317-2d91-582b-8cca-37743461f687","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/causality-loops","result":{"data":{"markdownRemark":{"id":"2ca49a4a-51a0-5d4e-b91d-fc76ff59751a","excerpt":"Cycles The interconnection pattern for a collection of reactors can form a cycle, but some care is required. Consider the following example: $start(Cycle)$ $end…","html":"

    Cycles

    \n

    The interconnection pattern for a collection of reactors can form a cycle, but some care is required. Consider the following example:

    \n

    $start(Cycle)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(x) {=\n    # ... something here ...\n  =}\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n

    $end(Cycle)$

    \n

    This program yields the following diagram:

    \n\"Lingua\n

    The diagram highlights a causality loop in the program. At each tag, in reactor B, the first reaction has to execute before the second if it is enabled, a precedence indicated with the red dashed arrow. But the first can’t execute until the reaction of A has executed, and that reaction cannot execute until the second reaction B has executed. There is no way to satisfy these requirements, so the tools refuse to generated code.

    \n

    Cycles with Delays

    \n

    One way to break the causality loop and get an executable program is to introduce a logical delay into the loop, as shown below:

    \n

    $start(CycleWithDelay)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(x) {=\n    # ... something here ...\n  =}\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x after 0\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(x) {=\n    // ... something here ...\n  =}\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x after 0;\n  b.y -> a.x;\n}\n
    \n

    $end(CycleWithDelay)$

    \n\"Lingua\n

    Here, we have used a delay of 0, which results in a delay of one microstep. We could equally well have specified a positive time value.

    \n

    Reaction Order

    \n

    Frequently, a program will have such cycles, but you don’t want a logical delay in the loop. To get a cycle without logical delays, the reactions need to be reordered, as shown below:

    \n

    $start(CycleReordered)$

    \n
    target C;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Cpp;\nreactor A {\n  input x:int;\n  output y:int;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:int;\n  output y:int;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target Python;\nreactor A {\n  input x;\n  output y;\n  reaction(x) -> y {=\n    # ... something here ...\n  =}\n}\nreactor B {\n  input x;\n  output y;\n  reaction(startup) -> y {=\n    # ... something here ...\n  =}\n  reaction(x) {=\n    # ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n
    target TypeScript\nreactor A {\n  input x:number\n  output y:number\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:number\n  output y:number\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A()\n  b = new B()\n  a.y -> b.x\n  b.y -> a.x\n}\n
    \n
    target Rust;\nreactor A {\n  input x:u32;\n  output y:u32;\n  reaction(x) -> y {=\n    // ... something here ...\n  =}\n}\nreactor B {\n  input x:u32;\n  output y:u32;\n  reaction(startup) -> y {=\n    // ... something here ...\n  =}\n  reaction(x) {=\n    // ... something here ...\n  =}\n}\nmain reactor {\n  a = new A();\n  b = new B();\n  a.y -> b.x;\n  b.y -> a.x;\n}\n
    \n

    $end(CycleReordered)$

    \n\"Lingua\n

    There is no longer any causality loop.

    ","headings":[{"value":"Cycles","depth":2},{"value":"Cycles with Delays","depth":2},{"value":"Reaction Order","depth":2}],"frontmatter":{"permalink":"/docs/handbook/causality-loops","title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Methods","oneline":"Methods in Lingua Franca.","permalink":"/docs/handbook/methods"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","permalink":"/docs/handbook/extending-reactors"}}}},"pageContext":{"id":"1-causality-loops","slug":"/docs/handbook/causality-loops","repoPath":"/packages/documentation/copy/en/topics/Causality Loops.md","previousID":"c2fc2a0b-a389-5c48-9cb0-c079bf2e6034","nextID":"d4ade317-2d91-582b-8cca-37743461f687","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/code-extension/page-data.json b/page-data/docs/handbook/code-extension/page-data.json index 4a15863ce..52a5f7f74 100644 --- a/page-data/docs/handbook/code-extension/page-data.json +++ b/page-data/docs/handbook/code-extension/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/code-extension","result":{"data":{"markdownRemark":{"id":"7ff25653-23b9-5682-8f53-43f7664a8487","excerpt":"The Lingua Franca extension for Visual Studio Code (VS Code) provides syntax-directed editing capability, compilation, and diagram synthesis for Lingua Franca…","html":"

    The Lingua Franca extension for Visual Studio Code (VS Code) provides syntax-directed editing capability, compilation, and diagram synthesis for Lingua Franca programs.

    \n

    Usage

    \n

    Creating a new file

    \n

    To create a new LF file, go to File > New File… and select New Lingua Franca File. When saving the file, save it in a directory called src to make sure that generated code is placed conveniently in an adjacent src-gen directory. For instance, for a file called Foo.lf, the directory structure after building should look something like this:

    \n
    bin/Foo\nsrc/\n└ Foo.lf\nsrc-gen/Foo
    \n

    Rendering diagrams

    \n

    To show the diagram for the currently active Lingua Franca file, click on the diagrams icon at the upper right:

    \n\n \n \"diagrams\n \n

    Compilation

    \n

    To compile the .lf source, open the command palette (Ctrl + Shift + P) and then enter Lingua Franca: Build.

    \n

    Running

    \n

    You can also build and immediately afterwards run your code by opening the command palette (Ctrl + Shift + P) and then entering Lingua Franca: Build and Run.\nRunning the code can also be done from the VS Code terminal by executing the generated file in ./bin.

    \n

    Notes

    \n

    For Python Users

    \n

    Users who edit LF programs with a Python target will benefit the most from Python linting by installing Pylint 2.12.2 or later.

    ","headings":[{"value":"Usage","depth":2},{"value":"Creating a new file","depth":3},{"value":"Rendering diagrams","depth":3},{"value":"Compilation","depth":3},{"value":"Running","depth":3},{"value":"Notes","depth":2},{"value":"For Python Users","depth":3}],"frontmatter":{"permalink":"/docs/handbook/code-extension","title":"Code Extension","oneline":"Visual Studio Code Extension for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Epoch IDE","oneline":"Epoch IDE for Lingua Franca.","permalink":"/docs/handbook/epoch-ide"}}}},"pageContext":{"id":"2-code-extension","slug":"/docs/handbook/code-extension","repoPath":"/packages/documentation/copy/en/tools/Code Extension.md","nextID":"bf08510b-3948-5b7f-bceb-5636ab49d351","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/code-extension","result":{"data":{"markdownRemark":{"id":"7ff25653-23b9-5682-8f53-43f7664a8487","excerpt":"The Lingua Franca extension for Visual Studio Code (VS Code) provides syntax-directed editing capability, compilation, and diagram synthesis for Lingua Franca…","html":"

    The Lingua Franca extension for Visual Studio Code (VS Code) provides syntax-directed editing capability, compilation, and diagram synthesis for Lingua Franca programs.

    \n

    Usage

    \n

    Creating a new file

    \n

    To create a new LF file, go to File > New File… and select New Lingua Franca File. When saving the file, save it in a directory called src to make sure that generated code is placed conveniently in an adjacent src-gen directory. For instance, for a file called Foo.lf, the directory structure after building should look something like this:

    \n
    bin/Foo\nsrc/\n└ Foo.lf\nsrc-gen/Foo
    \n

    Rendering diagrams

    \n

    To show the diagram for the currently active Lingua Franca file, click on the diagrams icon at the upper right:

    \n\n \n \"diagrams\n \n

    Compilation

    \n

    To compile the .lf source, open the command palette (Ctrl + Shift + P) and then enter Lingua Franca: Build.

    \n

    Running

    \n

    You can also build and immediately afterwards run your code by opening the command palette (Ctrl + Shift + P) and then entering Lingua Franca: Build and Run.\nRunning the code can also be done from the VS Code terminal by executing the generated file in ./bin.

    \n

    Notes

    \n

    For Python Users

    \n

    Users who edit LF programs with a Python target will benefit the most from Python linting by installing Pylint 2.12.2 or later.

    ","headings":[{"value":"Usage","depth":2},{"value":"Creating a new file","depth":3},{"value":"Rendering diagrams","depth":3},{"value":"Compilation","depth":3},{"value":"Running","depth":3},{"value":"Notes","depth":2},{"value":"For Python Users","depth":3}],"frontmatter":{"permalink":"/docs/handbook/code-extension","title":"Code Extension","oneline":"Visual Studio Code Extension for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Epoch IDE","oneline":"Epoch IDE for Lingua Franca.","permalink":"/docs/handbook/epoch-ide"}}}},"pageContext":{"id":"2-code-extension","slug":"/docs/handbook/code-extension","repoPath":"/packages/documentation/copy/en/tools/Code Extension.md","nextID":"bf08510b-3948-5b7f-bceb-5636ab49d351","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/command-line-tools/page-data.json b/page-data/docs/handbook/command-line-tools/page-data.json index e12c308d7..09c496f8b 100644 --- a/page-data/docs/handbook/command-line-tools/page-data.json +++ b/page-data/docs/handbook/command-line-tools/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/command-line-tools","result":{"data":{"markdownRemark":{"id":"d1faa7cd-c94d-58d6-8fee-1ad5d56dae5e","excerpt":"Usage Set up a Lingua Franca project by putting your program in a file with the .lf extension,\nsuch as Example.lf and putting that file with a directory called…","html":"

    Usage

    \n

    Set up a Lingua Franca project by putting your program in a file with the .lf extension,\nsuch as Example.lf and putting that file with a directory called src.\nThen compile the program:

    \n
    $ lfc src/Example.lf\n
    \n

    This will create two directories in parallel with the src directory, src-gen and bin. If your target language is a compiled one (like C and C++), then the bin directory should contain an executable that you can run:

    \n
    $ bin/Example\n
    \n

    To see the options that can be given to lfc, get help:

    \n
    $ lfc --help\n
    ","headings":[{"value":"Usage","depth":2}],"frontmatter":{"permalink":"/docs/handbook/command-line-tools","title":"Command Line Tools","oneline":"Command-line tools for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Epoch IDE","oneline":"Epoch IDE for Lingua Franca.","permalink":"/docs/handbook/epoch-ide"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Troubleshooting","oneline":"Troubleshooting page for Lingua Franca tools.","permalink":"/docs/handbook/troubleshooting"}}}},"pageContext":{"id":"2-command-line-tools","slug":"/docs/handbook/command-line-tools","repoPath":"/packages/documentation/copy/en/tools/Command Line Tools.md","previousID":"bf08510b-3948-5b7f-bceb-5636ab49d351","nextID":"0f46feff-0811-5262-9ee0-d7dc527800b9","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/command-line-tools","result":{"data":{"markdownRemark":{"id":"d1faa7cd-c94d-58d6-8fee-1ad5d56dae5e","excerpt":"Usage Set up a Lingua Franca project by putting your program in a file with the .lf extension,\nsuch as Example.lf and putting that file with a directory called…","html":"

    Usage

    \n

    Set up a Lingua Franca project by putting your program in a file with the .lf extension,\nsuch as Example.lf and putting that file with a directory called src.\nThen compile the program:

    \n
    $ lfc src/Example.lf\n
    \n

    This will create two directories in parallel with the src directory, src-gen and bin. If your target language is a compiled one (like C and C++), then the bin directory should contain an executable that you can run:

    \n
    $ bin/Example\n
    \n

    To see the options that can be given to lfc, get help:

    \n
    $ lfc --help\n
    ","headings":[{"value":"Usage","depth":2}],"frontmatter":{"permalink":"/docs/handbook/command-line-tools","title":"Command Line Tools","oneline":"Command-line tools for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Epoch IDE","oneline":"Epoch IDE for Lingua Franca.","permalink":"/docs/handbook/epoch-ide"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Troubleshooting","oneline":"Troubleshooting page for Lingua Franca tools.","permalink":"/docs/handbook/troubleshooting"}}}},"pageContext":{"id":"2-command-line-tools","slug":"/docs/handbook/command-line-tools","repoPath":"/packages/documentation/copy/en/tools/Command Line Tools.md","previousID":"bf08510b-3948-5b7f-bceb-5636ab49d351","nextID":"0f46feff-0811-5262-9ee0-d7dc527800b9","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/composing-reactors/page-data.json b/page-data/docs/handbook/composing-reactors/page-data.json index ea697251a..5c2524020 100644 --- a/page-data/docs/handbook/composing-reactors/page-data.json +++ b/page-data/docs/handbook/composing-reactors/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/composing-reactors","result":{"data":{"markdownRemark":{"id":"2fc4ce5a-22b6-5d88-8f16-224e8c04aa2e","excerpt":"$page-showing-target$ Contained Reactors Reactors can contain instances of other reactors defined in the same file or in an imported file. Assume the Count and…","html":"

    $page-showing-target$

    \n

    Contained Reactors

    \n

    Reactors can contain instances of other reactors defined in the same file or in an imported file. Assume the Count and Scale reactors defined in Parameters and State Variables are stored in files Count.lf and Scale.lf, respectively,\nand that the TestCount reactor from Time and Timers is stored in TestCount.lf. Then the following program composes one instance of each of the three:

    \n

    $start(RegressionTest)$

    \n
    target C {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Cpp {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Python {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target TypeScript {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, numInputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Rust {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n

    $end(RegressionTest)$

    \n

    Diagrams

    \n

    As soon as programs consist of more than one reactor, it becomes particularly useful to reference the diagrams that are automatically created and displayed by the Lingua Franca IDEs. The diagram for the above program is as follows:

    \n\"Lingua\n

    In this diagram, the timer is represented by a clock-like icon, the reactions by chevron shapes, and the $shutdown$ event by a diamond. If there were a $startup$ event in this program, it would appear as a circle.

    \n

    Creating Reactor Instances

    \n

    An instance is created with the syntax:

    \n
        <instance_name> = new <class_name>(<parameters>)\n
    \n

    A bank with several instances can be created in one such statement, as explained in the banks of reactors documentation.

    \n

    The <parameters> argument is a comma-separated list of assignments:

    \n
        <parameter_name> = <value>, ...\n
    \n

    Like the default value for parameters, <value> can be a numeric constant, a string enclosed in quotation marks, a time value such as 10 msec, target-language code enclosed in {= ... =}, or any of the list forms described in Expressions.

    \n

    Connections

    \n

    Connections between ports are specified with the syntax:

    \n
        <source_port_reference> -> <destination_port_reference>\n
    \n

    where the port references are either <instance_name>.<port_name> or just <port_name>, where the latter form is used for connections that cross hierarchical boundaries, as illustrated in the next section.

    \n

    On the left and right of a connection statement, you can put a comma-separated list. For example, the above pair of connections can be written,

    \n
        c.y, s.y -> s.x, t.x\n
    \n

    A constraint is that the total number of channels on the left match the total number on the right.\nIn addition, some targets require the types of all the ports to be the same.

    \n

    A destination port (on the right) can only be connected to a single source port (on the left). However, a source port may be connected to multiple destinations, as in the following example:

    \n
    \n
    reactor A {\n  output y\n}\nreactor B {\n  input x\n}\nmain reactor {\n  a = new A()\n  b1 = new B()\n  b2 = new B()\n  a.y -> b1.x\n  a.y -> b2.x\n}\n
    \n
    \n
    \n
    reactor A {\n  output y:int\n}\nreactor B {\n  input x:int\n}\nmain reactor {\n  a = new A()\n  b1 = new B()\n  b2 = new B()\n  a.y -> b1.x\n  a.y -> b2.x\n}\n
    \n
    \n\"Lingua\n

    Lingua Franca provides a convenient shortcut for such multicast connections, where the above two lines can be replaced by one as follows:

    \n
      (a.y)+ -> b1.x, b2.x\n
    \n

    The enclosing ( ... )+ means to repeat the enclosed comma-separated list of sources however many times is needed to provide inputs to all the sinks on the right of the connection ->.

    \n

    Import Statement

    \n

    An import statement has the form:

    \n
      import <classname> as <alias> from "<path>"\n
    \n

    where <classname> and <alias> can be a comma-separated list to import multiple reactors from the same file. The <path> specifies another .lf file relative to the location of the current file. The as <alias> portion is optional and specifies alternative class names to use in the $new$ statements.

    \n

    Hierarchy

    \n

    Reactors can be composed in arbitrarily deep hierarchies. For example, the following program combines the Count and Scale reactors within on Container:

    \n

    $start(Hierarchy)$

    \n
    target C\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: int = 2) {\n  output y: int\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target Cpp\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: int(2)) {\n  output y: int\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target Python\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride=2) {\n  output y\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target TypeScript\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: number = 2) {\n  output y: number\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, numInputs=11)\n  c.y -> t.x\n}\n
    \n
    target Rust\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: u32 = 2) {\n  output y: u32\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n

    $end(Hierarchy)$

    \n\"Lingua\n

    The Container has a parameter named stride, whose value is passed to the factor parameter of the Scale reactor. The line

    \n
      s.y -> y;\n
    \n

    establishes a connection across levels of the hierarchy. This propagates the output of a contained reactor to the output of the container. A similar notation may be used to propagate the input of a container to the input of a contained reactor,

    \n
      x -> s.x;\n
    \n

    Connections with Logical Delays

    \n

    Connections may include a logical delay using the $after$ keyword, as follows:

    \n
      <source_port_reference> -> <destination_port_reference> after <time_value>\n
    \n

    where <time_value> can be any of the forms described in Expressions.

    \n

    The $after$ keyword specifies that the logical time of the event delivered to the destination port will be larger than the logical time of the reaction that wrote to source port. The time value is required to be non-negative, but it can be zero, in which case the input event at the receiving end will be one microstep later than the event that triggered it.

    \n

    Physical Connections

    \n

    A subtle and rarely used variant of the -> connection is a physical connection, denoted ~>. For example:

    \n
    main reactor {\n  a = new A();\n  b = new B();\n  a.y ~> b.x;\n}\n
    \n

    This is rendered in by the diagram synthesizer as follows:

    \n\"Lingua\n

    In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender (unless fast is set to true), so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed-Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

    ","headings":[{"value":"Contained Reactors","depth":2},{"value":"Diagrams","depth":2},{"value":"Creating Reactor Instances","depth":2},{"value":"Connections","depth":2},{"value":"Import Statement","depth":2},{"value":"Hierarchy","depth":2},{"value":"Connections with Logical Delays","depth":2},{"value":"Physical Connections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/composing-reactors","title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","permalink":"/docs/handbook/time-and-timers"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Reactions","oneline":"Reactions in Lingua Franca.","permalink":"/docs/handbook/reactions"}}}},"pageContext":{"id":"1-composing-reactors","slug":"/docs/handbook/composing-reactors","repoPath":"/packages/documentation/copy/en/topics/Composing Reactors.md","previousID":"bba867ed-95b9-5017-b4f1-350e621e99da","nextID":"25ac2513-8979-52dd-9176-b0db61f55dc9","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/composing-reactors","result":{"data":{"markdownRemark":{"id":"2fc4ce5a-22b6-5d88-8f16-224e8c04aa2e","excerpt":"$page-showing-target$ Contained Reactors Reactors can contain instances of other reactors defined in the same file or in an imported file. Assume the Count and…","html":"

    $page-showing-target$

    \n

    Contained Reactors

    \n

    Reactors can contain instances of other reactors defined in the same file or in an imported file. Assume the Count and Scale reactors defined in Parameters and State Variables are stored in files Count.lf and Scale.lf, respectively,\nand that the TestCount reactor from Time and Timers is stored in TestCount.lf. Then the following program composes one instance of each of the three:

    \n

    $start(RegressionTest)$

    \n
    target C {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Cpp {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Python {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target TypeScript {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, numInputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n
    target Rust {\n  timeout: 1 sec,\n  fast: true\n}\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nmain reactor RegressionTest {\n  c = new Count()\n  s = new Scale(factor=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> s.x\n  s.y -> t.x\n}\n
    \n

    $end(RegressionTest)$

    \n

    Diagrams

    \n

    As soon as programs consist of more than one reactor, it becomes particularly useful to reference the diagrams that are automatically created and displayed by the Lingua Franca IDEs. The diagram for the above program is as follows:

    \n\"Lingua\n

    In this diagram, the timer is represented by a clock-like icon, the reactions by chevron shapes, and the $shutdown$ event by a diamond. If there were a $startup$ event in this program, it would appear as a circle.

    \n

    Creating Reactor Instances

    \n

    An instance is created with the syntax:

    \n
        <instance_name> = new <class_name>(<parameters>)\n
    \n

    A bank with several instances can be created in one such statement, as explained in the banks of reactors documentation.

    \n

    The <parameters> argument is a comma-separated list of assignments:

    \n
        <parameter_name> = <value>, ...\n
    \n

    Like the default value for parameters, <value> can be a numeric constant, a string enclosed in quotation marks, a time value such as 10 msec, target-language code enclosed in {= ... =}, or any of the list forms described in Expressions.

    \n

    Connections

    \n

    Connections between ports are specified with the syntax:

    \n
        <source_port_reference> -> <destination_port_reference>\n
    \n

    where the port references are either <instance_name>.<port_name> or just <port_name>, where the latter form is used for connections that cross hierarchical boundaries, as illustrated in the next section.

    \n

    On the left and right of a connection statement, you can put a comma-separated list. For example, the above pair of connections can be written,

    \n
        c.y, s.y -> s.x, t.x\n
    \n

    A constraint is that the total number of channels on the left match the total number on the right.\nIn addition, some targets require the types of all the ports to be the same.

    \n

    A destination port (on the right) can only be connected to a single source port (on the left). However, a source port may be connected to multiple destinations, as in the following example:

    \n
    \n
    reactor A {\n  output y\n}\nreactor B {\n  input x\n}\nmain reactor {\n  a = new A()\n  b1 = new B()\n  b2 = new B()\n  a.y -> b1.x\n  a.y -> b2.x\n}\n
    \n
    \n
    \n
    reactor A {\n  output y:int\n}\nreactor B {\n  input x:int\n}\nmain reactor {\n  a = new A()\n  b1 = new B()\n  b2 = new B()\n  a.y -> b1.x\n  a.y -> b2.x\n}\n
    \n
    \n\"Lingua\n

    Lingua Franca provides a convenient shortcut for such multicast connections, where the above two lines can be replaced by one as follows:

    \n
      (a.y)+ -> b1.x, b2.x\n
    \n

    The enclosing ( ... )+ means to repeat the enclosed comma-separated list of sources however many times is needed to provide inputs to all the sinks on the right of the connection ->.

    \n

    Import Statement

    \n

    An import statement has the form:

    \n
      import <classname> as <alias> from "<path>"\n
    \n

    where <classname> and <alias> can be a comma-separated list to import multiple reactors from the same file. The <path> specifies another .lf file relative to the location of the current file. The as <alias> portion is optional and specifies alternative class names to use in the $new$ statements.

    \n

    Hierarchy

    \n

    Reactors can be composed in arbitrarily deep hierarchies. For example, the following program combines the Count and Scale reactors within on Container:

    \n

    $start(Hierarchy)$

    \n
    target C\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: int = 2) {\n  output y: int\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target Cpp\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: int(2)) {\n  output y: int\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target Python\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride=2) {\n  output y\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n
    target TypeScript\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: number = 2) {\n  output y: number\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, numInputs=11)\n  c.y -> t.x\n}\n
    \n
    target Rust\nimport Count from "Count.lf"\nimport Scale from "Scale.lf"\nimport TestCount from "TestCount.lf"\nreactor Container(stride: u32 = 2) {\n  output y: u32\n  c = new Count()\n  s = new Scale(factor=stride)\n  c.y -> s.x\n  s.y -> y\n}\nmain reactor Hierarchy {\n  c = new Container(stride=4)\n  t = new TestCount(stride=4, num_inputs=11)\n  c.y -> t.x\n}\n
    \n

    $end(Hierarchy)$

    \n\"Lingua\n

    The Container has a parameter named stride, whose value is passed to the factor parameter of the Scale reactor. The line

    \n
      s.y -> y;\n
    \n

    establishes a connection across levels of the hierarchy. This propagates the output of a contained reactor to the output of the container. A similar notation may be used to propagate the input of a container to the input of a contained reactor,

    \n
      x -> s.x;\n
    \n

    Connections with Logical Delays

    \n

    Connections may include a logical delay using the $after$ keyword, as follows:

    \n
      <source_port_reference> -> <destination_port_reference> after <time_value>\n
    \n

    where <time_value> can be any of the forms described in Expressions.

    \n

    The $after$ keyword specifies that the logical time of the event delivered to the destination port will be larger than the logical time of the reaction that wrote to source port. The time value is required to be non-negative, but it can be zero, in which case the input event at the receiving end will be one microstep later than the event that triggered it.

    \n

    Physical Connections

    \n

    A subtle and rarely used variant of the -> connection is a physical connection, denoted ~>. For example:

    \n
    main reactor {\n  a = new A();\n  b = new B();\n  a.y ~> b.x;\n}\n
    \n

    This is rendered in by the diagram synthesizer as follows:

    \n\"Lingua\n

    In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender (unless fast is set to true), so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed-Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

    ","headings":[{"value":"Contained Reactors","depth":2},{"value":"Diagrams","depth":2},{"value":"Creating Reactor Instances","depth":2},{"value":"Connections","depth":2},{"value":"Import Statement","depth":2},{"value":"Hierarchy","depth":2},{"value":"Connections with Logical Delays","depth":2},{"value":"Physical Connections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/composing-reactors","title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","permalink":"/docs/handbook/time-and-timers"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Reactions","oneline":"Reactions in Lingua Franca.","permalink":"/docs/handbook/reactions"}}}},"pageContext":{"id":"1-composing-reactors","slug":"/docs/handbook/composing-reactors","repoPath":"/packages/documentation/copy/en/topics/Composing Reactors.md","previousID":"bba867ed-95b9-5017-b4f1-350e621e99da","nextID":"25ac2513-8979-52dd-9176-b0db61f55dc9","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/containerized-execution/page-data.json b/page-data/docs/handbook/containerized-execution/page-data.json index 83884c723..2d50ecb78 100644 --- a/page-data/docs/handbook/containerized-execution/page-data.json +++ b/page-data/docs/handbook/containerized-execution/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/containerized-execution","result":{"data":{"markdownRemark":{"id":"2548d58b-6c82-5c27-83b8-e05a47521264","excerpt":"For the C target at least, the Lingua Franca code generator is able to generate a Dockerfile when it generates the C source files. To enable this, include the…","html":"

    For the C target at least, the Lingua Franca code generator is able to generate a Dockerfile when it generates the C source files. To enable this, include the docker property in your target specification, as follows:

    \n
    target C {\n    docker: true\n};\n
    \n

    The generated Docker file has the same name as the LF file except that the extension is .Dockerfile and will be put in the src-gen directory. You can also specify options. Currently, only the base image (FROM) can be customized, but this will be extended to allow further customization is the future. To customize the Docker file, instead of just true above, which gives default options, specify the options as in the following example:

    \n
    target C {\n    docker: {FROM: "alpine:latest"}\n};\n
    \n

    This specifies that the base image is the latest version of alpine, a very small Linux. In fact, alpine:latest is the default value for this option, so you only need to specify this option if you need something other than alpine:latest.

    \n

    How to use this depends on whether your application is federated. You will need to install Docker if you haven’t already in order to use this.

    \n

    Unfederated Execution

    \n

    Using docker build and docker run

    \n

    Suppose your LF source file is Foo.lf. When you run lfc or use the IDE to generate code, a file called Foo.Dockerfile will appear in the src_gen directory. You can use this file to build a Docker image as follows. First, make sure you are in the same directory as the source file. Then issue the command:

    \n
       docker build -t foo -f src-gen/Foo.Dockerfile .\n
    \n

    This will create a Docker image with tag foo. The tag is required to be all lower-case letters. By convention, we advise using the LF source file name, converted to lower case.

    \n

    You can then use this tag to run the image in a container:

    \n
        docker run -t --rm foo\n
    \n

    The -t option creates a pseudo terminal, which is necessary for you to see any output produced by your program to stdout. If your program also reads from stdin, then you will need to give the -i option as well, or combine the two as it.

    \n

    The --rm option is important. This removes the container upon completion of the run. If you omit this option, the container will continue to exist even after your program has terminated. You can alternatively remove the container after the run using docker rm.

    \n

    If you wish for your program to run in the background, give a -d option as well (for “detached”). In this case, you will not see any output from your run.

    \n

    The above run command can include any supported command-line arguments to the LF program. For example, to specify a logical timeout, you can do this:

    \n
        docker run -t --rm foo --timeout 20 sec\n
    \n

    Using docker compose up

    \n

    When you use lfc to compile Foo.lf, a file called docker-compose.yml will also appear in the same directory where Foo.Dockerfile is located. cd to that directory. Then, use docker compose up to automatically build and run the container. Once the container finishes execution, use docker compose down in the same directory where docker-compose.yml is located to remove the container.

    \n

    Federated Execution

    \n

    Using docker build and docker run

    \n

    For a federated Lingua Franca program, one Dockerfile is created for each federate plus an additional one for the RTI. The Dockerfile for the RTI will be generated at src-gen/RTI. You will need to run docker build for each of these. For example, to build the image for the RTI, you can do this:

    \n
    docker build -t distributedcountcontainerized_rti -f src-gen/DistributedCountContainerized_RTI.Dockerfile .\n
    \n

    This is for the DistributedCountContainerized.lf, a federated that automatically runs in multiple Docker containers (one for the RTI and one for each federate) in continuous integration.

    \n

    Now, there are several options for how to proceed. One is to create a named network on which to run your federation. For example, to create a network named lf, do this:

    \n
        docker network create lf\n
    \n

    You can then run the RTI on this network and assign the RTI a name that the federates can use to find the RTI:

    \n
        docker run -t --rm --network lf --name distributedcount-rti distributedcount_rti\n
    \n

    Here, the assigned name is not quite the same as the tag that was specified when building the image (the last argument is the tag of the image to run in a container) because a host name is not allowed to have an underscore in it.

    \n

    Currently, you will also have to specify this host name in the LF source file so that the federates know where to find the RTI. E.g., in DistributedCount.lf, change the end of the file to read as follows:

    \n
    federated reactor DistributedCount at distributedcount-rti {\n    c = new Count();\n    p = new Print();\n    c.out -> p.in after 200 msec;\n}\n
    \n

    Notice the at distributedcount-rti, which must match the name you use when running the RTI. FIXME: We should find a way to make this more automatic!

    \n

    In two other terminals, you can now run the other federates on the same network:

    \n
    docker run -t --rm --network lf distributedcount_c\n
    \n

    and

    \n
    docker run -t --rm --network lf distributedcount_p\n
    \n

    Using docker compose up

    \n

    For a federated Lingua Franca program, once you use lfc to compile Foo.lf, on top of the docker-compose.yml for the reactors, an additional docker-compose.yml will be generated for the RTI and placed in src-gen/RTI.

    \n

    To run the federated program, open two terminals. In the first terminal, go to src-gen/RTI and use docker compose up to build and run the containerized RTI. Wait until the RTI is booted up. Then, open a second terminal and cd to the top level folder of the program (this is the folder that contains one folder for each of the federated reactors). You should see a docker-compose.yml there. Run docker compose up to build and run the containers.

    \n

    Once the program finished executing, run docker compose down in both the folder that contains the docker-compose.yml for the RTI as well as the folder that contains the docker-compose.yml for the reactors to remove the containers.

    ","headings":[{"value":"Unfederated Execution","depth":2},{"value":"Using docker build and docker run","depth":3},{"value":"Using docker compose up","depth":3},{"value":"Federated Execution","depth":2},{"value":"Using docker build and docker run","depth":3},{"value":"Using docker compose up","depth":3}],"frontmatter":{"permalink":"/docs/handbook/containerized-execution","title":"Containerized Execution","oneline":"Containerized Execution using Docker","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Tracing","oneline":"Tracing (preliminary)","permalink":"/docs/handbook/tracing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Security","oneline":"Secure Federated Execution","permalink":"/docs/handbook/security"}}}},"pageContext":{"id":"3-containerized-execution","slug":"/docs/handbook/containerized-execution","repoPath":"/packages/documentation/copy/en/reference/Containerized Execution.md","previousID":"3f4000b5-1133-5c34-807d-29c05884f149","nextID":"b02df86d-9ef8-5f8e-ba32-437934fba499","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/containerized-execution","result":{"data":{"markdownRemark":{"id":"2548d58b-6c82-5c27-83b8-e05a47521264","excerpt":"For the C target at least, the Lingua Franca code generator is able to generate a Dockerfile when it generates the C source files. To enable this, include the…","html":"

    For the C target at least, the Lingua Franca code generator is able to generate a Dockerfile when it generates the C source files. To enable this, include the docker property in your target specification, as follows:

    \n
    target C {\n    docker: true\n};\n
    \n

    The generated Docker file has the same name as the LF file except that the extension is .Dockerfile and will be put in the src-gen directory. You can also specify options. Currently, only the base image (FROM) can be customized, but this will be extended to allow further customization is the future. To customize the Docker file, instead of just true above, which gives default options, specify the options as in the following example:

    \n
    target C {\n    docker: {FROM: "alpine:latest"}\n};\n
    \n

    This specifies that the base image is the latest version of alpine, a very small Linux. In fact, alpine:latest is the default value for this option, so you only need to specify this option if you need something other than alpine:latest.

    \n

    How to use this depends on whether your application is federated. You will need to install Docker if you haven’t already in order to use this.

    \n

    Unfederated Execution

    \n

    Using docker build and docker run

    \n

    Suppose your LF source file is Foo.lf. When you run lfc or use the IDE to generate code, a file called Foo.Dockerfile will appear in the src_gen directory. You can use this file to build a Docker image as follows. First, make sure you are in the same directory as the source file. Then issue the command:

    \n
       docker build -t foo -f src-gen/Foo.Dockerfile .\n
    \n

    This will create a Docker image with tag foo. The tag is required to be all lower-case letters. By convention, we advise using the LF source file name, converted to lower case.

    \n

    You can then use this tag to run the image in a container:

    \n
        docker run -t --rm foo\n
    \n

    The -t option creates a pseudo terminal, which is necessary for you to see any output produced by your program to stdout. If your program also reads from stdin, then you will need to give the -i option as well, or combine the two as it.

    \n

    The --rm option is important. This removes the container upon completion of the run. If you omit this option, the container will continue to exist even after your program has terminated. You can alternatively remove the container after the run using docker rm.

    \n

    If you wish for your program to run in the background, give a -d option as well (for “detached”). In this case, you will not see any output from your run.

    \n

    The above run command can include any supported command-line arguments to the LF program. For example, to specify a logical timeout, you can do this:

    \n
        docker run -t --rm foo --timeout 20 sec\n
    \n

    Using docker compose up

    \n

    When you use lfc to compile Foo.lf, a file called docker-compose.yml will also appear in the same directory where Foo.Dockerfile is located. cd to that directory. Then, use docker compose up to automatically build and run the container. Once the container finishes execution, use docker compose down in the same directory where docker-compose.yml is located to remove the container.

    \n

    Federated Execution

    \n

    Using docker build and docker run

    \n

    For a federated Lingua Franca program, one Dockerfile is created for each federate plus an additional one for the RTI. The Dockerfile for the RTI will be generated at src-gen/RTI. You will need to run docker build for each of these. For example, to build the image for the RTI, you can do this:

    \n
    docker build -t distributedcountcontainerized_rti -f src-gen/DistributedCountContainerized_RTI.Dockerfile .\n
    \n

    This is for the DistributedCountContainerized.lf, a federated that automatically runs in multiple Docker containers (one for the RTI and one for each federate) in continuous integration.

    \n

    Now, there are several options for how to proceed. One is to create a named network on which to run your federation. For example, to create a network named lf, do this:

    \n
        docker network create lf\n
    \n

    You can then run the RTI on this network and assign the RTI a name that the federates can use to find the RTI:

    \n
        docker run -t --rm --network lf --name distributedcount-rti distributedcount_rti\n
    \n

    Here, the assigned name is not quite the same as the tag that was specified when building the image (the last argument is the tag of the image to run in a container) because a host name is not allowed to have an underscore in it.

    \n

    Currently, you will also have to specify this host name in the LF source file so that the federates know where to find the RTI. E.g., in DistributedCount.lf, change the end of the file to read as follows:

    \n
    federated reactor DistributedCount at distributedcount-rti {\n    c = new Count();\n    p = new Print();\n    c.out -> p.in after 200 msec;\n}\n
    \n

    Notice the at distributedcount-rti, which must match the name you use when running the RTI. FIXME: We should find a way to make this more automatic!

    \n

    In two other terminals, you can now run the other federates on the same network:

    \n
    docker run -t --rm --network lf distributedcount_c\n
    \n

    and

    \n
    docker run -t --rm --network lf distributedcount_p\n
    \n

    Using docker compose up

    \n

    For a federated Lingua Franca program, once you use lfc to compile Foo.lf, on top of the docker-compose.yml for the reactors, an additional docker-compose.yml will be generated for the RTI and placed in src-gen/RTI.

    \n

    To run the federated program, open two terminals. In the first terminal, go to src-gen/RTI and use docker compose up to build and run the containerized RTI. Wait until the RTI is booted up. Then, open a second terminal and cd to the top level folder of the program (this is the folder that contains one folder for each of the federated reactors). You should see a docker-compose.yml there. Run docker compose up to build and run the containers.

    \n

    Once the program finished executing, run docker compose down in both the folder that contains the docker-compose.yml for the RTI as well as the folder that contains the docker-compose.yml for the reactors to remove the containers.

    ","headings":[{"value":"Unfederated Execution","depth":2},{"value":"Using docker build and docker run","depth":3},{"value":"Using docker compose up","depth":3},{"value":"Federated Execution","depth":2},{"value":"Using docker build and docker run","depth":3},{"value":"Using docker compose up","depth":3}],"frontmatter":{"permalink":"/docs/handbook/containerized-execution","title":"Containerized Execution","oneline":"Containerized Execution using Docker","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Tracing","oneline":"Tracing (preliminary)","permalink":"/docs/handbook/tracing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Security","oneline":"Secure Federated Execution","permalink":"/docs/handbook/security"}}}},"pageContext":{"id":"3-containerized-execution","slug":"/docs/handbook/containerized-execution","repoPath":"/packages/documentation/copy/en/reference/Containerized Execution.md","previousID":"3f4000b5-1133-5c34-807d-29c05884f149","nextID":"b02df86d-9ef8-5f8e-ba32-437934fba499","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/contributing/page-data.json b/page-data/docs/handbook/contributing/page-data.json index 30830425a..5adb59e31 100644 --- a/page-data/docs/handbook/contributing/page-data.json +++ b/page-data/docs/handbook/contributing/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/contributing","result":{"data":{"markdownRemark":{"id":"cb27d929-c4ae-5578-9f34-e2f268594fd2","excerpt":"The preferred way to contribute to Lingua Franca is to issue pull requests through GitHub.\nSee the Contributing document for more details.","html":"

    The preferred way to contribute to Lingua Franca is to issue pull requests through GitHub.\nSee the Contributing document for more details.

    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/contributing","title":"Contributing","oneline":"Contribute to Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","permalink":"/docs/handbook/developer-setup"}}}},"pageContext":{"id":"5-contributing","slug":"/docs/handbook/contributing","repoPath":"/packages/documentation/copy/en/developer/Contributing.md","nextID":"81b7347d-2806-53c1-91ad-c7b12d062d3c","lang":"en","modifiedTime":"2023-10-19T14:14:27.297Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/contributing","result":{"data":{"markdownRemark":{"id":"cb27d929-c4ae-5578-9f34-e2f268594fd2","excerpt":"The preferred way to contribute to Lingua Franca is to issue pull requests through GitHub.\nSee the Contributing document for more details.","html":"

    The preferred way to contribute to Lingua Franca is to issue pull requests through GitHub.\nSee the Contributing document for more details.

    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/contributing","title":"Contributing","oneline":"Contribute to Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","permalink":"/docs/handbook/developer-setup"}}}},"pageContext":{"id":"5-contributing","slug":"/docs/handbook/contributing","repoPath":"/packages/documentation/copy/en/developer/Contributing.md","nextID":"81b7347d-2806-53c1-91ad-c7b12d062d3c","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/deadlines/page-data.json b/page-data/docs/handbook/deadlines/page-data.json index 379ec97b2..097ea01b8 100644 --- a/page-data/docs/handbook/deadlines/page-data.json +++ b/page-data/docs/handbook/deadlines/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/deadlines","result":{"data":{"markdownRemark":{"id":"f38ee330-34ee-5bea-906f-ebea05b6c4bd","excerpt":"$page-showing-target$ Lingua Franca includes a notion of a deadline, which is a constraint on the relation between logical time and physical time. Specifically…","html":"

    $page-showing-target$

    \n

    Lingua Franca includes a notion of a deadline, which is a constraint on the relation between logical time and physical time. Specifically, a program may specify that the invocation of a reaction must occur within some physical time interval of the logical time of the message. If a reaction is invoked at logical time 12 noon, for example, and the reaction has a deadline of one hour, then the reaction is required to be invoked before the physical-time clock of the execution platform reaches 1 PM. If the deadline is violated, then the specified deadline handler is invoked instead of the reaction.

    \n

    Purposes for Deadlines

    \n

    A deadline in an LF program serves two purposes. First, it can guide scheduling in that a scheduler may prioritize reactions with deadlines over those without or those with longer deadlines. For this purpose, if a reaction has a deadline, then all upstream reactions on which it depends (without logical delay) inherit its deadline. Hence, those upstream reactions will also be given higher priority.

    \n

    Second, the deadline mechanism provides a fault handler, a section of code to invoke when the deadline requirement is violated. Because invocation of the fault handler depends on factors beyond the control of the LF program, an LF program with deadlines becomes nondeterministic. The behavior of the program depends on the exact timing of the execution.

    \n

    There remains the question of when the fault handler should be invoked. By default, deadlines in LF are lazy, meaning that the fault handler is invoked at the logical time of the event triggering the reaction whose deadline is missed. Specifically, the possible violation of a deadline is not checked until the reaction with the deadline is ready to execute. Only then is the determination made whether to invoke the regular reaction or the fault handler.

    \n

    Deadline Specification

    \n

    A deadline is specified as follows:

    \n

    $start(Deadline)$

    \n
    target C;\nreactor Deadline {\n  input x:int;\n  output d:int; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    printf("Normal reaction.\\n");\n  =} deadline(10 msec) {=\n    printf("Deadline violation detected.\\n");\n    lf_set(d, x->value);\n  =}\n}\n
    \n
    target Cpp;\nreactor Deadline {\n  input x:int;\n  output d:int; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    std::cout << "Normal reaction." << std::endl;\n  =} deadline(10ms) {=\n    std::cout << "Deadline violation detected." << std::endl;\n    d.set(*x.get());\n  =}\n}\n
    \n
    target Python;\nreactor Deadline {\n  input x;\n  output d; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    print("Normal reaction.")\n  =} deadline(10 msec) {=\n    print("Deadline violation detected.")\n    d.set(x.value)\n  =}\n}\n
    \n
    target TypeScript\nreactor Deadline {\n  input x:number\n  output d:number // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    console.log("Normal reaction.")\n  =} deadline(10 msec) {=\n    console.log("Deadline violation detected.")\n    d = x\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Deadline.lf\n
    \n

    $end(Deadline)$

    \n

    This reactor specifies a deadline of 10 milliseconds (this can be a parameter of the reactor). If the reaction to x is triggered later in physical time than 10 msec past the timestamp of x, then the second body of code is executed instead of the first. That second body of code has access to anything the first body of code has access to, including the input x and the output d. The output can be used to notify the rest of the system that a deadline violation occurred. This reactor can be tested as follows:

    \n

    $start(DeadlineTest)$

    \n
    target C\nimport Deadline from "Deadline.lf"\npreamble {=\n  #include "platform.h"\n=}\nmain reactor {\n  logical action a\n  d = new Deadline()\n  reaction(startup) -> d.x, a {=\n    lf_set(d.x, 0);\n    lf_schedule(a, 0);\n  =}\n  reaction(a) -> d.x {=\n    lf_set(d.x, 0);\n    lf_sleep(MSEC(20));\n  =}\n  reaction(d.d) {=\n    printf("Deadline reactor produced an output.\\n");\n  =}\n}\n
    \n
    target Cpp;\nimport Deadline from "Deadline.lf";\nmain reactor {\n  logical action a;\n  d = new Deadline();\n  reaction(startup) -> d.x, a {=\n    d.x.set(0);\n    a.schedule(0ms);\n  =}\n  reaction(a) -> d.x {=\n    d.x.set(0);\n    std::this_thread::sleep_for(20ms);\n  =}\n  reaction(d.d) {=\n    std::cout << "Deadline reactor produced an output." << std::endl;\n  =}\n}\n
    \n
    target Python;\nimport Deadline from "Deadline.lf";\npreamble {= import time =}\nmain reactor {\n  logical action a;\n  d = new Deadline();\n  reaction(startup) -> d.x, a {=\n    d.x.set(0)\n    a.schedule(0)\n  =}\n  reaction(a) -> d.x {=\n    d.x.set(0)\n    time.sleep(0.02)\n  =}\n  reaction(d.d) {=\n    print("Deadline reactor produced an output.")\n  =}\n}\n
    \n
    target TypeScript\nimport Deadline from "Deadline.lf"\nmain reactor {\n  logical action a\n  d = new Deadline()\n  reaction(startup) -> d.x, a {=\n    d.x = 0\n    actions.a.schedule(TimeValue.zero(), null)\n  =}\n  reaction(a) -> d.x {=\n    d.x = 0\n    for (const later = util.getCurrentPhysicalTime().add(TimeValue.msecs(20));\n      util.getCurrentPhysicalTime() < later;) {\n      // Take time...\n    }\n  =}\n  reaction(d.d) {=\n    console.log("Deadline reactor produced an output.")\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/DeadlineTest.lf\n
    \n

    $end(DeadlineTest)$

    \n\"Lingua\n

    Running this program will result in the following output:

    \n
    Normal reaction.\nDeadline violation detected.\nDeadline reactor produced an output.
    \n

    The first reaction of the Deadline reactor does not violate the deadline, but the second does. Notice that the sleep in the $main$ reactor occurs after setting the output, but because of the deterministic semantics of LF, this does not matter. The actual value of an output cannot be known until every reaction that sets that output completes its execution. Since this reaction takes at least 20 msec to complete, the deadline is assured of being violated.

    \n

    Notice that the deadline is annotated in the diagram with a small clock symbol.

    \n
    \n

    Notice that the deadline violation here is caused by an invocation of lf_sleep, defined in \"platform.h\" (see Libraries Available to Programmers).\nIt is not generally advisable for a reaction to sleep because this can block other reactions from executing.\nBut this is exactly what we are trying to accomplish here in order to force a deadline to be violated.

    \n
    \n

    Deadline Violations During Execution

    \n

    Whether a deadline violation occurs is checked only before invoking the reaction with a deadline. What if the reaction itself runs for long enough that the deadline gets violated during the reaction execution? For this purpose, a target-language function is provided to check whether a deadline is violated during execution of a reaction with a deadline.

    \n
    \n

    NOTE: As of this writing, this function is only implemented in the C target.

    \n
    \n

    Consider this example:

    \n

    $start(CheckDeadline)$

    \n
    target C;\nreactor Count {\n  output out:int;\n  reaction(startup) -> out {=\n    int count = 0;\n    while (!lf_check_deadline(self, true)) {\n      count++;\n    }\n    lf_set(out, count);\n  =} deadline (3 msec) {=\n    printf("Stopped counting.\\n");\n  =}\n}\nmain reactor {\n  c = new Count();\n  reaction(c.out) {=\n    printf("Counted to %d\\n", c.out->value);\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/py/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/CheckDeadline.lf\n
    \n

    $end(CheckDeadline)$

    \n
    \n

    The Count reactor has a single reaction with a deadline of 3 msec.\nIf the deadline is not already violated when this reaction becomes enabled (at startup), then the reaction begins executing a loop. In each iteration of the loop, it calls lf_check_deadline(self, true), which returns true if the deadline has been violated and false otherwise. Hence, this reaction will increment the count variable as many times as possible before the deadline is violated and, at\nthat point, will exit the loop and produce on the output the count. Running this program will produce something like this:

    \n
    Stopped counting.\nCounted to 20257
    \n

    This is a (rather trivial) example of an anytime computation. Such computations proceed to improve results until time runs out and then produce the most improved result.

    \n

    The arguments to the lf_check_deadline are the self struct and a boolean that indicates whether the deadline violation handler should be invoked upon detecting a deadline violation. Because the argument is true above, the handler is invoked and Stopped counting is printed.

    \n
    ","headings":[{"value":"Purposes for Deadlines","depth":2},{"value":"Deadline Specification","depth":2},{"value":"Deadline Violations During Execution","depth":2}],"frontmatter":{"permalink":"/docs/handbook/deadlines","title":"Deadlines","oneline":"Deadlines in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Modal Reactors","oneline":"Modal Reactors","permalink":"/docs/handbook/modal-models"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","permalink":"/docs/handbook/multiports-and-banks"}}}},"pageContext":{"id":"1-deadlines","slug":"/docs/handbook/deadlines","repoPath":"/packages/documentation/copy/en/topics/Deadlines.md","previousID":"ddeb2577-9554-5362-9ed2-abba8f412fc1","nextID":"9ff63bbf-2bdf-553e-a96d-52355866ec94","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/deadlines","result":{"data":{"markdownRemark":{"id":"f38ee330-34ee-5bea-906f-ebea05b6c4bd","excerpt":"$page-showing-target$ Lingua Franca includes a notion of a deadline, which is a constraint on the relation between logical time and physical time. Specifically…","html":"

    $page-showing-target$

    \n

    Lingua Franca includes a notion of a deadline, which is a constraint on the relation between logical time and physical time. Specifically, a program may specify that the invocation of a reaction must occur within some physical time interval of the logical time of the message. If a reaction is invoked at logical time 12 noon, for example, and the reaction has a deadline of one hour, then the reaction is required to be invoked before the physical-time clock of the execution platform reaches 1 PM. If the deadline is violated, then the specified deadline handler is invoked instead of the reaction.

    \n

    Purposes for Deadlines

    \n

    A deadline in an LF program serves two purposes. First, it can guide scheduling in that a scheduler may prioritize reactions with deadlines over those without or those with longer deadlines. For this purpose, if a reaction has a deadline, then all upstream reactions on which it depends (without logical delay) inherit its deadline. Hence, those upstream reactions will also be given higher priority.

    \n

    Second, the deadline mechanism provides a fault handler, a section of code to invoke when the deadline requirement is violated. Because invocation of the fault handler depends on factors beyond the control of the LF program, an LF program with deadlines becomes nondeterministic. The behavior of the program depends on the exact timing of the execution.

    \n

    There remains the question of when the fault handler should be invoked. By default, deadlines in LF are lazy, meaning that the fault handler is invoked at the logical time of the event triggering the reaction whose deadline is missed. Specifically, the possible violation of a deadline is not checked until the reaction with the deadline is ready to execute. Only then is the determination made whether to invoke the regular reaction or the fault handler.

    \n

    Deadline Specification

    \n

    A deadline is specified as follows:

    \n

    $start(Deadline)$

    \n
    target C;\nreactor Deadline {\n  input x:int;\n  output d:int; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    printf("Normal reaction.\\n");\n  =} deadline(10 msec) {=\n    printf("Deadline violation detected.\\n");\n    lf_set(d, x->value);\n  =}\n}\n
    \n
    target Cpp;\nreactor Deadline {\n  input x:int;\n  output d:int; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    std::cout << "Normal reaction." << std::endl;\n  =} deadline(10ms) {=\n    std::cout << "Deadline violation detected." << std::endl;\n    d.set(*x.get());\n  =}\n}\n
    \n
    target Python;\nreactor Deadline {\n  input x;\n  output d; // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    print("Normal reaction.")\n  =} deadline(10 msec) {=\n    print("Deadline violation detected.")\n    d.set(x.value)\n  =}\n}\n
    \n
    target TypeScript\nreactor Deadline {\n  input x:number\n  output d:number // Produced if the deadline is violated.\n  reaction(x) -> d {=\n    console.log("Normal reaction.")\n  =} deadline(10 msec) {=\n    console.log("Deadline violation detected.")\n    d = x\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Deadline.lf\n
    \n

    $end(Deadline)$

    \n

    This reactor specifies a deadline of 10 milliseconds (this can be a parameter of the reactor). If the reaction to x is triggered later in physical time than 10 msec past the timestamp of x, then the second body of code is executed instead of the first. That second body of code has access to anything the first body of code has access to, including the input x and the output d. The output can be used to notify the rest of the system that a deadline violation occurred. This reactor can be tested as follows:

    \n

    $start(DeadlineTest)$

    \n
    target C\nimport Deadline from "Deadline.lf"\npreamble {=\n  #include "platform.h"\n=}\nmain reactor {\n  logical action a\n  d = new Deadline()\n  reaction(startup) -> d.x, a {=\n    lf_set(d.x, 0);\n    lf_schedule(a, 0);\n  =}\n  reaction(a) -> d.x {=\n    lf_set(d.x, 0);\n    lf_sleep(MSEC(20));\n  =}\n  reaction(d.d) {=\n    printf("Deadline reactor produced an output.\\n");\n  =}\n}\n
    \n
    target Cpp;\nimport Deadline from "Deadline.lf";\nmain reactor {\n  logical action a;\n  d = new Deadline();\n  reaction(startup) -> d.x, a {=\n    d.x.set(0);\n    a.schedule(0ms);\n  =}\n  reaction(a) -> d.x {=\n    d.x.set(0);\n    std::this_thread::sleep_for(20ms);\n  =}\n  reaction(d.d) {=\n    std::cout << "Deadline reactor produced an output." << std::endl;\n  =}\n}\n
    \n
    target Python;\nimport Deadline from "Deadline.lf";\npreamble {= import time =}\nmain reactor {\n  logical action a;\n  d = new Deadline();\n  reaction(startup) -> d.x, a {=\n    d.x.set(0)\n    a.schedule(0)\n  =}\n  reaction(a) -> d.x {=\n    d.x.set(0)\n    time.sleep(0.02)\n  =}\n  reaction(d.d) {=\n    print("Deadline reactor produced an output.")\n  =}\n}\n
    \n
    target TypeScript\nimport Deadline from "Deadline.lf"\nmain reactor {\n  logical action a\n  d = new Deadline()\n  reaction(startup) -> d.x, a {=\n    d.x = 0\n    actions.a.schedule(TimeValue.zero(), null)\n  =}\n  reaction(a) -> d.x {=\n    d.x = 0\n    for (const later = util.getCurrentPhysicalTime().add(TimeValue.msecs(20));\n      util.getCurrentPhysicalTime() < later;) {\n      // Take time...\n    }\n  =}\n  reaction(d.d) {=\n    console.log("Deadline reactor produced an output.")\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/DeadlineTest.lf\n
    \n

    $end(DeadlineTest)$

    \n\"Lingua\n

    Running this program will result in the following output:

    \n
    Normal reaction.\nDeadline violation detected.\nDeadline reactor produced an output.
    \n

    The first reaction of the Deadline reactor does not violate the deadline, but the second does. Notice that the sleep in the $main$ reactor occurs after setting the output, but because of the deterministic semantics of LF, this does not matter. The actual value of an output cannot be known until every reaction that sets that output completes its execution. Since this reaction takes at least 20 msec to complete, the deadline is assured of being violated.

    \n

    Notice that the deadline is annotated in the diagram with a small clock symbol.

    \n
    \n

    Notice that the deadline violation here is caused by an invocation of lf_sleep, defined in \"platform.h\" (see Libraries Available to Programmers).\nIt is not generally advisable for a reaction to sleep because this can block other reactions from executing.\nBut this is exactly what we are trying to accomplish here in order to force a deadline to be violated.

    \n
    \n

    Deadline Violations During Execution

    \n

    Whether a deadline violation occurs is checked only before invoking the reaction with a deadline. What if the reaction itself runs for long enough that the deadline gets violated during the reaction execution? For this purpose, a target-language function is provided to check whether a deadline is violated during execution of a reaction with a deadline.

    \n
    \n

    NOTE: As of this writing, this function is only implemented in the C target.

    \n
    \n

    Consider this example:

    \n

    $start(CheckDeadline)$

    \n
    target C;\nreactor Count {\n  output out:int;\n  reaction(startup) -> out {=\n    int count = 0;\n    while (!lf_check_deadline(self, true)) {\n      count++;\n    }\n    lf_set(out, count);\n  =} deadline (3 msec) {=\n    printf("Stopped counting.\\n");\n  =}\n}\nmain reactor {\n  c = new Count();\n  reaction(c.out) {=\n    printf("Counted to %d\\n", c.out->value);\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/py/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/CheckDeadline.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/CheckDeadline.lf\n
    \n

    $end(CheckDeadline)$

    \n
    \n

    The Count reactor has a single reaction with a deadline of 3 msec.\nIf the deadline is not already violated when this reaction becomes enabled (at startup), then the reaction begins executing a loop. In each iteration of the loop, it calls lf_check_deadline(self, true), which returns true if the deadline has been violated and false otherwise. Hence, this reaction will increment the count variable as many times as possible before the deadline is violated and, at\nthat point, will exit the loop and produce on the output the count. Running this program will produce something like this:

    \n
    Stopped counting.\nCounted to 20257
    \n

    This is a (rather trivial) example of an anytime computation. Such computations proceed to improve results until time runs out and then produce the most improved result.

    \n

    The arguments to the lf_check_deadline are the self struct and a boolean that indicates whether the deadline violation handler should be invoked upon detecting a deadline violation. Because the argument is true above, the handler is invoked and Stopped counting is printed.

    \n
    ","headings":[{"value":"Purposes for Deadlines","depth":2},{"value":"Deadline Specification","depth":2},{"value":"Deadline Violations During Execution","depth":2}],"frontmatter":{"permalink":"/docs/handbook/deadlines","title":"Deadlines","oneline":"Deadlines in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Modal Reactors","oneline":"Modal Reactors","permalink":"/docs/handbook/modal-models"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","permalink":"/docs/handbook/multiports-and-banks"}}}},"pageContext":{"id":"1-deadlines","slug":"/docs/handbook/deadlines","repoPath":"/packages/documentation/copy/en/topics/Deadlines.md","previousID":"ddeb2577-9554-5362-9ed2-abba8f412fc1","nextID":"9ff63bbf-2bdf-553e-a96d-52355866ec94","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/developer-setup/page-data.json b/page-data/docs/handbook/developer-setup/page-data.json index a595d4dcc..052a2ced1 100644 --- a/page-data/docs/handbook/developer-setup/page-data.json +++ b/page-data/docs/handbook/developer-setup/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/developer-setup","result":{"data":{"markdownRemark":{"id":"fcfe37e2-bc0b-50b8-8e1b-490aa315a726","excerpt":"Prerequisites Java 17 (download from Oracle) Cloning the Repository Please run the following commands to clone the repository and its submodules. Building the…","html":"

    Prerequisites

    \n\n

    Cloning the Repository

    \n

    Please run the following commands to clone the repository and its submodules.

    \n
    git clone git@github.com:lf-lang/lingua-franca.git\ncd lingua-franca\ngit submodule update --init --recursive\n
    \n

    Building the command line tools

    \n

    We use Gradle for building the code within our repository.

    \n

    For an easy start, the bin/ directory contains scripts for building and running our command line tools, including the compiler lfc.\nTry to run ./bin/lfc-dev --version.\nThis will first build lfc and then execute it through Gradle.

    \n

    To build the entire repository, you can simply run ./gradlew build.\nThis will build all tools and also run all formatting checks and unit tests.\nNote that this does not run our integration tests.\nFor more details on our testing infrastructure, please refer to the Regression Test section.

    \n

    If you only want to build without running any tests, you can use ./gradlew assemble instead.\nBoth the assemble and the build task will create a distribution package containing our command line tools in build/distribution.\nThere is also an installed version of this package in build/install/lf-cli/.\nIf you run build/install/lf-cli/bin/lfc this will run lfc as it was last build.\nThus, you can choose if you want to use bin/lfc-dev, which first builds lfc using the latest code and then runs it, or if you prefer to run ./gradlew build and then separately invoke build/install/lf-cli/bin/lfc.

    \n

    IDE Integration

    \n

    You can use any editor or IDE that you like to work with the code base.\nHowever, we would suggest to choose an IDE that comes with good Java (and\nideally Kotlin) support and that integrates well with Gradle.\nWe recommend to use our IntelliJ setup.

    \n

    Building IDEs

    \n

    Currently, we provide two IDEs that support Lingua Franca programs.\nTheir source code is located in external repositories.\nWe have a Lingua Franca extension for VS code and an Eclipse based IDE called Epoch.\nPlease refer to the READMEs for build instructions.

    ","headings":[{"value":"Prerequisites","depth":2},{"value":"Cloning the Repository","depth":2},{"value":"Building the command line tools","depth":2},{"value":"IDE Integration","depth":2},{"value":"Building IDEs","depth":2}],"frontmatter":{"permalink":"/docs/handbook/developer-setup","title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","permalink":"/docs/handbook/intellij"}}}},"pageContext":{"id":"5-developer-setup","slug":"/docs/handbook/developer-setup","repoPath":"/packages/documentation/copy/en/developer/Downloading and Building.md","previousID":"008e847f-8ee6-513a-afab-0995ffce336b","nextID":"1d9f0442-2300-5615-9c04-6ee5f2c33793","lang":"en","modifiedTime":"2023-10-19T14:14:27.297Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/developer-setup","result":{"data":{"markdownRemark":{"id":"fcfe37e2-bc0b-50b8-8e1b-490aa315a726","excerpt":"Prerequisites Java 17 (download from Oracle) Cloning the Repository Please run the following commands to clone the repository and its submodules. Building the…","html":"

    Prerequisites

    \n\n

    Cloning the Repository

    \n

    Please run the following commands to clone the repository and its submodules.

    \n
    git clone git@github.com:lf-lang/lingua-franca.git\ncd lingua-franca\ngit submodule update --init --recursive\n
    \n

    Building the command line tools

    \n

    We use Gradle for building the code within our repository.

    \n

    For an easy start, the bin/ directory contains scripts for building and running our command line tools, including the compiler lfc.\nTry to run ./bin/lfc-dev --version.\nThis will first build lfc and then execute it through Gradle.

    \n

    To build the entire repository, you can simply run ./gradlew build.\nThis will build all tools and also run all formatting checks and unit tests.\nNote that this does not run our integration tests.\nFor more details on our testing infrastructure, please refer to the Regression Test section.

    \n

    If you only want to build without running any tests, you can use ./gradlew assemble instead.\nBoth the assemble and the build task will create a distribution package containing our command line tools in build/distribution.\nThere is also an installed version of this package in build/install/lf-cli/.\nIf you run build/install/lf-cli/bin/lfc this will run lfc as it was last build.\nThus, you can choose if you want to use bin/lfc-dev, which first builds lfc using the latest code and then runs it, or if you prefer to run ./gradlew build and then separately invoke build/install/lf-cli/bin/lfc.

    \n

    IDE Integration

    \n

    You can use any editor or IDE that you like to work with the code base.\nHowever, we would suggest to choose an IDE that comes with good Java (and\nideally Kotlin) support and that integrates well with Gradle.\nWe recommend to use our IntelliJ setup.

    \n

    Building IDEs

    \n

    Currently, we provide two IDEs that support Lingua Franca programs.\nTheir source code is located in external repositories.\nWe have a Lingua Franca extension for VS code and an Eclipse based IDE called Epoch.\nPlease refer to the READMEs for build instructions.

    ","headings":[{"value":"Prerequisites","depth":2},{"value":"Cloning the Repository","depth":2},{"value":"Building the command line tools","depth":2},{"value":"IDE Integration","depth":2},{"value":"Building IDEs","depth":2}],"frontmatter":{"permalink":"/docs/handbook/developer-setup","title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","permalink":"/docs/handbook/intellij"}}}},"pageContext":{"id":"5-developer-setup","slug":"/docs/handbook/developer-setup","repoPath":"/packages/documentation/copy/en/developer/Downloading and Building.md","previousID":"008e847f-8ee6-513a-afab-0995ffce336b","nextID":"1d9f0442-2300-5615-9c04-6ee5f2c33793","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/distributed-execution/page-data.json b/page-data/docs/handbook/distributed-execution/page-data.json index ddb85de45..486919980 100644 --- a/page-data/docs/handbook/distributed-execution/page-data.json +++ b/page-data/docs/handbook/distributed-execution/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/distributed-execution","result":{"data":{"markdownRemark":{"id":"01e8e05d-d3c6-5a55-a16a-4c1baf83a83c","excerpt":"Federated execution is not supported in $target-language$. $page-showing-target$ NOTE: Distributed execution of Lingua Franca programs is at an early stage of…","html":"
    \n

    Federated execution is not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    NOTE: Distributed execution of Lingua Franca programs is at an early stage of development with many missing capabilities and a rather brittle execution. It is ready for experimentation, but not yet for deployment of serious systems. The capability has been tested on macOS and Linux, and there are no plans currently to support Windows systems.

    \n

    A distributed Lingua Franca program is called a federation. Each reactor within the main reactor is called a federate. The LF compiler generates a separate program for each federate and synthesizes the code for the federates to communicate. The federates can be distributed across networks and eventually will be able to be written in different target languages, although this is not yet supported.

    \n

    In addition to the federates, there is a program called the RTI, for runtime infrastructure, that coordinates startup and shutdown and may, if the coordination is centralized, mediate communication. The RTI needs to be compiled and installed separately on the system before any federation can execute.

    \n

    It is possible to encapsulate federates in Docker containers for deployment.\nSee containerized execution.

    \n

    Installation of the RTI

    \n

    Federated execution requires installation of a separate stand-alone program called the Runtime Infrastructure or RTI. At the current time, the only way to install this is from source files:

    \n
    git clone https://github.com/lf-lang/reactor-c.git\ncd reactor-c/core/federated/RTI/\nmkdir build && cd build\ncmake ../\nmake\nsudo make install\n
    \n

    The above will create a program called RTI and install it at /usr/local/bin/RTI. Once this program is available in your path, you can compile and execute federated Lingua Franca programs using Epoch, VS Code, or the command-line tools. For more details, see the README file.

    \n

    Minimal Example

    \n

    A minimal federated execution is specified by using the $federated$ keyword instead of $main$ for the main federate. An example is given below:

    \n

    $start(Federated)$

    \n
    target C\nreactor Count {\n  output out: int\n  state c: int = 0\n  timer t(0, 1 sec)\n  reaction(t) -> out {=\n    lf_set(out, self->c++);\n  =}\n}\nreactor Print {\n  input in: int\n  reaction(in) {=\n    lf_print("Received: %d at (%lld, %d)", in->value,\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new Print()\n  c.out -> p.in\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Federated.lf\n
    \n
    target Python\nreactor Count {\n  output out\n  state c = 0\n  timer t(0, 1 sec)\n  reaction(t) -> out {=\n    out.set(self.c)\n    self.c += 1\n  =}\n}\nreactor Print {\n  input inp\n  reaction(inp) {=\n    print(\n        f"Received: {inp.value} "\n        f"at ({lf.time.logical_elapsed()}, {lf.tag().microstep})"\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new Print()\n  c.out -> p.inp\n}\n
    \n
    target TypeScript {\n  timeout: 1 msec  // FIXME: This should work with timeout: 0 msec.\n}\nreactor Source {\n  output out: string\n  reaction(startup) -> out {=\n    out = "Hello World!";\n  =}\n}\nreactor Destination {\n  input inp: string\n  reaction(inp) {=\n    console.log("Received: " + inp);\n  =}\n}\nfederated reactor Federated {\n  s = new Source()\n  d = new Destination()\n  s.out -> d.inp\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Federated.lf\n
    \n

    $end(Federated)$

    \n

    The $federated$ keyword tells the code generator that the program is to be split into several distinct programs, one for each top level reactor.

    \n

    When you run the code generator on src/Federated.lf containing the above code, the following three programs will appear:

    \n
    \n
    bin/Federated\nbin/Federated_s\nbin/Federated_d
    \n
    \n
    \n
    bin/Federated\nsrc-gen/Federated/s/Federated_s.py\nsrc-gen/Federated/d/Federated_d.py
    \n
    \n
    \n
    bin/Federated\nsrc-gen/dist/Federated/Federated_s.js\nsrc-gen/dist/Federated/Federated_d.js
    \n
    \n

    The root name, Federated, is the name of the .lf file from which these are generated (and the name of the main reactor, which is required to match if it is specified). The suffixes “_s” and “_d” come from the names of the top-level instances. There will always be one federate for each top-level reactor instance.

    \n

    To run the program, you can simply run bin/Federated, which is a bash script that launches the RTI and two other programs, Federated_s and Federated_d.\nAlternatively, you can manually execute the RTI followed by the two federate programs by starting them on the command line. It is best to use three separate terminal windows (so that outputs from the three programs do not get jumbled together) to execute the following commands:

    \n
    \n
    RTI -n 2\nbin/Federated_s\nbin/Federated_d\n
    \n
    \n
    \n
    RTI -n 2\npython3 src-gen/Federated/s/Federated_s.py\npython3 src-gen/Federated/d/Federated_d.py\n
    \n
    \n
    \n
    RTI -n 2\nnode src-gen/Federated/dist/Federated_s.js\nnode src-gen/Federated/dist/Federated_d.js\n
    \n
    \n

    The -n argument to the RTI specifies that there it should expect two federates to join the federation.

    \n

    Upon running the program, you will see information printed about the starting and ending of the federation, and buried in the output will be this line:

    \n
    \n
    Federate 1: Received: Hello World!
    \n

    The prefix Federate 1 is automatically added by the built-in lf_print function to help disambiguate the outputs from multiple concurrent federates.

    \n
    \n
    \n
    Received: Hello World!
    \n
    \n
    \n
    Received: Hello World!
    \n
    \n

    Federation ID

    \n

    You may have several federations running on the same machine(s) or even several instances of the same federation. In this case, it is necessary to distinguish between the federations. To accomplish this, you can pass a -i or --id parameter to the RTI and its federates with an identifier that is unique to the particular federation. For example,

    \n
    \n
    RTI -n 2 -i myFederation\nbin/Federated_s -i myFederation\nbin/Federated_d -i myFederation\n
    \n
    \n
    \n
    RTI -n 2 -i myFederation\npython3 src-gen/Federated/s/Federated_s.py -i myFederation\npython3 src-gen/Federated/d/Federated_d.py -i myFederation\n
    \n
    \n
    \n
    RTI -n 2 -i myFederation\nnode src-gen/Federated/dist/Federated_s.js -i myFederation\nnode src-gen/Federated/dist/Federated_d.js -i myFederation\n
    \n
    \n

    Each federate must have the same ID as the RTI in order to join the federation.\nThe bash script that executes each of the components of the federation automatically generates a unique federation ID each time you run it.

    \n

    Coordinated Start

    \n

    When the above programs execute, each federate registers with the RTI. When all expected federates have registered, the RTI broadcasts to the federates the logical time at which they should start execution. Hence, all federates start at the same logical time.

    \n

    The starting logical time is determined as follows. When each federate starts executing, it sends its current physical time (drawn from its real-time clock) to the RTI. When the RTI has heard from all the federates, it chooses the largest of these physical times, adds a fixed offset (currently one second), and broadcasts the resulting time to each federate.

    \n

    When a federate receives the starting time from the RTI, if it is running in realtime mode (the default), then it will wait until its local physical clock matches or exceeds that starting time. Thus, to the extent that the machines have synchronized clocks, the federates will all start executing at roughly the same physical time, a physical time close to the starting logical time.

    \n

    Coordinated Shutdown

    \n

    Coordinating the shutdown of a distributed program is discussed in Termination.

    \n

    Communication Between Federates

    \n

    When one federate sends data to another, by default, the timestamp at the receiver will match the timestamp at the sender. You can also specify a logical delay on the communication using the after keyword. For example, if we had instead specified

    \n
    \ts.out -> p.in after 200 msec;\n
    \n

    then the timestamp at the receiving end will be incremented by 200 msec compared to the timestamp at the sender.

    \n

    The preservation of timestamps across federates implies some constraints (unless you use physical connections). How these constraints are managed depends on whether you choose centralized or decentralized coordination.

    \n

    Centralized Coordination

    \n

    In the centralized mode of coordination (the default), the RTI regulates the advancement of time in each of the federates in order to ensure that the logical time semantics of Lingua Franca is respected. If the p federate above has an event with timestamp t that it wants to react to (it is the earliest event in its event queue), then it needs to get the OK from the RTI to advance its logical time to t. The RTI grants this time advance only when it can assure that p has received all messages that it will ever receive with timestamps t or less.

    \n

    First, note that, by default, logical time on each federate never advances ahead of physical time, as reported by its local physical clock. Consider the consequences for the above connection. Suppose the timestamp of the message sent by s is t. This message cannot be sent before the local clock at s reaches t and also cannot be sent before the RTI grants to s a time advance to t. Since s has no federates upstream of it, the RTI will always grant it such a time advance (in fact, it does not even wait for a response from the RTI).

    \n

    Suppose that the communication latency is L. That is, it takes L time units (in physical time) for a message to traverse the network. Then the p federate will not see the message from s before physical time t + L, where this physical time is measured by the physical clock on s’s host. If that clock differs from the clock on p’s host by E, then p will see the message at physical time t + E + L, as measured by its own clock. Let the value of the after specification (200 msec above) be a. Then the timestamp of the received message is t + a. The relationship between logical and physical times at the receiving end (the p federate), therefore, will depend on the relationship between a and E + L. If, for example, E + L > a, then federate p will lag behind physical time by at least E + L - a.

    \n

    Assume the RTI has granted a time advance to t to federate s. Hence, s is able to send a message with timestamp t. The RTI now cannot grant any time advance to p that is greater than or equal to t + a until the message has been delivered to p. In centralized coordination, all messages flow through the RTI, so the RTI will deliver a Tag Advance Grant (TAG) message to p only after it has delivered the message.

    \n

    If a > E + L, then the existence of this communication does not cause p’s logical time to lag behind physical time. This means that if we were to modify p to have a physical action, the RTI will be able to immediately grant a TAG to p to advance the timestamp of that physical action. However, if a < E + L, then the RTI will delay granting a time advance to p by at least E + L - a. Hence, E + L - a represents an additional latency in the processing of physical actions! This latency could present a problem for meeting deadlines. For this reason, if there are physical actions or deadlines at a federate that receives network messages, it is desirable to have $after$ delays on the connection to that federate larger than any expected E + L. This way, there is no additional latency to processing physical actions at this federate and no additional risk of missing deadlines.

    \n

    If, in addition, the physical clocks on the hosts are allowed to drift with respect to one another, then E can grow without bound, and hence the lag between logical time and physical time in processing events can grow without bound. This is mitigated either by hosts that themselves realize some clock synchronization algorithm, such as NTP or PTP, or by utilizing Lingua Franca’s own built in clock synchronization. If the federates lack physical actions and deadlines, however, then unsynchronized clocks present no semantic problem if you are using centralized coordination. However, because of logical time chases physical time, federates will slow to match the slowest clock of federates upstream of them.

    \n

    With centralized coordination, all messages (except those on physical connections) go through the RTI. This can create a bottleneck and a single point of failure. To avoid this bottleneck, you can use decentralized coordination.

    \n

    Decentralized Coordination

    \n

    The default coordination between mechanisms is centralized, equivalent to specifying the target property:

    \n
      coordination: centralized\n
    \n

    An alternative is decentralized coordination, which extends a technique realized in PTIDES and Google Spanner, a globally distributed database system:

    \n
      coordination: decentralized\n
    \n

    With decentralized coordination, the RTI coordinates startup, shutdown, and clock synchronization, but is otherwise not involved in the execution of the distributed program.

    \n

    In decentralized coordination, each federate and some reactions have a safe-to-process (STP) offset. When one federate communicates with another, it does so directly through a dedicated socket without going through the RTI. Moreover, it does not consult the RTI to advance logical time. Instead, it can advance its logical time to t when its physical clock matches or exceeds t + STP.

    \n

    By default, the STP is zero. An STP of zero is OK for any federate where either every logical connection into the federate has a sufficiently large $after$ clause, or the federate has only one upstream federate sending it messages and it has no local timers or actions. The value of the $after$ delay on each connection must exceed the sum of the clock synchronization error E, a bound L on the network latency, and the time lag on the sender D (the physical time at which it sends the message minus the timestamp of the message). The sender’s time lag D can be enforced by using a $deadline$. For example:

    \n

    $start(DecentralizedTimerAfter)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerAfter.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(\n        f"Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerAfter.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerAfter.lf\n
    \n

    $end(DecentralizedTimerAfter)$

    \n

    This example inherits from the Federated example above.\nIn this example, as long as the messages from federate c arrive at federate p within 10 msec, all messages will be processed in tag order, as with an unfederated program.

    \n

    An alternative to the $after$ delays is to add an STP offset to downstream federates, as in the following example:

    \n

    $start(DecentralizedTimerSTP)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer(STP_offset: time = 10 msec) extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerSTP.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer(STP_offset = 10 msec) extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(\n        "Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerSTP.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerSTP.lf\n
    \n

    $end(DecentralizedTimerSTP)$

    \n

    Here, a parameter named STP_offset (not case sensitive) gives a time value, and the federate waits this specified amount of time (physical time) beyond a logical time t before advancing its logical time to t. In the above example, reactions to the timer events will be delayed by the amount specified by the STP_offset parameter. Just as with the use of $after$, if the STP_offset exceeds the sum of network latency, clock synchronization error, and execution times, then all events will be processed in tag order.

    \n

    Of course, the assumptions about network latency, etc., can be violated in practice. Analogous to a deadline violation, Lingua Franca provides a mechanism for handling such a violation by providing an STP violation handler. The pattern is:

    \n
    reaction(in) {=\n    // User code\n=} STP (0) {=\n    // Error handling code\n=}\n
    \n

    If the tag at which this reaction is to be invoked (the value returned by lf_tag()) exceeds the tag of an incoming message in (the current tag has already advanced beyond the intended tag of in), then the STP violation handler will be invoked instead of the normal reaction. Within the body of the STP handler, the code can access the intended tag of in using in->intended_tag, which has two fields, a timestamp in->intended_tag.time and a microstep in->intended_tag.microstep. The code can then ascertain the severity of the error and act accordingly. For example:

    \n

    $start(DecentralizedTimerAfterHandler)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count from "Federated.lf"\nreactor PrintTimer {\n  timer t(0, 1 sec)\n  input in: int\n  reaction(in) {=\n    lf_print("Received: %d at (%lld, %d)", in->value,\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =} STP(0) {=\n    lf_print("****** STP violation handler invoked at (%lld, %d). "\n        "Intended tag was (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep,\n        in->intended_tag.time - lf_time_start(), in->intended_tag.microstep\n    );\n  =}\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerAfterHandler.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count from "Federated.lf"\nreactor PrintTimer {\n  timer t(0, 1 sec)\n  input inp\n  reaction(inp) {=\n    print(\n        f"Received: {inp.value} "\n        f"at ({lf.time.logical_elapsed()}, {lf.tag().microstep})"\n    )\n  =} STP(0) {=\n    print(\n        "****** STP violation handler invoked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep}). "\n        "Intended tag was "\n        f"({inp.intended_tag.time - lf.time.start()}, {inp.intended_tag.microstep})."\n    )\n  =}\n  reaction(t) {=\n    print(\n        "Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerAfterHandler.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerAfterHandler.lf\n
    \n

    $end(DecentralizedTimerAfterHandler)$

    \n

    For more advanced users, the LF API provides two functions that can be used to dynamically adjust the STP:

    \n
    interval_t lf_get_stp_offset();\nvoid lf_set_stp_offset(interval_t offset);\n
    \n

    Using these functions, however, is a pretty advanced operation.

    \n

    Physical Connections

    \n

    Coordinating the execution of the federates so that timestamps are preserved is tricky. If your application does not require the deterministic execution that results from preserving the timestamps, then you can alternatively specify a physical connection as follows:

    \n
    source.out ~> print.in;\n
    \n

    The tilde specifies that the timestamp of the sender should be discarded. A new timestamp will be assigned at the receiving end based on the local physical clock, much like a physical action. To distinguish it from a physical connection, the normal connection is called a logical connection.

    \n

    There are a number of subtleties with physical connections. One is that if you specify an after clause, for example like this:

    \n
    count.out ~> print.in after 10 msec;\n
    \n

    then what does this mean? At the receiving end, the timestamp assigned to the incoming event will be the current physical time plus 10 msec.

    \n

    Prerequisites for Distributed Execution

    \n

    In the above example, all of the generated programs expect to run on localhost. This is the default. With these defaults, every federate has to run on the same machine as the RTI because localhost is not a host that is visible from other machines on the network. In order to run federates or the RTI on remote machines, you can specify a domain name or IP address for the RTI and/or federates.

    \n

    In order for a federated execution to work, there is some setup required on the machines to be used. First, each machine must be running on ssh server. On a Linux machine, this is typically done with a command like this:

    \n
      sudo systemctl <start|enable> ssh.service\n
    \n

    Enable means to always start the service at startup, whereas start means to just start it this once. On macOS, open System Preferences from the Apple menu and click on the “Sharing” preference panel. Select the checkbox next to “Remote Login” to enable it.

    \n

    It will also be much more convenient if the launcher does not have to enter passwords to gain access to the remote machine. This can be accomplished by installing your public key (typically found in ~/.ssh/id_rsa.pub) in ~/.ssh/authorized_keys on the remote host.

    \n

    Second, the RTI must be installed on the remote machine. See instructions for installation the RTI.

    \n

    Specifying RTI Hosts

    \n

    You can specify a domain name on which the RTI should run as follows:

    \n
    federated reactor DistributedCount at www.example.com {\n  ...\n}\n
    \n

    You can alternatively specify an IP address (either IPv4 or IPv6):

    \n
    federated reactor DistributedCount at 10.0.0.198 { ... }\n
    \n

    By default, the RTI starts a socket server on port 15045, if that port is available, and increments the port number by 1 until it finds an available port. The number of increments is limited by a target-specific number. In the C target, in rti.h, STARTING_PORT defines the number 15045 and PORT_RANGE_LIMIT limits the range of ports attempted (currently 1024).

    \n

    You can also specify a port for the RTI to use as follows:

    \n
    federated reactor DistributedCount at 10.0.0.198:8080 { ... }\n
    \n

    If you specify a specific port, then it will use that port if it is available and fail otherwise. The above changes this to port 8080.

    \n

    Note that if the machine uses DHCP to obtain its address, then the generated code may not work in the future since the address of the machine may change in the future.

    \n

    Address 0.0.0.0: The default host, localhost is used if no address is specified. Using localhost requires that the generated federates run on the local machine. This is ideal for testing. If you use 0.0.0.0, then you are also specifying that the local machine (the one performing the code generation) will be the host, but now the process(es) running on this local machine can establish connections with processes on remote machines. The code generator will determine the IP address of the local machine, and any other hosts that need to communicate with reactors on the local host will use the current IP address of that local host at the time of code generation.

    \n

    Specifying Federate Hosts

    \n

    A federate may be mapped to a particular remote machine using a syntax like this:

    \n
      count = new Count() at user@host:port/path;\n
    \n

    The port is ignored in centralized mode because all communication is routed through the RTI, but in decentralized mode it will specify the port on which a socket server listens for incoming connections from other federates.

    \n

    If any federate has such a remote designator, then a Federation_distribute.sh shell script will be generated. This script will distribute the generated code for the RTI to the remote machine at the specified directory.

    \n

    You can also specify a user name on the remote machine for cases where the username will not match whoever launches the federation:

    \n
    federated reactor DistributedCount at user@10.0.0.198:8080 { ... }\n
    \n

    The general form of the host designation is

    \n
    federated reactor DistributedCount at user@host:port/path { ... }\n
    \n

    where user@, :port, and /path are all optional. The path specifies the directory on the remote machine (relative to the home directory of the user) where the generated code will be put. The host should be an IPv4 address (e.g. 93.184.216.34), IPv6 address (e.g. 2606:2800:220:1:248:1893:25c8:1946), or a domain name (e.g. www.example.com). It can also be localhost or 0.0.0.0. The host can be remote as long as it is accessible from the machine where the programs will be started.

    \n

    If user@ is not given, then it is assumed that the username on the remote host is the same as on the machine that launches the programs. If :port is not given, then it defaults to port 15045. If /path is not given, then ~user/LinguaFrancaRemote will be the root directory on the remote machine.

    \n

    Clock Synchronization

    \n

    Both centralized and decentralized coordination have some reliance on clock synchronization. First, the RTI determines the start time of all federates, and the actually physical start time will differ by the extent that their physical clocks differ. This is particularly problematic if clocks differ by hours or more, which is certainly possible. If the hosts on which you are running run a clock synchronization algorithm, such as NTP or PTP, then you may not need to be concerned about this at all. Windows, Mac, and most versions of Linux, by default, run NTP, which synchronizes their clocks to some remote host. NTP is not particularly precise, however, so clock synchronization error can be hundreds of milliseconds or larger. PTP protocols are much more precise, so if your hosts derive their physical clocks from a PTP implementation, then you probably don’t need to do anything further. Unfortunately, as of this writing, even though almost all networking hardware provides support for PTP, few operating systems utilize it. We expect this to change when people have finally understood the value of precise clock synchronization.

    \n

    If your host is not running any clock synchronization, or if it is running only NTP and your application needs tighter latencies, then Lingua Franca’s own built-in clock synchronization may provide better precision, depending on your network conditions. Like NTP, it realizes a software-only protocol, which are much less precise than hardware-supported protocols such as PTP, but if your hosts are on the same local area network, then network conditions may be such that the performance of LF clock synchronization will be much better than NTP. If your network is equipped with PTP, you will want to disable the clock synchronization in Lingua Franca by specifying in your target properties the following:

    \n
      clock-sync: off\n
    \n

    When a federation is mapped onto multiple machines, then, by default, any federate mapped to a machine that is not the one running the RTI will attempt during startup to synchronize its clock with the one on the machine running the RTI. The determination of whether the federate is running on the same machine is determined by comparing the string that comes after the at clause between the federate and the RTI. If they differ at all, then they will be treated as if the federate is running on a different machine even if it is actually running on the same machine. This default behavior can be obtained by either specifying nothing in the target properties or saying:

    \n
      clock-sync: initial\n
    \n

    This results in clock synchronization being done during startup only. To account for the possibility of your clocks drifting during execution of the program, you can alternatively specify:

    \n
      clock-sync: on\n
    \n

    With this specification, in addition to synchronization during startup, synchronization will be redone periodically during program execution.

    \n

    Clock Synchronization Options

    \n

    A number of options can be specified using the clock-sync-options target parameter. For example:

    \n
      clock-sync-options: {local-federates-on: true, test-offset: 200 msec}\n
    \n

    The supported options are:

    \n
      \n
    • \n

      local-federates-on: Should be true or false. By default, if a federate is mapped to the same host as the RTI (using the at keyword), then clock synchronization is turned off. This assumes that the federate will be using the same clock as the RTI, so there is no point in performing clock synchronization. However, sometimes it is useful to force clock synchronization to be run even in this case, for example to test the performance of clock synchronization. To force clock synchronization on in this case, set this option to true.

      \n
    • \n
    • \n

      test-offset: The value should be a time value with units, e.g. 200 msec. This will establish an artificial fixed offset for each federate’s clock of one plus the federate ID times the time value given. For example, with the value 200 msec, a fixed offset of 200 milliseconds will be set on the clock for federate 0, 400 msec on the clock of federate 1, etc.

      \n
    • \n
    • \n

      period: A time value (with units) that specifies how often runtime clock synchronization will be performed if it is turned on. The default is 5 msec.

      \n
    • \n
    • \n

      attenuation: A positive integer specifying a divisor applied to the estimated clock error during runtime clock synchronization when adjusting the clock offset. The default is 10. Making this number bigger reduces each adjustment to the clock. Making the number equal to 1 means that each round of clock synchronization fully applies its estimated clock synchronization error.

      \n
    • \n
    • \n

      trials: The number of rounds of message exchange with the RTI in each clock synchronization round. This defaults to 10.

      \n
    • \n
    \n
    ","headings":[{"value":"Installation of the RTI","depth":2},{"value":"Minimal Example","depth":2},{"value":"Federation ID","depth":2},{"value":"Coordinated Start","depth":2},{"value":"Coordinated Shutdown","depth":2},{"value":"Communication Between Federates","depth":2},{"value":"Centralized Coordination","depth":2},{"value":"Decentralized Coordination","depth":2},{"value":"Physical Connections","depth":2},{"value":"Prerequisites for Distributed Execution","depth":2},{"value":"Specifying RTI Hosts","depth":2},{"value":"Specifying Federate Hosts","depth":2},{"value":"Clock Synchronization","depth":2},{"value":"Clock Synchronization Options","depth":3}],"frontmatter":{"permalink":"/docs/handbook/distributed-execution","title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Preambles","oneline":"Defining preambles in Lingua Franca.","permalink":"/docs/handbook/preambles"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Termination","oneline":"Terminating a Lingua Franca execution.","permalink":"/docs/handbook/termination"}}}},"pageContext":{"id":"1-distributed-execution","slug":"/docs/handbook/distributed-execution","repoPath":"/packages/documentation/copy/en/topics/Distributed Execution.md","previousID":"d9c76683-1fe5-55e0-b223-8e21c125f9cd","nextID":"ddf59040-674f-5833-a630-a62f39d0106e","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/distributed-execution","result":{"data":{"markdownRemark":{"id":"01e8e05d-d3c6-5a55-a16a-4c1baf83a83c","excerpt":"Federated execution is not supported in $target-language$. $page-showing-target$ NOTE: Distributed execution of Lingua Franca programs is at an early stage of…","html":"
    \n

    Federated execution is not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    NOTE: Distributed execution of Lingua Franca programs is at an early stage of development with many missing capabilities and a rather brittle execution. It is ready for experimentation, but not yet for deployment of serious systems. The capability has been tested on macOS and Linux, and there are no plans currently to support Windows systems.

    \n

    A distributed Lingua Franca program is called a federation. Each reactor within the main reactor is called a federate. The LF compiler generates a separate program for each federate and synthesizes the code for the federates to communicate. The federates can be distributed across networks and eventually will be able to be written in different target languages, although this is not yet supported.

    \n

    In addition to the federates, there is a program called the RTI, for runtime infrastructure, that coordinates startup and shutdown and may, if the coordination is centralized, mediate communication. The RTI needs to be compiled and installed separately on the system before any federation can execute.

    \n

    It is possible to encapsulate federates in Docker containers for deployment.\nSee containerized execution.

    \n

    Installation of the RTI

    \n

    Federated execution requires installation of a separate stand-alone program called the Runtime Infrastructure or RTI. At the current time, the only way to install this is from source files:

    \n
    git clone https://github.com/lf-lang/reactor-c.git\ncd reactor-c/core/federated/RTI/\nmkdir build && cd build\ncmake ../\nmake\nsudo make install\n
    \n

    The above will create a program called RTI and install it at /usr/local/bin/RTI. Once this program is available in your path, you can compile and execute federated Lingua Franca programs using Epoch, VS Code, or the command-line tools. For more details, see the README file.

    \n

    Minimal Example

    \n

    A minimal federated execution is specified by using the $federated$ keyword instead of $main$ for the main federate. An example is given below:

    \n

    $start(Federated)$

    \n
    target C\nreactor Count {\n  output out: int\n  state c: int = 0\n  timer t(0, 1 sec)\n  reaction(t) -> out {=\n    lf_set(out, self->c++);\n  =}\n}\nreactor Print {\n  input in: int\n  reaction(in) {=\n    lf_print("Received: %d at (%lld, %d)", in->value,\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new Print()\n  c.out -> p.in\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Federated.lf\n
    \n
    target Python\nreactor Count {\n  output out\n  state c = 0\n  timer t(0, 1 sec)\n  reaction(t) -> out {=\n    out.set(self.c)\n    self.c += 1\n  =}\n}\nreactor Print {\n  input inp\n  reaction(inp) {=\n    print(\n        f"Received: {inp.value} "\n        f"at ({lf.time.logical_elapsed()}, {lf.tag().microstep})"\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new Print()\n  c.out -> p.inp\n}\n
    \n
    target TypeScript {\n  timeout: 1 msec  // FIXME: This should work with timeout: 0 msec.\n}\nreactor Source {\n  output out: string\n  reaction(startup) -> out {=\n    out = "Hello World!";\n  =}\n}\nreactor Destination {\n  input inp: string\n  reaction(inp) {=\n    console.log("Received: " + inp);\n  =}\n}\nfederated reactor Federated {\n  s = new Source()\n  d = new Destination()\n  s.out -> d.inp\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Federated.lf\n
    \n

    $end(Federated)$

    \n

    The $federated$ keyword tells the code generator that the program is to be split into several distinct programs, one for each top level reactor.

    \n

    When you run the code generator on src/Federated.lf containing the above code, the following three programs will appear:

    \n
    \n
    bin/Federated\nbin/Federated_s\nbin/Federated_d
    \n
    \n
    \n
    bin/Federated\nsrc-gen/Federated/s/Federated_s.py\nsrc-gen/Federated/d/Federated_d.py
    \n
    \n
    \n
    bin/Federated\nsrc-gen/dist/Federated/Federated_s.js\nsrc-gen/dist/Federated/Federated_d.js
    \n
    \n

    The root name, Federated, is the name of the .lf file from which these are generated (and the name of the main reactor, which is required to match if it is specified). The suffixes “_s” and “_d” come from the names of the top-level instances. There will always be one federate for each top-level reactor instance.

    \n

    To run the program, you can simply run bin/Federated, which is a bash script that launches the RTI and two other programs, Federated_s and Federated_d.\nAlternatively, you can manually execute the RTI followed by the two federate programs by starting them on the command line. It is best to use three separate terminal windows (so that outputs from the three programs do not get jumbled together) to execute the following commands:

    \n
    \n
    RTI -n 2\nbin/Federated_s\nbin/Federated_d\n
    \n
    \n
    \n
    RTI -n 2\npython3 src-gen/Federated/s/Federated_s.py\npython3 src-gen/Federated/d/Federated_d.py\n
    \n
    \n
    \n
    RTI -n 2\nnode src-gen/Federated/dist/Federated_s.js\nnode src-gen/Federated/dist/Federated_d.js\n
    \n
    \n

    The -n argument to the RTI specifies that there it should expect two federates to join the federation.

    \n

    Upon running the program, you will see information printed about the starting and ending of the federation, and buried in the output will be this line:

    \n
    \n
    Federate 1: Received: Hello World!
    \n

    The prefix Federate 1 is automatically added by the built-in lf_print function to help disambiguate the outputs from multiple concurrent federates.

    \n
    \n
    \n
    Received: Hello World!
    \n
    \n
    \n
    Received: Hello World!
    \n
    \n

    Federation ID

    \n

    You may have several federations running on the same machine(s) or even several instances of the same federation. In this case, it is necessary to distinguish between the federations. To accomplish this, you can pass a -i or --id parameter to the RTI and its federates with an identifier that is unique to the particular federation. For example,

    \n
    \n
    RTI -n 2 -i myFederation\nbin/Federated_s -i myFederation\nbin/Federated_d -i myFederation\n
    \n
    \n
    \n
    RTI -n 2 -i myFederation\npython3 src-gen/Federated/s/Federated_s.py -i myFederation\npython3 src-gen/Federated/d/Federated_d.py -i myFederation\n
    \n
    \n
    \n
    RTI -n 2 -i myFederation\nnode src-gen/Federated/dist/Federated_s.js -i myFederation\nnode src-gen/Federated/dist/Federated_d.js -i myFederation\n
    \n
    \n

    Each federate must have the same ID as the RTI in order to join the federation.\nThe bash script that executes each of the components of the federation automatically generates a unique federation ID each time you run it.

    \n

    Coordinated Start

    \n

    When the above programs execute, each federate registers with the RTI. When all expected federates have registered, the RTI broadcasts to the federates the logical time at which they should start execution. Hence, all federates start at the same logical time.

    \n

    The starting logical time is determined as follows. When each federate starts executing, it sends its current physical time (drawn from its real-time clock) to the RTI. When the RTI has heard from all the federates, it chooses the largest of these physical times, adds a fixed offset (currently one second), and broadcasts the resulting time to each federate.

    \n

    When a federate receives the starting time from the RTI, if it is running in realtime mode (the default), then it will wait until its local physical clock matches or exceeds that starting time. Thus, to the extent that the machines have synchronized clocks, the federates will all start executing at roughly the same physical time, a physical time close to the starting logical time.

    \n

    Coordinated Shutdown

    \n

    Coordinating the shutdown of a distributed program is discussed in Termination.

    \n

    Communication Between Federates

    \n

    When one federate sends data to another, by default, the timestamp at the receiver will match the timestamp at the sender. You can also specify a logical delay on the communication using the after keyword. For example, if we had instead specified

    \n
    \ts.out -> p.in after 200 msec;\n
    \n

    then the timestamp at the receiving end will be incremented by 200 msec compared to the timestamp at the sender.

    \n

    The preservation of timestamps across federates implies some constraints (unless you use physical connections). How these constraints are managed depends on whether you choose centralized or decentralized coordination.

    \n

    Centralized Coordination

    \n

    In the centralized mode of coordination (the default), the RTI regulates the advancement of time in each of the federates in order to ensure that the logical time semantics of Lingua Franca is respected. If the p federate above has an event with timestamp t that it wants to react to (it is the earliest event in its event queue), then it needs to get the OK from the RTI to advance its logical time to t. The RTI grants this time advance only when it can assure that p has received all messages that it will ever receive with timestamps t or less.

    \n

    First, note that, by default, logical time on each federate never advances ahead of physical time, as reported by its local physical clock. Consider the consequences for the above connection. Suppose the timestamp of the message sent by s is t. This message cannot be sent before the local clock at s reaches t and also cannot be sent before the RTI grants to s a time advance to t. Since s has no federates upstream of it, the RTI will always grant it such a time advance (in fact, it does not even wait for a response from the RTI).

    \n

    Suppose that the communication latency is L. That is, it takes L time units (in physical time) for a message to traverse the network. Then the p federate will not see the message from s before physical time t + L, where this physical time is measured by the physical clock on s’s host. If that clock differs from the clock on p’s host by E, then p will see the message at physical time t + E + L, as measured by its own clock. Let the value of the after specification (200 msec above) be a. Then the timestamp of the received message is t + a. The relationship between logical and physical times at the receiving end (the p federate), therefore, will depend on the relationship between a and E + L. If, for example, E + L > a, then federate p will lag behind physical time by at least E + L - a.

    \n

    Assume the RTI has granted a time advance to t to federate s. Hence, s is able to send a message with timestamp t. The RTI now cannot grant any time advance to p that is greater than or equal to t + a until the message has been delivered to p. In centralized coordination, all messages flow through the RTI, so the RTI will deliver a Tag Advance Grant (TAG) message to p only after it has delivered the message.

    \n

    If a > E + L, then the existence of this communication does not cause p’s logical time to lag behind physical time. This means that if we were to modify p to have a physical action, the RTI will be able to immediately grant a TAG to p to advance the timestamp of that physical action. However, if a < E + L, then the RTI will delay granting a time advance to p by at least E + L - a. Hence, E + L - a represents an additional latency in the processing of physical actions! This latency could present a problem for meeting deadlines. For this reason, if there are physical actions or deadlines at a federate that receives network messages, it is desirable to have $after$ delays on the connection to that federate larger than any expected E + L. This way, there is no additional latency to processing physical actions at this federate and no additional risk of missing deadlines.

    \n

    If, in addition, the physical clocks on the hosts are allowed to drift with respect to one another, then E can grow without bound, and hence the lag between logical time and physical time in processing events can grow without bound. This is mitigated either by hosts that themselves realize some clock synchronization algorithm, such as NTP or PTP, or by utilizing Lingua Franca’s own built in clock synchronization. If the federates lack physical actions and deadlines, however, then unsynchronized clocks present no semantic problem if you are using centralized coordination. However, because of logical time chases physical time, federates will slow to match the slowest clock of federates upstream of them.

    \n

    With centralized coordination, all messages (except those on physical connections) go through the RTI. This can create a bottleneck and a single point of failure. To avoid this bottleneck, you can use decentralized coordination.

    \n

    Decentralized Coordination

    \n

    The default coordination between mechanisms is centralized, equivalent to specifying the target property:

    \n
      coordination: centralized\n
    \n

    An alternative is decentralized coordination, which extends a technique realized in PTIDES and Google Spanner, a globally distributed database system:

    \n
      coordination: decentralized\n
    \n

    With decentralized coordination, the RTI coordinates startup, shutdown, and clock synchronization, but is otherwise not involved in the execution of the distributed program.

    \n

    In decentralized coordination, each federate and some reactions have a safe-to-process (STP) offset. When one federate communicates with another, it does so directly through a dedicated socket without going through the RTI. Moreover, it does not consult the RTI to advance logical time. Instead, it can advance its logical time to t when its physical clock matches or exceeds t + STP.

    \n

    By default, the STP is zero. An STP of zero is OK for any federate where either every logical connection into the federate has a sufficiently large $after$ clause, or the federate has only one upstream federate sending it messages and it has no local timers or actions. The value of the $after$ delay on each connection must exceed the sum of the clock synchronization error E, a bound L on the network latency, and the time lag on the sender D (the physical time at which it sends the message minus the timestamp of the message). The sender’s time lag D can be enforced by using a $deadline$. For example:

    \n

    $start(DecentralizedTimerAfter)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerAfter.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(\n        f"Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerAfter.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerAfter.lf\n
    \n

    $end(DecentralizedTimerAfter)$

    \n

    This example inherits from the Federated example above.\nIn this example, as long as the messages from federate c arrive at federate p within 10 msec, all messages will be processed in tag order, as with an unfederated program.

    \n

    An alternative to the $after$ delays is to add an STP offset to downstream federates, as in the following example:

    \n

    $start(DecentralizedTimerSTP)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer(STP_offset: time = 10 msec) extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerSTP.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count, Print from "Federated.lf"\nreactor PrintTimer(STP_offset = 10 msec) extends Print {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(\n        "Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerSTP.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerSTP.lf\n
    \n

    $end(DecentralizedTimerSTP)$

    \n

    Here, a parameter named STP_offset (not case sensitive) gives a time value, and the federate waits this specified amount of time (physical time) beyond a logical time t before advancing its logical time to t. In the above example, reactions to the timer events will be delayed by the amount specified by the STP_offset parameter. Just as with the use of $after$, if the STP_offset exceeds the sum of network latency, clock synchronization error, and execution times, then all events will be processed in tag order.

    \n

    Of course, the assumptions about network latency, etc., can be violated in practice. Analogous to a deadline violation, Lingua Franca provides a mechanism for handling such a violation by providing an STP violation handler. The pattern is:

    \n
    reaction(in) {=\n    // User code\n=} STP (0) {=\n    // Error handling code\n=}\n
    \n

    If the tag at which this reaction is to be invoked (the value returned by lf_tag()) exceeds the tag of an incoming message in (the current tag has already advanced beyond the intended tag of in), then the STP violation handler will be invoked instead of the normal reaction. Within the body of the STP handler, the code can access the intended tag of in using in->intended_tag, which has two fields, a timestamp in->intended_tag.time and a microstep in->intended_tag.microstep. The code can then ascertain the severity of the error and act accordingly. For example:

    \n

    $start(DecentralizedTimerAfterHandler)$

    \n
    target C {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count from "Federated.lf"\nreactor PrintTimer {\n  timer t(0, 1 sec)\n  input in: int\n  reaction(in) {=\n    lf_print("Received: %d at (%lld, %d)", in->value,\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =} STP(0) {=\n    lf_print("****** STP violation handler invoked at (%lld, %d). "\n        "Intended tag was (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep,\n        in->intended_tag.time - lf_time_start(), in->intended_tag.microstep\n    );\n  =}\n  reaction(t) {=\n    lf_print("Timer ticked at (%lld, %d).",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.in after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/DecentralizedTimerAfterHandler.lf\n
    \n
    target Python {\n  timeout: 5 sec,\n  coordination: decentralized\n}\nimport Count from "Federated.lf"\nreactor PrintTimer {\n  timer t(0, 1 sec)\n  input inp\n  reaction(inp) {=\n    print(\n        f"Received: {inp.value} "\n        f"at ({lf.time.logical_elapsed()}, {lf.tag().microstep})"\n    )\n  =} STP(0) {=\n    print(\n        "****** STP violation handler invoked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep}). "\n        "Intended tag was "\n        f"({inp.intended_tag.time - lf.time.start()}, {inp.intended_tag.microstep})."\n    )\n  =}\n  reaction(t) {=\n    print(\n        "Timer ticked at "\n        f"({lf.time.logical_elapsed()}, {lf.tag().microstep})."\n    )\n  =}\n}\nfederated reactor {\n  c = new Count()\n  p = new PrintTimer()\n  c.out -> p.inp after 10 msec\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/DecentralizedTimerAfterHandler.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/DecentralizedTimerAfterHandler.lf\n
    \n

    $end(DecentralizedTimerAfterHandler)$

    \n

    For more advanced users, the LF API provides two functions that can be used to dynamically adjust the STP:

    \n
    interval_t lf_get_stp_offset();\nvoid lf_set_stp_offset(interval_t offset);\n
    \n

    Using these functions, however, is a pretty advanced operation.

    \n

    Physical Connections

    \n

    Coordinating the execution of the federates so that timestamps are preserved is tricky. If your application does not require the deterministic execution that results from preserving the timestamps, then you can alternatively specify a physical connection as follows:

    \n
    source.out ~> print.in;\n
    \n

    The tilde specifies that the timestamp of the sender should be discarded. A new timestamp will be assigned at the receiving end based on the local physical clock, much like a physical action. To distinguish it from a physical connection, the normal connection is called a logical connection.

    \n

    There are a number of subtleties with physical connections. One is that if you specify an after clause, for example like this:

    \n
    count.out ~> print.in after 10 msec;\n
    \n

    then what does this mean? At the receiving end, the timestamp assigned to the incoming event will be the current physical time plus 10 msec.

    \n

    Prerequisites for Distributed Execution

    \n

    In the above example, all of the generated programs expect to run on localhost. This is the default. With these defaults, every federate has to run on the same machine as the RTI because localhost is not a host that is visible from other machines on the network. In order to run federates or the RTI on remote machines, you can specify a domain name or IP address for the RTI and/or federates.

    \n

    In order for a federated execution to work, there is some setup required on the machines to be used. First, each machine must be running on ssh server. On a Linux machine, this is typically done with a command like this:

    \n
      sudo systemctl <start|enable> ssh.service\n
    \n

    Enable means to always start the service at startup, whereas start means to just start it this once. On macOS, open System Preferences from the Apple menu and click on the “Sharing” preference panel. Select the checkbox next to “Remote Login” to enable it.

    \n

    It will also be much more convenient if the launcher does not have to enter passwords to gain access to the remote machine. This can be accomplished by installing your public key (typically found in ~/.ssh/id_rsa.pub) in ~/.ssh/authorized_keys on the remote host.

    \n

    Second, the RTI must be installed on the remote machine. See instructions for installation the RTI.

    \n

    Specifying RTI Hosts

    \n

    You can specify a domain name on which the RTI should run as follows:

    \n
    federated reactor DistributedCount at www.example.com {\n  ...\n}\n
    \n

    You can alternatively specify an IP address (either IPv4 or IPv6):

    \n
    federated reactor DistributedCount at 10.0.0.198 { ... }\n
    \n

    By default, the RTI starts a socket server on port 15045, if that port is available, and increments the port number by 1 until it finds an available port. The number of increments is limited by a target-specific number. In the C target, in rti.h, STARTING_PORT defines the number 15045 and PORT_RANGE_LIMIT limits the range of ports attempted (currently 1024).

    \n

    You can also specify a port for the RTI to use as follows:

    \n
    federated reactor DistributedCount at 10.0.0.198:8080 { ... }\n
    \n

    If you specify a specific port, then it will use that port if it is available and fail otherwise. The above changes this to port 8080.

    \n

    Note that if the machine uses DHCP to obtain its address, then the generated code may not work in the future since the address of the machine may change in the future.

    \n

    Address 0.0.0.0: The default host, localhost is used if no address is specified. Using localhost requires that the generated federates run on the local machine. This is ideal for testing. If you use 0.0.0.0, then you are also specifying that the local machine (the one performing the code generation) will be the host, but now the process(es) running on this local machine can establish connections with processes on remote machines. The code generator will determine the IP address of the local machine, and any other hosts that need to communicate with reactors on the local host will use the current IP address of that local host at the time of code generation.

    \n

    Specifying Federate Hosts

    \n

    A federate may be mapped to a particular remote machine using a syntax like this:

    \n
      count = new Count() at user@host:port/path;\n
    \n

    The port is ignored in centralized mode because all communication is routed through the RTI, but in decentralized mode it will specify the port on which a socket server listens for incoming connections from other federates.

    \n

    If any federate has such a remote designator, then a Federation_distribute.sh shell script will be generated. This script will distribute the generated code for the RTI to the remote machine at the specified directory.

    \n

    You can also specify a user name on the remote machine for cases where the username will not match whoever launches the federation:

    \n
    federated reactor DistributedCount at user@10.0.0.198:8080 { ... }\n
    \n

    The general form of the host designation is

    \n
    federated reactor DistributedCount at user@host:port/path { ... }\n
    \n

    where user@, :port, and /path are all optional. The path specifies the directory on the remote machine (relative to the home directory of the user) where the generated code will be put. The host should be an IPv4 address (e.g. 93.184.216.34), IPv6 address (e.g. 2606:2800:220:1:248:1893:25c8:1946), or a domain name (e.g. www.example.com). It can also be localhost or 0.0.0.0. The host can be remote as long as it is accessible from the machine where the programs will be started.

    \n

    If user@ is not given, then it is assumed that the username on the remote host is the same as on the machine that launches the programs. If :port is not given, then it defaults to port 15045. If /path is not given, then ~user/LinguaFrancaRemote will be the root directory on the remote machine.

    \n

    Clock Synchronization

    \n

    Both centralized and decentralized coordination have some reliance on clock synchronization. First, the RTI determines the start time of all federates, and the actually physical start time will differ by the extent that their physical clocks differ. This is particularly problematic if clocks differ by hours or more, which is certainly possible. If the hosts on which you are running run a clock synchronization algorithm, such as NTP or PTP, then you may not need to be concerned about this at all. Windows, Mac, and most versions of Linux, by default, run NTP, which synchronizes their clocks to some remote host. NTP is not particularly precise, however, so clock synchronization error can be hundreds of milliseconds or larger. PTP protocols are much more precise, so if your hosts derive their physical clocks from a PTP implementation, then you probably don’t need to do anything further. Unfortunately, as of this writing, even though almost all networking hardware provides support for PTP, few operating systems utilize it. We expect this to change when people have finally understood the value of precise clock synchronization.

    \n

    If your host is not running any clock synchronization, or if it is running only NTP and your application needs tighter latencies, then Lingua Franca’s own built-in clock synchronization may provide better precision, depending on your network conditions. Like NTP, it realizes a software-only protocol, which are much less precise than hardware-supported protocols such as PTP, but if your hosts are on the same local area network, then network conditions may be such that the performance of LF clock synchronization will be much better than NTP. If your network is equipped with PTP, you will want to disable the clock synchronization in Lingua Franca by specifying in your target properties the following:

    \n
      clock-sync: off\n
    \n

    When a federation is mapped onto multiple machines, then, by default, any federate mapped to a machine that is not the one running the RTI will attempt during startup to synchronize its clock with the one on the machine running the RTI. The determination of whether the federate is running on the same machine is determined by comparing the string that comes after the at clause between the federate and the RTI. If they differ at all, then they will be treated as if the federate is running on a different machine even if it is actually running on the same machine. This default behavior can be obtained by either specifying nothing in the target properties or saying:

    \n
      clock-sync: initial\n
    \n

    This results in clock synchronization being done during startup only. To account for the possibility of your clocks drifting during execution of the program, you can alternatively specify:

    \n
      clock-sync: on\n
    \n

    With this specification, in addition to synchronization during startup, synchronization will be redone periodically during program execution.

    \n

    Clock Synchronization Options

    \n

    A number of options can be specified using the clock-sync-options target parameter. For example:

    \n
      clock-sync-options: {local-federates-on: true, test-offset: 200 msec}\n
    \n

    The supported options are:

    \n
      \n
    • \n

      local-federates-on: Should be true or false. By default, if a federate is mapped to the same host as the RTI (using the at keyword), then clock synchronization is turned off. This assumes that the federate will be using the same clock as the RTI, so there is no point in performing clock synchronization. However, sometimes it is useful to force clock synchronization to be run even in this case, for example to test the performance of clock synchronization. To force clock synchronization on in this case, set this option to true.

      \n
    • \n
    • \n

      test-offset: The value should be a time value with units, e.g. 200 msec. This will establish an artificial fixed offset for each federate’s clock of one plus the federate ID times the time value given. For example, with the value 200 msec, a fixed offset of 200 milliseconds will be set on the clock for federate 0, 400 msec on the clock of federate 1, etc.

      \n
    • \n
    • \n

      period: A time value (with units) that specifies how often runtime clock synchronization will be performed if it is turned on. The default is 5 msec.

      \n
    • \n
    • \n

      attenuation: A positive integer specifying a divisor applied to the estimated clock error during runtime clock synchronization when adjusting the clock offset. The default is 10. Making this number bigger reduces each adjustment to the clock. Making the number equal to 1 means that each round of clock synchronization fully applies its estimated clock synchronization error.

      \n
    • \n
    • \n

      trials: The number of rounds of message exchange with the RTI in each clock synchronization round. This defaults to 10.

      \n
    • \n
    \n
    ","headings":[{"value":"Installation of the RTI","depth":2},{"value":"Minimal Example","depth":2},{"value":"Federation ID","depth":2},{"value":"Coordinated Start","depth":2},{"value":"Coordinated Shutdown","depth":2},{"value":"Communication Between Federates","depth":2},{"value":"Centralized Coordination","depth":2},{"value":"Decentralized Coordination","depth":2},{"value":"Physical Connections","depth":2},{"value":"Prerequisites for Distributed Execution","depth":2},{"value":"Specifying RTI Hosts","depth":2},{"value":"Specifying Federate Hosts","depth":2},{"value":"Clock Synchronization","depth":2},{"value":"Clock Synchronization Options","depth":3}],"frontmatter":{"permalink":"/docs/handbook/distributed-execution","title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Preambles","oneline":"Defining preambles in Lingua Franca.","permalink":"/docs/handbook/preambles"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Termination","oneline":"Terminating a Lingua Franca execution.","permalink":"/docs/handbook/termination"}}}},"pageContext":{"id":"1-distributed-execution","slug":"/docs/handbook/distributed-execution","repoPath":"/packages/documentation/copy/en/topics/Distributed Execution.md","previousID":"d9c76683-1fe5-55e0-b223-8e21c125f9cd","nextID":"ddf59040-674f-5833-a630-a62f39d0106e","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/eclipse-oomph/page-data.json b/page-data/docs/handbook/eclipse-oomph/page-data.json index 24ea90e7d..6de7be80e 100644 --- a/page-data/docs/handbook/eclipse-oomph/page-data.json +++ b/page-data/docs/handbook/eclipse-oomph/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/eclipse-oomph","result":{"data":{"markdownRemark":{"id":"d47b3353-7fbd-5138-a517-921fb5a960c3","excerpt":"Prerequisites Java 17 (download from Oracle) Each target language may have additional requirements. See the Target Language Details page and select your target…","html":"

    Prerequisites

    \n\n

    Note: Eclipse does not currently support Kotlin, the language used for some of the target code generators. If you plan to develop Kotlin code, we recommend using IntelliJ instead of Eclipse.

    \n

    Oomph Setup

    \n

    The Eclipse setup with Oomph allows to automatically create a fully configured Eclipse IDE for the development of Lingua Franca. Note that we recommend installing a new instance of Eclipse even if you already have one for other purposes. There is no problem having multiple Eclipse installations on the same machine, and separate installations help prevent cross-project problems.

    \n
      \n
    1. If you have previously installed Eclipse and you want to start fresh, then remove or move a hidden directory called .p2 in your home directory. I do this:
    2. \n
    \n
    mv ~/.p2 ~/.p2.bak\n
    \n
      \n
    1. \n

      Go to the Eclipse download site (https://www.eclipse.org/downloads/index.php) and download the Eclipse Installer for your platform. The site does not advertise that it ships the Oomph Eclipse Installer but downloading Eclipse with the orange download button will give you the installer.
      \nYou can skip this step if you already have the installer available on your system.

      \n
    2. \n
    3. \n

      Starting the installer for the first time will open a window that looks like the following (if you have previously followed these steps, skip to step 4):
      \n\n \n \n

      \n
    4. \n
    5. \n

      Next, we need to register the Lingua Franca specific setup in Oomph (only the first time you use the installer). Click the green Plus button at the top right corner. Select “GitHub Projects” as catalog and paste the following URL into the “Resource URI” field:\nhttps://raw.githubusercontent.com/icyphy/lingua-franca/master/oomph/LinguaFranca.setup.\nThen press OK.\nNOTE: to check out another branch instead, adjust the URL above accordingly. For instance, in order to install the setup from foo-bar branch, change the URL to https://raw.githubusercontent.com/icyphy/lingua-franca/foo-bar/oomph/LinguaFranca.setup. Also, in the subsequent screen in the wizard, select the particular branch of interest instead of default, which is master.

      \n
    6. \n
    7. \n

      Now Oomph lists the Lingua Franca setup in the ”” directory of the “GitHub Projects” catalog. Check the Lingua Franca entry. A new entry for Lingua Franca will appear in the table at the bottom of the window. Select Lingua Franca and click Next.
      \n\n \n \n

      \n
    8. \n
    9. \n

      Now you can further configure where and how your development Eclipse should be created. Check “Show all variables” to enable all possible configuration options. You can hover over the field labels to get a more detailed explanation of their effects.

      \n
    10. \n
    \n
      \n
    • If you already have cloned the LF repository and you want Eclipse to use this location instead of cloning it into the new IDE environment, you should adjust the “Git clone location rule”.
    • \n
    • Preferably, you have a GitHub account with an SSH key uploaded to GitHub. Otherwise, you should adjust the “Lingua Franca GitHub repository” entry to use the https option in the drop-down menu. See adding an SSH key to your GitHub account.
    • \n
    • If the “JRE 17 location” is empty, you need to install and/or locate a JDK that has at least version 17.\n\n \n \n
    • \n
    \n
      \n
    1. \n

      Click Next to get a summary of what will happen during installation. Click Finish to start.

      \n
    2. \n
    3. \n

      Once the basic installation is complete, your new Eclipse will start. If it fails to clone the GitHub repository, then you should use the back button in the Oomph dialog and change the way you are accessing the repo (ssh or https). See above.
      \nThe setup may also fail to clone the repository via SHH if Eclipse cannot find the private ssh key that matches the public key you uploaded to GitHub. You can configure the location of your private key in Eclipse as follows. In the Eclipse IDE, click the menu entry Window -> Preferences (on Mac Apple-Menu -> Preferences) and navigate to General -> Network Connections -> SSH2 in the tree view on the left and configure the SSH home directory and key names according to your computer. After the repo has been cloned, you can safely close the initial Oomph dialog (if not dismissed automatically). You will see a Welcome page that you can close.

      \n
    4. \n
    5. \n

      In the new Eclipse, it may automatically start building the project, or it may pop up an “Eclipse Updater” dialog. If neither happens, you can click the button with the yellow and blue cycling arrows in the status bar at the bottom. Oomph will perform various operations to configure the Eclipse environment, including the initial code generation for the LF language. This may take some time. Wait until the setup is finished.

      \n
    6. \n
    7. \n

      If you get compile errors, make sure Eclipse is using Java 17. If you skipped the first step above (removing your ~/.p2 directory), then you may have legacy configuration information that causes Eclipse to mysteriously use an earlier version of Java. Lingua Franca requires Java 17, and will get compiler errors if it uses an earlier version. To fix this, go to the menu Project->Properties and select Java Build Path. Remove the entry for JRE System Library [JRE for JavaSE-8] (or similar). Choose Add Library on the right, and choose JRE System Library. You should now be able to choose Workspace default JRE (JRE for JavaSE-17). A resulting rebuild should then compile correctly.

      \n
    8. \n
    9. \n

      When the setup dialog is closed, your LF development IDE is ready. Probably, Eclipse is still compiling some code but when this is finished as well, all error markers on the project should have disappeared. Now, you can start a runtime Eclipse to test the actual Lingua Franca end-user IDE. In the toolbar, click on the small arrow next to the green Start button. There may already be an entry named “Launch Runtime Eclipse”, but probably not. To create it, click on “Run Configurations…“. Expand the “Eclipse Application” entry, select “Launch Runtime Eclipse”, as follows:

      \n
    10. \n
    \n

    \n \n \n

    \n\n
  • \n

    Next, we need to register the Lingua Franca specific setup in Oomph (only the first time you use the installer). Click the green Plus button at the top right corner. Select “GitHub Projects” as catalog and paste the following URL into the “Resource URI” field:\nhttps://raw.githubusercontent.com/icyphy/lingua-franca/master/oomph/LinguaFranca.setup.\nThen press OK.\nNOTE: to check out another branch instead, adjust the URL above accordingly. For instance, in order to install the setup from foo-bar branch, change the URL to https://raw.githubusercontent.com/icyphy/lingua-franca/foo-bar/oomph/LinguaFranca.setup. Also, in the subsequent screen in the wizard, select the particular branch of interest instead of default, which is master.

    \n
  • \n
  • \n

    Now Oomph lists the Lingua Franca setup in the ”” directory of the “GitHub Projects” catalog. Check the Lingua Franca entry. A new entry for Lingua Franca will appear in the table at the bottom of the window. Select Lingua Franca and click Next.
    \n\n \n \n

    \n
  • \n
  • \n

    Now you can further configure where and how your development Eclipse should be created. Check “Show all variables” to enable all possible configuration options. You can hover over the field labels to get a more detailed explanation of their effects.

    \n
  • \n\n
      \n
    • If you already have cloned the LF repository and you want Eclipse to use this location instead of cloning it into the new IDE environment, you should adjust the “Git clone location rule”.
    • \n
    • Preferably, you have a GitHub account with an SSH key uploaded to GitHub. Otherwise, you should adjust the “Lingua Franca GitHub repository” entry to use the https option in the drop-down menu. See adding an SSH key to your GitHub account.
    • \n
    • If the “JRE 17 location” is empty, you need to install and/or locate a JDK that has at least version 17.\n\n \n \n
    • \n
    \n
      \n
    1. \n

      Click Next to get a summary of what will happen during installation. Click Finish to start.

      \n
    2. \n
    3. \n

      Once the basic installation is complete, your new Eclipse will start. If it fails to clone the GitHub repository, then you should use the back button in the Oomph dialog and change the way you are accessing the repo (ssh or https). See above.
      \nThe setup may also fail to clone the repository via SHH if Eclipse cannot find the private ssh key that matches the public key you uploaded to GitHub. You can configure the location of your private key in Eclipse as follows. In the Eclipse IDE, click the menu entry Window -> Preferences (on Mac Apple-Menu -> Preferences) and navigate to General -> Network Connections -> SSH2 in the tree view on the left and configure the SSH home directory and key names according to your computer. After the repo has been cloned, you can safely close the initial Oomph dialog (if not dismissed automatically). You will see a Welcome page that you can close.

      \n
    4. \n
    5. \n

      In the new Eclipse, it may automatically start building the project, or it may pop up an “Eclipse Updater” dialog. If neither happens, you can click the button with the yellow and blue cycling arrows in the status bar at the bottom. Oomph will perform various operations to configure the Eclipse environment, including the initial code generation for the LF language. This may take some time. Wait until the setup is finished.

      \n
    6. \n
    7. \n

      If you get compile errors, make sure Eclipse is using Java 17. If you skipped the first step above (removing your ~/.p2 directory), then you may have legacy configuration information that causes Eclipse to mysteriously use an earlier version of Java. Lingua Franca requires Java 17, and will get compiler errors if it uses an earlier version. To fix this, go to the menu Project->Properties and select Java Build Path. Remove the entry for JRE System Library [JRE for JavaSE-8] (or similar). Choose Add Library on the right, and choose JRE System Library. You should now be able to choose Workspace default JRE (JRE for JavaSE-17). A resulting rebuild should then compile correctly.

      \n
    8. \n
    9. \n

      When the setup dialog is closed, your LF development IDE is ready. Probably, Eclipse is still compiling some code but when this is finished as well, all error markers on the project should have disappeared. Now, you can start a runtime Eclipse to test the actual Lingua Franca end-user IDE. In the toolbar, click on the small arrow next to the green Start button. There may already be an entry named “Launch Runtime Eclipse”, but probably not. To create it, click on “Run Configurations…“. Expand the “Eclipse Application” entry, select “Launch Runtime Eclipse”, as follows:

      \n
    10. \n
    \n

    \n . Single-quoted literals must be exactly one character long —even in Python.\n

  • Boolean literals: true, false, True, False. The latter two are there for Python.
  • \n\n\n
  • \n

    Parameter references, which are simple identifiers (e.g. foo). Any identifier in expression position must refer to a parameter of the enclosing reactor.

    \n
  • \n
  • \n

    Time values, e.g. 1 msec or 10 seconds. The syntax of time values is integer time_unit, where time_unit is one of the following:

    \n
      \n
    • nsec or ns: nanoseconds
    • \n
    • usec or us: microseconds
    • \n
    • msec or ms: milliseconds
    • \n
    • sec, second, or s: seconds
    • \n
    • minute or min: 60 seconds
    • \n
    • hour: 60 minutes
    • \n
    • day: 24 hours
    • \n
    • week: 7 days
    • \n
    \n

    Each of these units also support a plural version (e.g., nsecs, minutes, and days), which means the same thing.

    \n

    The time value 0 need not be given a unit, but for all other values, the unit is required.

    \n

    Time values are compatible with the time type.

    \n
  • \n
  • \n

    Escaped target-language expression, e.g. {= foo() =}. This syntax is used to write any expression which does not fall into one of the other forms described here. The contents are not parsed and are used verbatim in the generated file.

    \n
  • \n\n
    \n

    For instance, to have a 2-dimensional array as a parameter in C:

    \n
    reactor Foo(param:{= int[][] =}({= { {1}, {2} } =})) {\n  ...\n}\n
    \n

    Both int[][] and { {1}, {2} } are C fragments here, not LF.

    \n
    \n
    \n

    For instance, to assign a 2-dimensional list as an initial value to a parameter\nin the Python target:

    \n
    reactor Foo(param({= ((1, 2, 3), (4, 5, 6)) =})) {\n  ...\n}\n
    \n
    \n

    Collections

    \n
    \n

    To avoid the awkwardness of using the code delimiters {= ... =}, Lingua Franca supports initialization of simple arrays and similar structures. The interpretation is slightly different in each target language.

    \n
    \n
    \n

    In C, a parameter or state may be given an array value as in the following example:

    \n
    reactor Foo(param: double[] = {0.0, 1.0, 2.0}) {\n  reaction(startup) {=\n    printf("%f %f %f\\n", self->param[0], self->param[1], self->param[2]);\n  =}\n}\n
    \n

    The parameter named param will become an array of length three. When instantiating this reactor, the default parameter value can be overridden using a similar syntax:

    \n
    main reactor {\n  f = new Foo(param = {3.3, 4.4, 5.5});\n}\n
    \n

    See the Target Language Details for details and alternative syntaxes.

    \n
    \n
    \n

    In C++, initial values for a parameter or state can be used to pass arguments to a constructor, as in the following example:

    \n
      state x: int[](2, 0);\n
    \n

    Here, the type int[] is translated by the code generator into std::vector and the (2, 0) to constructor arguments, as in new std::vector(2,0), which creates a vector of length 2 filled with elements with value 0. See the Target Language Details for details and alternative syntaxes.

    \n
    \n
    \n

    In Python, a parameter or state variable may be assigned a list expression as its initial value, as in the following example:

    \n
    reactor Foo(param = {= [1, 2, 3] =}) {\n  state x = {= [1, 2, 3] =}\n  ...\n}\n
    \n

    The param parameter and x state variable become Python lists.\nTheir elements may be accessed as arrays, for example self.x[i], where i is an array index.

    \n

    The parameter may be overridden with a different list at instantiation:

    \n
    main reactor {\n  f = new Foo(param = {= [3, 4, 5, 6]} )\n}\n
    \n

    See the Target Language Details for more details.

    \n
    \n
    \n

    In TypeScript, a parameter or state variable may be assigned an array expression as its initial value, as in the following example:

    \n
    reactor Foo(param:{=Array<number>=}({= [1, 2, 3] =})) {\n  state x:{=Array<number>=} = {= [0.1, 0.2, 0.3] =}\n}\n
    \n

    See the TypeScript reactor documentation for details and alternative syntaxes.

    \n
    \n
    \n

    FIXME: Rust

    \n
    ","headings":[{"value":"Basic expressions","depth":2},{"value":"Collections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/expressions","title":"Expressions","oneline":"Expressions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Target Language Details","oneline":"Detailed reference for each target langauge.","permalink":"/docs/handbook/target-language-details"}}}},"pageContext":{"id":"3-expressions","slug":"/docs/handbook/expressions","repoPath":"/packages/documentation/copy/en/reference/Expressions.md","nextID":"15e2a8e5-dd68-55e9-839f-18c6d342e73c","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/expressions","result":{"data":{"markdownRemark":{"id":"5002de77-4c7a-5740-bedf-9efa09e5ede6","excerpt":"$page-showing-target$ A subset of LF syntax is used to write expressions, which represent values in the target language. Expressions are used to initialize…","html":"

    $page-showing-target$

    \n

    A subset of LF syntax is used to write expressions, which represent values in the target language. Expressions are used to initialize state variable and to give values to parameters. Arbitrary expressions in the target language can always be given within delimiters {= ... =}, but simple forms do not require the delimiters. These simple forms are documented here.

    \n

    Basic expressions

    \n

    The most basic expression forms, which are supported by all target languages, are the following:

    \n
      \n
    • \n

      Literals:

      \n
        \n
      • Numeric literals, e.g. 1, -120, 1.5, 3.14e10. Note that the sign, if any, is part of the literal and must not be separated by whitespace.
      • \n
      • String literals, e.g. \"abcd\". String literals always use double-quotes, even in languages which support other forms (like Python).
      • \n
      • Character literals. e.g. 'a'. Single-quoted literals must be exactly one character long —even in Python.
      • \n
      • Boolean literals: true, false, True, False. The latter two are there for Python.
      • \n
      \n
    • \n
    • \n

      Parameter references, which are simple identifiers (e.g. foo). Any identifier in expression position must refer to a parameter of the enclosing reactor.

      \n
    • \n
    • \n

      Time values, e.g. 1 msec or 10 seconds. The syntax of time values is integer time_unit, where time_unit is one of the following:

      \n
        \n
      • nsec or ns: nanoseconds
      • \n
      • usec or us: microseconds
      • \n
      • msec or ms: milliseconds
      • \n
      • sec, second, or s: seconds
      • \n
      • minute or min: 60 seconds
      • \n
      • hour: 60 minutes
      • \n
      • day: 24 hours
      • \n
      • week: 7 days
      • \n
      \n

      Each of these units also support a plural version (e.g., nsecs, minutes, and days), which means the same thing.

      \n

      The time value 0 need not be given a unit, but for all other values, the unit is required.

      \n

      Time values are compatible with the time type.

      \n
    • \n
    • \n

      Escaped target-language expression, e.g. {= foo() =}. This syntax is used to write any expression which does not fall into one of the other forms described here. The contents are not parsed and are used verbatim in the generated file.

      \n
    • \n
    \n
    \n

    For instance, to have a 2-dimensional array as a parameter in C:

    \n
    reactor Foo(param:{= int[][] =}({= { {1}, {2} } =})) {\n  ...\n}\n
    \n

    Both int[][] and { {1}, {2} } are C fragments here, not LF.

    \n
    \n
    \n

    For instance, to assign a 2-dimensional list as an initial value to a parameter\nin the Python target:

    \n
    reactor Foo(param({= ((1, 2, 3), (4, 5, 6)) =})) {\n  ...\n}\n
    \n
    \n

    Collections

    \n
    \n

    To avoid the awkwardness of using the code delimiters {= ... =}, Lingua Franca supports initialization of simple arrays and similar structures. The interpretation is slightly different in each target language.

    \n
    \n
    \n

    In C, a parameter or state may be given an array value as in the following example:

    \n
    reactor Foo(param: double[] = {0.0, 1.0, 2.0}) {\n  reaction(startup) {=\n    printf("%f %f %f\\n", self->param[0], self->param[1], self->param[2]);\n  =}\n}\n
    \n

    The parameter named param will become an array of length three. When instantiating this reactor, the default parameter value can be overridden using a similar syntax:

    \n
    main reactor {\n  f = new Foo(param = {3.3, 4.4, 5.5});\n}\n
    \n

    See the Target Language Details for details and alternative syntaxes.

    \n
    \n
    \n

    In C++, initial values for a parameter or state can be used to pass arguments to a constructor, as in the following example:

    \n
      state x: int[](2, 0);\n
    \n

    Here, the type int[] is translated by the code generator into std::vector and the (2, 0) to constructor arguments, as in new std::vector(2,0), which creates a vector of length 2 filled with elements with value 0. See the Target Language Details for details and alternative syntaxes.

    \n
    \n
    \n

    In Python, a parameter or state variable may be assigned a list expression as its initial value, as in the following example:

    \n
    reactor Foo(param = {= [1, 2, 3] =}) {\n  state x = {= [1, 2, 3] =}\n  ...\n}\n
    \n

    The param parameter and x state variable become Python lists.\nTheir elements may be accessed as arrays, for example self.x[i], where i is an array index.

    \n

    The parameter may be overridden with a different list at instantiation:

    \n
    main reactor {\n  f = new Foo(param = {= [3, 4, 5, 6]} )\n}\n
    \n

    See the Target Language Details for more details.

    \n
    \n
    \n

    In TypeScript, a parameter or state variable may be assigned an array expression as its initial value, as in the following example:

    \n
    reactor Foo(param:{=Array<number>=}({= [1, 2, 3] =})) {\n  state x:{=Array<number>=} = {= [0.1, 0.2, 0.3] =}\n}\n
    \n

    See the TypeScript reactor documentation for details and alternative syntaxes.

    \n
    \n
    \n

    FIXME: Rust

    \n
    ","headings":[{"value":"Basic expressions","depth":2},{"value":"Collections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/expressions","title":"Expressions","oneline":"Expressions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Target Language Details","oneline":"Detailed reference for each target langauge.","permalink":"/docs/handbook/target-language-details"}}}},"pageContext":{"id":"3-expressions","slug":"/docs/handbook/expressions","repoPath":"/packages/documentation/copy/en/reference/Expressions.md","nextID":"15e2a8e5-dd68-55e9-839f-18c6d342e73c","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/extending-reactors/page-data.json b/page-data/docs/handbook/extending-reactors/page-data.json index 85944af91..5a186d783 100644 --- a/page-data/docs/handbook/extending-reactors/page-data.json +++ b/page-data/docs/handbook/extending-reactors/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/extending-reactors","result":{"data":{"markdownRemark":{"id":"2efb41b1-0afb-5eab-9d59-7f064a8b778e","excerpt":"$page-showing-target$ Extending a Base Reactor The Cpp target does not yet support extending reactors. Lingua Franca supports defining a reactor class as an…","html":"

    $page-showing-target$

    \n

    Extending a Base Reactor

    \n
    \n

    The Cpp target does not yet support extending reactors.

    \n
    \n
    \n

    Lingua Franca supports defining a reactor class as an extension (or subclass), as in the following example:

    \n

    $start(Extends)$

    \n
    target C;\nreactor A {\n  input a:int;\n  output out:int;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n}\nreactor B extends A {\n  input b:int;\n  reaction(a, b) -> out {=\n    lf_set(out, a->value + b->value);\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Extends.lf\n// the cpp target currently does not support reactor extends\n
    \n
    target Python;\nreactor A {\n  input a;\n  output out;\n  reaction(a) -> out {=\n    out.set(a.value)\n  =}\n}\nreactor B extends A {\n  input b;\n  reaction(a, b) -> out {=\n    out.set(a.value + b.value)\n  =}\n}\n
    \n
    target TypeScript\nreactor A {\n  input a:number\n  output out:number\n  reaction(a) -> out {=\n    out = a\n  =}\n}\nreactor B extends A {\n  input b:number\n  reaction(a, b) -> out {=\n    out = a + b\n  =}\n}\n
    \n
    target Rust;\nreactor A {\n  input a:u32;\n  output out:u32;\n  reaction(a) -> out {=\n    ctx.set(out, ctx.get(a).unwrap());\n  =}\n}\nreactor B extends A {\n  input b:u32;\n  reaction(a, b) -> out {=\n    ctx.set(out, ctx.get(a).unwrap() + ctx.get(b).unwrap());\n  =}\n}\n
    \n

    $end(Extends)$

    \n\"Lingua\n

    Here, the base class A has a single output that it writes to in reaction to an input. The subclass inherits the input, the output, and the reaction of A, and adds its own input b and reaction. When an input event a arrives, both reactions will be invoked, but, once again, in a well-defined order. The reactions of the base class are invoked before those of the derived class. So in this case, B will overwrite the output produced by A.

    \n

    One limitation is that a subclass cannot have ports, actions, or state variables with the same names as those in the base class. The names must be unique.

    \n

    A subclass can extend more than one base class by just providing a comma-separated list of base classes. If reactions in multiple base classes are triggered at the same tag, they will be invoked in the same order that they appear in the comma-separated list.

    \n
    ","headings":[{"value":"Extending a Base Reactor","depth":2}],"frontmatter":{"permalink":"/docs/handbook/extending-reactors","title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","permalink":"/docs/handbook/causality-loops"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Actions","oneline":"Actions in Lingua Franca.","permalink":"/docs/handbook/actions"}}}},"pageContext":{"id":"1-extending-reactors","slug":"/docs/handbook/extending-reactors","repoPath":"/packages/documentation/copy/en/topics/Extending Reactors.md","previousID":"de5af6b0-de72-5890-9668-c4f000ffdb2c","nextID":"ab880406-6c38-59c6-9a2c-a8f736013224","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/extending-reactors","result":{"data":{"markdownRemark":{"id":"2efb41b1-0afb-5eab-9d59-7f064a8b778e","excerpt":"$page-showing-target$ Extending a Base Reactor The Cpp target does not yet support extending reactors. Lingua Franca supports defining a reactor class as an…","html":"

    $page-showing-target$

    \n

    Extending a Base Reactor

    \n
    \n

    The Cpp target does not yet support extending reactors.

    \n
    \n
    \n

    Lingua Franca supports defining a reactor class as an extension (or subclass), as in the following example:

    \n

    $start(Extends)$

    \n
    target C;\nreactor A {\n  input a:int;\n  output out:int;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n}\nreactor B extends A {\n  input b:int;\n  reaction(a, b) -> out {=\n    lf_set(out, a->value + b->value);\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Extends.lf\n// the cpp target currently does not support reactor extends\n
    \n
    target Python;\nreactor A {\n  input a;\n  output out;\n  reaction(a) -> out {=\n    out.set(a.value)\n  =}\n}\nreactor B extends A {\n  input b;\n  reaction(a, b) -> out {=\n    out.set(a.value + b.value)\n  =}\n}\n
    \n
    target TypeScript\nreactor A {\n  input a:number\n  output out:number\n  reaction(a) -> out {=\n    out = a\n  =}\n}\nreactor B extends A {\n  input b:number\n  reaction(a, b) -> out {=\n    out = a + b\n  =}\n}\n
    \n
    target Rust;\nreactor A {\n  input a:u32;\n  output out:u32;\n  reaction(a) -> out {=\n    ctx.set(out, ctx.get(a).unwrap());\n  =}\n}\nreactor B extends A {\n  input b:u32;\n  reaction(a, b) -> out {=\n    ctx.set(out, ctx.get(a).unwrap() + ctx.get(b).unwrap());\n  =}\n}\n
    \n

    $end(Extends)$

    \n\"Lingua\n

    Here, the base class A has a single output that it writes to in reaction to an input. The subclass inherits the input, the output, and the reaction of A, and adds its own input b and reaction. When an input event a arrives, both reactions will be invoked, but, once again, in a well-defined order. The reactions of the base class are invoked before those of the derived class. So in this case, B will overwrite the output produced by A.

    \n

    One limitation is that a subclass cannot have ports, actions, or state variables with the same names as those in the base class. The names must be unique.

    \n

    A subclass can extend more than one base class by just providing a comma-separated list of base classes. If reactions in multiple base classes are triggered at the same tag, they will be invoked in the same order that they appear in the comma-separated list.

    \n
    ","headings":[{"value":"Extending a Base Reactor","depth":2}],"frontmatter":{"permalink":"/docs/handbook/extending-reactors","title":"Extending Reactors","oneline":"Extending reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","permalink":"/docs/handbook/causality-loops"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Actions","oneline":"Actions in Lingua Franca.","permalink":"/docs/handbook/actions"}}}},"pageContext":{"id":"1-extending-reactors","slug":"/docs/handbook/extending-reactors","repoPath":"/packages/documentation/copy/en/topics/Extending Reactors.md","previousID":"de5af6b0-de72-5890-9668-c4f000ffdb2c","nextID":"ab880406-6c38-59c6-9a2c-a8f736013224","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/features/page-data.json b/page-data/docs/handbook/features/page-data.json index b3f2cd559..639d15b1b 100644 --- a/page-data/docs/handbook/features/page-data.json +++ b/page-data/docs/handbook/features/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/features","result":{"data":{"markdownRemark":{"id":"f6a7c690-cb55-559f-8884-d60c4b264ed6","excerpt":"Target/Feature Banks & Multiports Clock Synchronization Federation C Y Y Y C++ Y N N TS Y N N Python Y N N","html":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    Target/FeatureBanks & MultiportsClock SynchronizationFederation
    CYYY
    C++YNN
    TSYNN
    PythonYNN
    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/features","title":"Target-Supported Features","oneline":"Which features are supported by which target?","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/features","repoPath":"/packages/documentation/copy/en/preliminary/Target-Supported Features.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/features","result":{"data":{"markdownRemark":{"id":"f6a7c690-cb55-559f-8884-d60c4b264ed6","excerpt":"Target/Feature Banks & Multiports Clock Synchronization Federation C Y Y Y C++ Y N N TS Y N N Python Y N N","html":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    Target/FeatureBanks & MultiportsClock SynchronizationFederation
    CYYY
    C++YNN
    TSYNN
    PythonYNN
    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/features","title":"Target-Supported Features","oneline":"Which features are supported by which target?","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/features","repoPath":"/packages/documentation/copy/en/preliminary/Target-Supported Features.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/generic-types-interfaces-inheritance/page-data.json b/page-data/docs/handbook/generic-types-interfaces-inheritance/page-data.json index 95f1dff82..fde7bf800 100644 --- a/page-data/docs/handbook/generic-types-interfaces-inheritance/page-data.json +++ b/page-data/docs/handbook/generic-types-interfaces-inheritance/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/generic-types-interfaces-inheritance","result":{"data":{"markdownRemark":{"id":"39dfe06d-8d92-5b0f-865d-1ef73107759b","excerpt":"The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals. Generics Reactor classes can be…","html":"

    The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals.

    \n

    Generics

    \n

    Reactor classes can be parameterized with type parameters as follows:

    \n
    reactor Foo<S, T> {\n    input:S;\n    output:T;\n}\n
    \n

    Type Constraints

    \n

    We could like to combine generics with type constraints of the form S extends Bar, where Bar refers to a reactor class or interface. The meaning of extending or implementing a reactor class will mean something slightly different from what this means in the target language — even if it features object orientation (OO).

    \n

    Interfaces

    \n

    While initially being tempted to distinguish interfaces from implementations, in an effort to promote simplicity, we (at least for the moment) propose not to. Only in case reactions and their signatures would be part of an interface and thus should be declared (without supplying an implementation) would there be a material difference between an interface and its implementation. Making reactions and their causality interfaces part of the reactor could prove useful, but it introduces a number of complications:

    \n
      \n
    • \n
    \n

    Inheritance

    \n
      \n
    • A reactor can extend multiple base classes;
    • \n
    • Reactions are inherited in the order of declaration; and
    • \n
    • Equally-named ports and actions between subclass and superclass must also be equally typed.
    • \n
    \n

    Example

    ","headings":[{"value":"Generics","depth":1},{"value":"Type Constraints","depth":2},{"value":"Interfaces","depth":1},{"value":"Inheritance","depth":1},{"value":"Example","depth":2}],"frontmatter":{"permalink":"/docs/handbook/generic-types-interfaces-inheritance","title":"Generic Types, Interfaces, and Inheritance","oneline":"Generic Types, Interfaces, and Inheritance (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/generic-types-interfaces-inheritance","repoPath":"/packages/documentation/copy/en/preliminary/Generic Types, Interfaces, and Inheritance.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/generic-types-interfaces-inheritance","result":{"data":{"markdownRemark":{"id":"39dfe06d-8d92-5b0f-865d-1ef73107759b","excerpt":"The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals. Generics Reactor classes can be…","html":"

    The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals.

    \n

    Generics

    \n

    Reactor classes can be parameterized with type parameters as follows:

    \n
    reactor Foo<S, T> {\n    input:S;\n    output:T;\n}\n
    \n

    Type Constraints

    \n

    We could like to combine generics with type constraints of the form S extends Bar, where Bar refers to a reactor class or interface. The meaning of extending or implementing a reactor class will mean something slightly different from what this means in the target language — even if it features object orientation (OO).

    \n

    Interfaces

    \n

    While initially being tempted to distinguish interfaces from implementations, in an effort to promote simplicity, we (at least for the moment) propose not to. Only in case reactions and their signatures would be part of an interface and thus should be declared (without supplying an implementation) would there be a material difference between an interface and its implementation. Making reactions and their causality interfaces part of the reactor could prove useful, but it introduces a number of complications:

    \n
      \n
    • \n
    \n

    Inheritance

    \n
      \n
    • A reactor can extend multiple base classes;
    • \n
    • Reactions are inherited in the order of declaration; and
    • \n
    • Equally-named ports and actions between subclass and superclass must also be equally typed.
    • \n
    \n

    Example

    ","headings":[{"value":"Generics","depth":1},{"value":"Type Constraints","depth":2},{"value":"Interfaces","depth":1},{"value":"Inheritance","depth":1},{"value":"Example","depth":2}],"frontmatter":{"permalink":"/docs/handbook/generic-types-interfaces-inheritance","title":"Generic Types, Interfaces, and Inheritance","oneline":"Generic Types, Interfaces, and Inheritance (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/generic-types-interfaces-inheritance","repoPath":"/packages/documentation/copy/en/preliminary/Generic Types, Interfaces, and Inheritance.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/generics/page-data.json b/page-data/docs/handbook/generics/page-data.json index babd8ffd9..b54a0e80b 100644 --- a/page-data/docs/handbook/generics/page-data.json +++ b/page-data/docs/handbook/generics/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/generics","result":{"data":{"markdownRemark":{"id":"efe15ca4-8dc6-5216-8a43-0d7ea216c339","excerpt":"Generic reactors are not supported in $target-language$. $page-showing-target$ Generic Reactors Sometimes it is useful to implement a generic pattern without…","html":"
    \n

    Generic reactors are not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    Generic Reactors

    \n

    Sometimes it is useful to implement a generic pattern without knowing the concrete types used. For instance, it could be useful to implement a delay reactor that forwards all values it receives with a fixed delay, regardless of their datatype. For this pattern, it is not required to know the concrete type in advance, and we would like to reuse the same logic for different types. This can be achieved with generic reactors in LF. Consider the following example:

    \n

    $start(GenericDelay)$

    \n
    target C\nreactor Delay<T>(delay: time = 0) {\n  input in: T\n  output out: T\n  logical action a(delay): T\n  reaction(a) -> out {= lf_set(out, a->value); =}\n  reaction(in) -> a {= lf_schedule_copy(a, self->delay, &in->value, 1); =}\n}\nmain reactor {\n  d = new Delay<int>(delay = 100 ms)\n  reaction(startup) -> d.in {= lf_set(d.in, 42); =}\n  reaction(d.out) {=\n    printf("Received %d at time %lld.\\n", d.out->value, lf_time_logical_elapsed());\n  =}\n}\n
    \n
    target Cpp\nreactor Delay<T>(delay: time = 0) {\n  input in: T\n  output out: T\n  logical action a(delay): T\n  reaction(a) -> out {= out.set(a.get()); =}\n  reaction(in) -> a {= a.schedule(in.get(), delay); =}\n}\nmain reactor {\n  d = new Delay<int>(delay = 100 ms)\n  reaction(startup) -> d.in {= d.in.set(42); =}\n  reaction(d.out) {=\n    std::cout << "received " << *d.out.get() << " at time "\n        << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/py/src/GenericDelay.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/GenericDelay.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/GenericDelay.lf\n
    \n

    $end(GenericDelay)$

    \n

    The example above defines a generic reactor Delay which has a type parameter named T. Its input, output and logical action are all of type T. The logic implemented in the reactions is straightforward. The reaction to in schedules the logical action a with the configured delay and the received value. The reaction to a simply forwards this value to the output port at a later tag. The concrete type T, however, is not relevant for this implementation and will be filled in only when the reactor is instantiated. In our example, the main reactor instantiates Delay, specifying int as the type to be assigned to T. As a consequence, we can set an integer on d’s input port and receive an integer on its output. If we wanted instead to delay a string, we can do this as follows:

    \n

    $start(GenericString)$

    \n
    target C\nimport Delay from "GenericDelay.lf"\nmain reactor {\n  d = new Delay<string>(delay = 100 ms)\n  reaction(startup) -> d.in {=\n    lf_set(d.in, "foo");\n  =}\n  reaction(d.out) {=\n    printf("Received %s at time %lld.\\n", d.out->value, lf_time_logical_elapsed());\n  =}\n}\n
    \n
    target Cpp\nimport Delay from "GenericDelay.lf"\nmain reactor {\n  d = new Delay<{= std::string =}>(delay = 100 ms)\n  reaction(startup) -> d.in {=\n    d.in.set("foo");\n  =}\n  reaction(d.out) {=\n    std::cout << "received " << *d.out.get() << " at time "\n        << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/py/src/GenericString.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/GenericString.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/GenericString.lf\n
    \n

    $end(GenericString)$

    \n

    Reactor definitions may also specify multiple type parameters. Moreover, type parameters are not limited to ports and actions, but can also be used in state variables, parameters, or methods. For instance, we can define the following reactor:

    \n
    reactor Generic<T, U, V>(bar: T) {\n  state baz: U\n\n  input in: V\n\n  method (x: T, y: U): V {= /* ... */ =}\n}\n
    \n

    This reactor could be instantiated for example like this:

    \n
    g = new Generic<float, int, bool>()\n
    \n
    ","headings":[{"value":"Generic Reactors","depth":2}],"frontmatter":{"permalink":"/docs/handbook/generics","title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","permalink":"/docs/handbook/multiports-and-banks"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Preambles","oneline":"Defining preambles in Lingua Franca.","permalink":"/docs/handbook/preambles"}}}},"pageContext":{"id":"1-generic-reactors","slug":"/docs/handbook/generics","repoPath":"/packages/documentation/copy/en/topics/Generics.md","previousID":"9ff63bbf-2bdf-553e-a96d-52355866ec94","nextID":"d9c76683-1fe5-55e0-b223-8e21c125f9cd","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/generics","result":{"data":{"markdownRemark":{"id":"efe15ca4-8dc6-5216-8a43-0d7ea216c339","excerpt":"Generic reactors are not supported in $target-language$. $page-showing-target$ Generic Reactors Sometimes it is useful to implement a generic pattern without…","html":"
    \n

    Generic reactors are not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    Generic Reactors

    \n

    Sometimes it is useful to implement a generic pattern without knowing the concrete types used. For instance, it could be useful to implement a delay reactor that forwards all values it receives with a fixed delay, regardless of their datatype. For this pattern, it is not required to know the concrete type in advance, and we would like to reuse the same logic for different types. This can be achieved with generic reactors in LF. Consider the following example:

    \n

    $start(GenericDelay)$

    \n
    target C\nreactor Delay<T>(delay: time = 0) {\n  input in: T\n  output out: T\n  logical action a(delay): T\n  reaction(a) -> out {= lf_set(out, a->value); =}\n  reaction(in) -> a {= lf_schedule_copy(a, self->delay, &in->value, 1); =}\n}\nmain reactor {\n  d = new Delay<int>(delay = 100 ms)\n  reaction(startup) -> d.in {= lf_set(d.in, 42); =}\n  reaction(d.out) {=\n    printf("Received %d at time %lld.\\n", d.out->value, lf_time_logical_elapsed());\n  =}\n}\n
    \n
    target Cpp\nreactor Delay<T>(delay: time = 0) {\n  input in: T\n  output out: T\n  logical action a(delay): T\n  reaction(a) -> out {= out.set(a.get()); =}\n  reaction(in) -> a {= a.schedule(in.get(), delay); =}\n}\nmain reactor {\n  d = new Delay<int>(delay = 100 ms)\n  reaction(startup) -> d.in {= d.in.set(42); =}\n  reaction(d.out) {=\n    std::cout << "received " << *d.out.get() << " at time "\n        << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/py/src/GenericDelay.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/GenericDelay.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/GenericDelay.lf\n
    \n

    $end(GenericDelay)$

    \n

    The example above defines a generic reactor Delay which has a type parameter named T. Its input, output and logical action are all of type T. The logic implemented in the reactions is straightforward. The reaction to in schedules the logical action a with the configured delay and the received value. The reaction to a simply forwards this value to the output port at a later tag. The concrete type T, however, is not relevant for this implementation and will be filled in only when the reactor is instantiated. In our example, the main reactor instantiates Delay, specifying int as the type to be assigned to T. As a consequence, we can set an integer on d’s input port and receive an integer on its output. If we wanted instead to delay a string, we can do this as follows:

    \n

    $start(GenericString)$

    \n
    target C\nimport Delay from "GenericDelay.lf"\nmain reactor {\n  d = new Delay<string>(delay = 100 ms)\n  reaction(startup) -> d.in {=\n    lf_set(d.in, "foo");\n  =}\n  reaction(d.out) {=\n    printf("Received %s at time %lld.\\n", d.out->value, lf_time_logical_elapsed());\n  =}\n}\n
    \n
    target Cpp\nimport Delay from "GenericDelay.lf"\nmain reactor {\n  d = new Delay<{= std::string =}>(delay = 100 ms)\n  reaction(startup) -> d.in {=\n    d.in.set("foo");\n  =}\n  reaction(d.out) {=\n    std::cout << "received " << *d.out.get() << " at time "\n        << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/py/src/GenericString.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/GenericString.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/GenericString.lf\n
    \n

    $end(GenericString)$

    \n

    Reactor definitions may also specify multiple type parameters. Moreover, type parameters are not limited to ports and actions, but can also be used in state variables, parameters, or methods. For instance, we can define the following reactor:

    \n
    reactor Generic<T, U, V>(bar: T) {\n  state baz: U\n\n  input in: V\n\n  method (x: T, y: U): V {= /* ... */ =}\n}\n
    \n

    This reactor could be instantiated for example like this:

    \n
    g = new Generic<float, int, bool>()\n
    \n
    ","headings":[{"value":"Generic Reactors","depth":2}],"frontmatter":{"permalink":"/docs/handbook/generics","title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","permalink":"/docs/handbook/multiports-and-banks"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Preambles","oneline":"Defining preambles in Lingua Franca.","permalink":"/docs/handbook/preambles"}}}},"pageContext":{"id":"1-generic-reactors","slug":"/docs/handbook/generics","repoPath":"/packages/documentation/copy/en/topics/Generics.md","previousID":"9ff63bbf-2bdf-553e-a96d-52355866ec94","nextID":"d9c76683-1fe5-55e0-b223-8e21c125f9cd","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/import-system/page-data.json b/page-data/docs/handbook/import-system/page-data.json index 9e154cfdc..4b6b69ab7 100644 --- a/page-data/docs/handbook/import-system/page-data.json +++ b/page-data/docs/handbook/import-system/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/import-system","result":{"data":{"markdownRemark":{"id":"4f193470-c244-5392-8875-25389d7cd383","excerpt":"The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals. Current Implementation of…","html":"

    The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals.

    \n

    Current Implementation of Imports

    \n

    The import functionality in Lingua Franca is limited to:

    \n
    import HelloWorld.lf
    \n

    This can be useful if the .lf file is located in the same directory as the file containing the main reactor.

    \n

    However, several shortcomings exist in this current system which we shall discuss next.

    \n

    Duplicate Reactor Names

    \n

    Reactors with the same name can cause issues. For example:

    \n
    import CatsAndPuppies.lf // Contains a Puppy reactor\nimport MeanPuppies.lf   // Contains another Puppy reactor
    \n

    There is no way for the LF program to distinguish between the two Puppy reactors.

    \n

    Note. With a relatively trivial extension to the current LF import mechanism, it is possible to detect duplicates, but there is no way to circumvent them in the current LF program (i.e., the original names might have to be changed).

    \n

    Selective Importing

    \n

    Selective importing is not possible. For example, using

    \n
    import CatsAndPuppies.lf
    \n

    will import all the reactors contained in the .lf file. It would be desirable to selectively import a subset of reactors in another .lf file.

    \n

    Qualified Paths

    \n

    Currently, there is no elegant way of importing modules that are not in the same directory.

    \n

    Renaming

    \n

    All the reactors imported will have the name originally given to them by the original programmer. It might make sense to rename them for the current LF program.

    \n

    Packages

    \n

    With the current import solution that only uses files, implementing packages in Lingua Franca is not feasible.

    \n

    Proposed Solution

    \n

    With inspirations from Python, we propose the following import mechanism:

    \n
    "import" LF_Trunc_File/module ("," LF_Trunc_File/module)* \n              | "from" LFTruncFile/module "import" reactor ["as" name]\n                ("," reactor ["as" name] )*\n              | "from" LF_Trunc_File/module "import" "*" 
    \n

    Before discussing some examples, let’s discuss LF_Trunc_File/module. First and foremost, LF_Truc_File stands for Lingua Franca Truncated File, which is a name.lf file with the .lf removed. Therefore, the legacy support for import can be carried over as:

    \n
    import HelloWorld
    \n

    Second, the module would introduce the notion of packages to Lingua Franca. The content of a module can be located in any path. To enable this facility, modules provide a Lingua Franca Meta file (LFM) that introduces the package name, and the absolute or relative paths of all the LF files that are included in that package. For example:

    \n
    // CatsAndPuppies.LFM\npackage CatsAndPuppies // Optional. The file name would be interpreted as the package name.\nimport /home/user/linguafranca/pets/Cats.lf // Absolute paths\nimport pets/Puppies.lf // Relative paths
    \n

    For a package to be accessible, the LFM file needs to be discoverable. For example, it can be automatically added to the current directory or “installed” in a known Lingua Franca path (e.g., /usr/local/LF/packages or /home/user/linguafranca/packages).

    \n

    With that in mind, let’s discuss some examples on how this might work next.\nThe content of the HelloWorld.lf example is as follows:

    \n
    target C; \nreactor SayHello {\n    timer t;\n    reaction(t) {=\n        printf("Hello World.\\n");\n    =}\n}\nmain reactor HelloWorldTest {\n    a = new HelloWorld();\n}
    \n

    Let us create a Greetings.lf program based on HelloWorld.

    \n
    target C; \nimport HelloWorld\n\nmain reactor Greetings {\n    a = new SayHello();\n}
    \n

    To generate code for Greetings.lf, Lingua Franca first searches for a HelloWorld.lf file in the same directory as Greetings.lf. If not found, it will look for a HelloWorld.LFM in the known paths. If none is found, an error is raised.

    \n

    Now we can demonstrate selective import. For example:

    \n
    target C; \nfrom HelloWorld import SayHello\n\nmain reactor Greetings {\n    a = new SayHello();\n}
    \n

    Finally, renaming can be done by using the as predicate:

    \n
    target C; \nfrom HelloWorld import SayHello as SayGreetings\n\nmain reactor Greetings {\n    a = new SayHeGreetings();\n}
    ","headings":[{"value":"Current Implementation of Imports","depth":1},{"value":"Duplicate Reactor Names","depth":2},{"value":"Selective Importing","depth":2},{"value":"Qualified Paths","depth":2},{"value":"Renaming","depth":2},{"value":"Packages","depth":2},{"value":"Proposed Solution","depth":1}],"frontmatter":{"permalink":"/docs/handbook/import-system","title":"Import System","oneline":"Import System (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/import-system","repoPath":"/packages/documentation/copy/en/preliminary/Import System.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/import-system","result":{"data":{"markdownRemark":{"id":"4f193470-c244-5392-8875-25389d7cd383","excerpt":"The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals. Current Implementation of…","html":"

    The following topics are meant as collections of design ideas, with the purpose of refining them into concrete design proposals.

    \n

    Current Implementation of Imports

    \n

    The import functionality in Lingua Franca is limited to:

    \n
    import HelloWorld.lf
    \n

    This can be useful if the .lf file is located in the same directory as the file containing the main reactor.

    \n

    However, several shortcomings exist in this current system which we shall discuss next.

    \n

    Duplicate Reactor Names

    \n

    Reactors with the same name can cause issues. For example:

    \n
    import CatsAndPuppies.lf // Contains a Puppy reactor\nimport MeanPuppies.lf   // Contains another Puppy reactor
    \n

    There is no way for the LF program to distinguish between the two Puppy reactors.

    \n

    Note. With a relatively trivial extension to the current LF import mechanism, it is possible to detect duplicates, but there is no way to circumvent them in the current LF program (i.e., the original names might have to be changed).

    \n

    Selective Importing

    \n

    Selective importing is not possible. For example, using

    \n
    import CatsAndPuppies.lf
    \n

    will import all the reactors contained in the .lf file. It would be desirable to selectively import a subset of reactors in another .lf file.

    \n

    Qualified Paths

    \n

    Currently, there is no elegant way of importing modules that are not in the same directory.

    \n

    Renaming

    \n

    All the reactors imported will have the name originally given to them by the original programmer. It might make sense to rename them for the current LF program.

    \n

    Packages

    \n

    With the current import solution that only uses files, implementing packages in Lingua Franca is not feasible.

    \n

    Proposed Solution

    \n

    With inspirations from Python, we propose the following import mechanism:

    \n
    "import" LF_Trunc_File/module ("," LF_Trunc_File/module)* \n              | "from" LFTruncFile/module "import" reactor ["as" name]\n                ("," reactor ["as" name] )*\n              | "from" LF_Trunc_File/module "import" "*" 
    \n

    Before discussing some examples, let’s discuss LF_Trunc_File/module. First and foremost, LF_Truc_File stands for Lingua Franca Truncated File, which is a name.lf file with the .lf removed. Therefore, the legacy support for import can be carried over as:

    \n
    import HelloWorld
    \n

    Second, the module would introduce the notion of packages to Lingua Franca. The content of a module can be located in any path. To enable this facility, modules provide a Lingua Franca Meta file (LFM) that introduces the package name, and the absolute or relative paths of all the LF files that are included in that package. For example:

    \n
    // CatsAndPuppies.LFM\npackage CatsAndPuppies // Optional. The file name would be interpreted as the package name.\nimport /home/user/linguafranca/pets/Cats.lf // Absolute paths\nimport pets/Puppies.lf // Relative paths
    \n

    For a package to be accessible, the LFM file needs to be discoverable. For example, it can be automatically added to the current directory or “installed” in a known Lingua Franca path (e.g., /usr/local/LF/packages or /home/user/linguafranca/packages).

    \n

    With that in mind, let’s discuss some examples on how this might work next.\nThe content of the HelloWorld.lf example is as follows:

    \n
    target C; \nreactor SayHello {\n    timer t;\n    reaction(t) {=\n        printf("Hello World.\\n");\n    =}\n}\nmain reactor HelloWorldTest {\n    a = new HelloWorld();\n}
    \n

    Let us create a Greetings.lf program based on HelloWorld.

    \n
    target C; \nimport HelloWorld\n\nmain reactor Greetings {\n    a = new SayHello();\n}
    \n

    To generate code for Greetings.lf, Lingua Franca first searches for a HelloWorld.lf file in the same directory as Greetings.lf. If not found, it will look for a HelloWorld.LFM in the known paths. If none is found, an error is raised.

    \n

    Now we can demonstrate selective import. For example:

    \n
    target C; \nfrom HelloWorld import SayHello\n\nmain reactor Greetings {\n    a = new SayHello();\n}
    \n

    Finally, renaming can be done by using the as predicate:

    \n
    target C; \nfrom HelloWorld import SayHello as SayGreetings\n\nmain reactor Greetings {\n    a = new SayHeGreetings();\n}
    ","headings":[{"value":"Current Implementation of Imports","depth":1},{"value":"Duplicate Reactor Names","depth":2},{"value":"Selective Importing","depth":2},{"value":"Qualified Paths","depth":2},{"value":"Renaming","depth":2},{"value":"Packages","depth":2},{"value":"Proposed Solution","depth":1}],"frontmatter":{"permalink":"/docs/handbook/import-system","title":"Import System","oneline":"Import System (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/import-system","repoPath":"/packages/documentation/copy/en/preliminary/Import System.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/inputs-and-outputs/page-data.json b/page-data/docs/handbook/inputs-and-outputs/page-data.json index fccafe2de..ddcdc1e8e 100644 --- a/page-data/docs/handbook/inputs-and-outputs/page-data.json +++ b/page-data/docs/handbook/inputs-and-outputs/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/inputs-and-outputs","result":{"data":{"markdownRemark":{"id":"28c65d97-e18a-53ae-8b3a-7e4caea32061","excerpt":"$page-showing-target$ In this section, we will endow reactors with inputs and outputs. Input and Output Declarations Input and output declarations have the form…","html":"

    $page-showing-target$

    \n

    In this section, we will endow reactors with inputs and outputs.

    \n

    Input and Output Declarations

    \n

    Input and output declarations have the form:

    \n
    \n
      input <name>:<type>\n  output <name>:<type>\n
    \n
    \n
    \n
      input <name>\n  output <name>\n
    \n
    \n

    For example, the following reactor doubles its input and sends the result to the output:

    \n

    $start(Double)$

    \n
    target C\nreactor Double {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    lf_set(y, x->value * 2);\n  =}\n}\n
    \n
    target Cpp\nreactor Double {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    if (x.is_present()){\n        y.set(*x.get() * 2);\n    }\n  =}\n}\n
    \n
    target Python\nreactor Double {\n  input x\n  output y\n  reaction(x) -> y {=\n    y.set(x.value * 2)\n  =}\n}\n
    \n
    target TypeScript\nreactor Double {\n  input x: number\n  output y: number\n  reaction(x) -> y {=\n    y = value * 2\n  =}\n}\n
    \n
    target Rust\nreactor Double {\n  input x: u32\n  output y: u32\n  reaction(x) -> y {=\n    ctx.set(y, ctx.get(x).unwrap() * 2);\n  =}\n}\n
    \n

    $end(Double)$

    \n

    Notice how the input value is accessed and how the output value is set. This is done differently for each target language. See the Target Language Details for detailed documentation of these mechanisms.\nSetting an output within a reaction will trigger downstream reactions at the same Logical Time that the reaction is invoked (or, more precisely, at the same tag). If a particular output port is set more than once at any tag, the last set value will be the one that downstream reactions see. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

    \n
    \n

    The type of a port is a type in the target language plus the special type $time$. A type may also be specified using a code block, delimited by the same delimiters {= ... =} that separate target language code from Lingua Franca code in reactions. Any valid target-language type designator can be given within these delimiters.

    \n
    \n

    The $reaction$ declaration above indicates that an input event on port x is a trigger and that an output event on port y is a (potential) effect. A reaction can declare more than one trigger or effect by just listing them separated by commas (See Reactions for details). For example, the following reactor has two triggers and tests each input for presence before using it:

    \n

    $start(Destination)$

    \n
    target C\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    int sum = 0;\n    if (x->is_present) {\n      sum += x->value;\n    }\n    if (y->is_present) {\n      sum += y->value;\n    }\n    printf("Received %d.\\n", sum);\n  =}\n}\n
    \n
    target Cpp\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    int sum = 0;\n    if (x.is_present()) {\n      sum += *x.get();\n    }\n    if (y.is_present()) {\n      sum += *y.get();\n    }\n    std::cout << "Received: " << sum << std::endl;\n  =}\n}\n
    \n
    target Python\nreactor Destination {\n  input x\n  input y\n  reaction(x, y) {=\n    sum = 0\n    if x.is_present:\n      sum += x.value\n    if y.is_present:\n      sum += y.value\n    print(f"Received {sum}")\n  =}\n}\n
    \n
    target TypeScript\nreactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    let sum = 0\n    if (x !== undefined) {\n      sum += x\n    }\n    if (y !== undefined) {\n      sum += y\n    }\n    console.log(`Received ${sum}.`)\n  =}\n}\n
    \n
    target Rust\nreactor Destination {\n  input x: u32\n  input y: u32\n  reaction(x, y) {=\n    let mut sum = 0;\n    if let Some(x) = ctx.get(x) {\n      sum += x;\n    }\n    if let Some(y) = ctx.get(y) {\n      sum += y;\n    }\n    println!("Received {}.", sum);\n  =}\n}\n
    \n

    $end(Destination)$

    \n

    NOTE: if a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is target dependent.\nIn the C target, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the data type of the input.\nIn the C++ target, a smart pointer is returned for present values and nullptr if the value is not present.\nIn the Python target, the value will be None if the input is not present.\nIn the TS target, the value will be undefined if the input is not present, a legitimate value in TypeScript.\nFIXME.

    \n

    Setting an Output Multiple Times

    \n

    If one or more reactions set an output multiple times at the same tag, then only the last value set will be seen by any downstream reactors.

    \n

    If a reaction wishes to test whether an output has been previously set at the current tag by some other reaction, it can test it in the same way it tests inputs for presence.

    \n

    Mutable Inputs

    \n

    Normally, a reaction does not modify the value of an input. An input is said to be immutable. The degree to which this is enforced varies by target language. Most of the target languages make it rather difficult to enforce, so the programmer needs to avoid modifying the input. Modifying an input value may lead to nondeterministic results.

    \n

    Occasionally, it is useful to modify an input. For example, the input may be a large data structure, and a reaction may wish to make a small modification and forward the result to an output. To accomplish this, the programmer should declare the input $mutable$ as follows:

    \n
    \n
      mutable input <name>:<type>\n
    \n
    \n
    \n
      mutable input <name>\n
    \n
    \n

    This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-specific reference documentation has more details about how this works.

    ","headings":[{"value":"Input and Output Declarations","depth":2},{"value":"Setting an Output Multiple Times","depth":2},{"value":"Mutable Inputs","depth":2}],"frontmatter":{"permalink":"/docs/handbook/inputs-and-outputs","title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"A First Reactor","oneline":"Writing your first Lingua Franca reactor.","permalink":"/docs/handbook/a-first-reactor"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","permalink":"/docs/handbook/parameters-and-state-variables"}}}},"pageContext":{"id":"1-inputs-and-outputs","slug":"/docs/handbook/inputs-and-outputs","repoPath":"/packages/documentation/copy/en/topics/Inputs and Outputs.md","previousID":"7ab4c09f-77ee-57d7-ac47-116061262590","nextID":"20781702-b6a5-5a16-b4d8-b4c45cd76fa3","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/inputs-and-outputs","result":{"data":{"markdownRemark":{"id":"28c65d97-e18a-53ae-8b3a-7e4caea32061","excerpt":"$page-showing-target$ In this section, we will endow reactors with inputs and outputs. Input and Output Declarations Input and output declarations have the form…","html":"

    $page-showing-target$

    \n

    In this section, we will endow reactors with inputs and outputs.

    \n

    Input and Output Declarations

    \n

    Input and output declarations have the form:

    \n
    \n
      input <name>:<type>\n  output <name>:<type>\n
    \n
    \n
    \n
      input <name>\n  output <name>\n
    \n
    \n

    For example, the following reactor doubles its input and sends the result to the output:

    \n

    $start(Double)$

    \n
    target C\nreactor Double {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    lf_set(y, x->value * 2);\n  =}\n}\n
    \n
    target Cpp\nreactor Double {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    if (x.is_present()){\n        y.set(*x.get() * 2);\n    }\n  =}\n}\n
    \n
    target Python\nreactor Double {\n  input x\n  output y\n  reaction(x) -> y {=\n    y.set(x.value * 2)\n  =}\n}\n
    \n
    target TypeScript\nreactor Double {\n  input x: number\n  output y: number\n  reaction(x) -> y {=\n    y = value * 2\n  =}\n}\n
    \n
    target Rust\nreactor Double {\n  input x: u32\n  output y: u32\n  reaction(x) -> y {=\n    ctx.set(y, ctx.get(x).unwrap() * 2);\n  =}\n}\n
    \n

    $end(Double)$

    \n

    Notice how the input value is accessed and how the output value is set. This is done differently for each target language. See the Target Language Details for detailed documentation of these mechanisms.\nSetting an output within a reaction will trigger downstream reactions at the same Logical Time that the reaction is invoked (or, more precisely, at the same tag). If a particular output port is set more than once at any tag, the last set value will be the one that downstream reactions see. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

    \n
    \n

    The type of a port is a type in the target language plus the special type $time$. A type may also be specified using a code block, delimited by the same delimiters {= ... =} that separate target language code from Lingua Franca code in reactions. Any valid target-language type designator can be given within these delimiters.

    \n
    \n

    The $reaction$ declaration above indicates that an input event on port x is a trigger and that an output event on port y is a (potential) effect. A reaction can declare more than one trigger or effect by just listing them separated by commas (See Reactions for details). For example, the following reactor has two triggers and tests each input for presence before using it:

    \n

    $start(Destination)$

    \n
    target C\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    int sum = 0;\n    if (x->is_present) {\n      sum += x->value;\n    }\n    if (y->is_present) {\n      sum += y->value;\n    }\n    printf("Received %d.\\n", sum);\n  =}\n}\n
    \n
    target Cpp\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    int sum = 0;\n    if (x.is_present()) {\n      sum += *x.get();\n    }\n    if (y.is_present()) {\n      sum += *y.get();\n    }\n    std::cout << "Received: " << sum << std::endl;\n  =}\n}\n
    \n
    target Python\nreactor Destination {\n  input x\n  input y\n  reaction(x, y) {=\n    sum = 0\n    if x.is_present:\n      sum += x.value\n    if y.is_present:\n      sum += y.value\n    print(f"Received {sum}")\n  =}\n}\n
    \n
    target TypeScript\nreactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    let sum = 0\n    if (x !== undefined) {\n      sum += x\n    }\n    if (y !== undefined) {\n      sum += y\n    }\n    console.log(`Received ${sum}.`)\n  =}\n}\n
    \n
    target Rust\nreactor Destination {\n  input x: u32\n  input y: u32\n  reaction(x, y) {=\n    let mut sum = 0;\n    if let Some(x) = ctx.get(x) {\n      sum += x;\n    }\n    if let Some(y) = ctx.get(y) {\n      sum += y;\n    }\n    println!("Received {}.", sum);\n  =}\n}\n
    \n

    $end(Destination)$

    \n

    NOTE: if a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is target dependent.\nIn the C target, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the data type of the input.\nIn the C++ target, a smart pointer is returned for present values and nullptr if the value is not present.\nIn the Python target, the value will be None if the input is not present.\nIn the TS target, the value will be undefined if the input is not present, a legitimate value in TypeScript.\nFIXME.

    \n

    Setting an Output Multiple Times

    \n

    If one or more reactions set an output multiple times at the same tag, then only the last value set will be seen by any downstream reactors.

    \n

    If a reaction wishes to test whether an output has been previously set at the current tag by some other reaction, it can test it in the same way it tests inputs for presence.

    \n

    Mutable Inputs

    \n

    Normally, a reaction does not modify the value of an input. An input is said to be immutable. The degree to which this is enforced varies by target language. Most of the target languages make it rather difficult to enforce, so the programmer needs to avoid modifying the input. Modifying an input value may lead to nondeterministic results.

    \n

    Occasionally, it is useful to modify an input. For example, the input may be a large data structure, and a reaction may wish to make a small modification and forward the result to an output. To accomplish this, the programmer should declare the input $mutable$ as follows:

    \n
    \n
      mutable input <name>:<type>\n
    \n
    \n
    \n
      mutable input <name>\n
    \n
    \n

    This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-specific reference documentation has more details about how this works.

    ","headings":[{"value":"Input and Output Declarations","depth":2},{"value":"Setting an Output Multiple Times","depth":2},{"value":"Mutable Inputs","depth":2}],"frontmatter":{"permalink":"/docs/handbook/inputs-and-outputs","title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"A First Reactor","oneline":"Writing your first Lingua Franca reactor.","permalink":"/docs/handbook/a-first-reactor"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","permalink":"/docs/handbook/parameters-and-state-variables"}}}},"pageContext":{"id":"1-inputs-and-outputs","slug":"/docs/handbook/inputs-and-outputs","repoPath":"/packages/documentation/copy/en/topics/Inputs and Outputs.md","previousID":"7ab4c09f-77ee-57d7-ac47-116061262590","nextID":"20781702-b6a5-5a16-b4d8-b4c45cd76fa3","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/intellij/page-data.json b/page-data/docs/handbook/intellij/page-data.json index 04a6bffd7..be031561d 100644 --- a/page-data/docs/handbook/intellij/page-data.json +++ b/page-data/docs/handbook/intellij/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/intellij","result":{"data":{"markdownRemark":{"id":"57e21136-002a-540c-84e6-b179b7e75eb2","excerpt":"Prerequisites Java 17 (download from Oracle) IntelliJ IDEA Community Edition (download from Jetbrains) Cloning lingua-franca repository If you have not done so…","html":"

    Prerequisites

    \n\n

    Cloning lingua-franca repository

    \n

    If you have not done so already, clone the lingua-franca repository into your working directory.

    \n
    $ git clone git@github.com:lf-lang/lingua-franca.git lingua-franca\n$ cd lingua-franca\n$ git submodule update --init --recursive\n
    \n

    Opening lingua-franca as IntelliJ Project

    \n

    To import the Lingua Franca repository as a project, simply run ./gradlew openIdea.\nThis will create some project files and then open the project in IntelliJ.

    \n

    When you open the project for the first time, you will see a small pop-up in the lower right corner.

    \n

    \n \n \n

    \n

    Click on Load Gradle Project to import the Gradle configurations.

    \n

    If you are prompted to a pop-up window asking if you trust the Gradle project, click Trust Project.

    \n

    \n \n \n

    \n

    Once the repository is imported as a Gradle project, you will see a Gradle tab on the right.

    \n

    Once the indexing finishes, you can expand the Gradle project and see the set of Tasks.

    \n

    \n \n \n

    \n

    You can run any Gradle command from IntelliJ simply by clicking on the Execute Gradle Task icon in the Gradle tab. You are then prompted for the precise command to run.

    \n

    Setting up run configurations

    \n

    You can set up a run configuration for running and debugging various Gradle tasks from the Gradle tab, including the code generation through lfc.\nTo set up a run configuration for the run task of lfc, expand the application task group under org.lflang > Tasks, right-click on ⚙️ run, and select Modify Run Configuration….\nThis will create a custom run/debug configuration for you.

    \n

    In the Create Run Configuration dialog, click on the text box next to Run, select cli:lfc:run from the drop-down menu, and append arguments to be passed to lfc using the --args flag. For instance, to invoke lfc on test/Cpp/src/HelloWorld.lf, enter cli:lfc:run --args 'test/Cpp/src/HelloWorld.lf' Then click OK.

    \n

    \n \n \n

    \n

    You will see a new run/debug config added to the top-level menu bar, as shown below.\nYou can always change the config, for example, changing the --args, by clicking Edit Configurations via a drop-down menu.

    \n

    \n \n \n

    \n

    Running and Debugging

    \n

    Using the newly added config, you can run and debug the code generator by clicking the play button and the debug button.

    \n

    \n \n \n

    \n

    Set up breakpoints before starting the debugger by clicking the space right next to the line numbers.\nWhile debugging, you can run code step-by-step by using the debugger tools.

    \n

    \n to the targetTest command or -DsingleTest=... to your singleTest command to specify the target (e.g., C) or the specific test that you would like to run.

    ","headings":[{"value":"Prerequisites","depth":2},{"value":"Cloning lingua-franca repository","depth":2},{"value":"Opening lingua-franca as IntelliJ Project","depth":2},{"value":"Setting up run configurations","depth":2},{"value":"Running and Debugging","depth":2},{"value":"Integration Tests","depth":2}],"frontmatter":{"permalink":"/docs/handbook/intellij","title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","permalink":"/docs/handbook/developer-setup"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","permalink":"/docs/handbook/regression-tests"}}}},"pageContext":{"id":"5-developer-intellij-setup","slug":"/docs/handbook/intellij","repoPath":"/packages/documentation/copy/en/developer/Developer IntelliJ Setup.md","previousID":"81b7347d-2806-53c1-91ad-c7b12d062d3c","nextID":"b004db16-2d0f-54e3-a2a8-d9d6f510eea1","lang":"en","modifiedTime":"2023-10-19T14:14:27.297Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/intellij","result":{"data":{"markdownRemark":{"id":"57e21136-002a-540c-84e6-b179b7e75eb2","excerpt":"Prerequisites Java 17 (download from Oracle) IntelliJ IDEA Community Edition (download from Jetbrains) Cloning lingua-franca repository If you have not done so…","html":"

    Prerequisites

    \n\n

    Cloning lingua-franca repository

    \n

    If you have not done so already, clone the lingua-franca repository into your working directory.

    \n
    $ git clone git@github.com:lf-lang/lingua-franca.git lingua-franca\n$ cd lingua-franca\n$ git submodule update --init --recursive\n
    \n

    Opening lingua-franca as IntelliJ Project

    \n

    To import the Lingua Franca repository as a project, simply run ./gradlew openIdea.\nThis will create some project files and then open the project in IntelliJ.

    \n

    When you open the project for the first time, you will see a small pop-up in the lower right corner.

    \n

    \n \n \n

    \n

    Click on Load Gradle Project to import the Gradle configurations.

    \n

    If you are prompted to a pop-up window asking if you trust the Gradle project, click Trust Project.

    \n

    \n \n \n

    \n

    Once the repository is imported as a Gradle project, you will see a Gradle tab on the right.

    \n

    Once the indexing finishes, you can expand the Gradle project and see the set of Tasks.

    \n

    \n \n \n

    \n

    You can run any Gradle command from IntelliJ simply by clicking on the Execute Gradle Task icon in the Gradle tab. You are then prompted for the precise command to run.

    \n

    Setting up run configurations

    \n

    You can set up a run configuration for running and debugging various Gradle tasks from the Gradle tab, including the code generation through lfc.\nTo set up a run configuration for the run task of lfc, expand the application task group under org.lflang > Tasks, right-click on ⚙️ run, and select Modify Run Configuration….\nThis will create a custom run/debug configuration for you.

    \n

    In the Create Run Configuration dialog, click on the text box next to Run, select cli:lfc:run from the drop-down menu, and append arguments to be passed to lfc using the --args flag. For instance, to invoke lfc on test/Cpp/src/HelloWorld.lf, enter cli:lfc:run --args 'test/Cpp/src/HelloWorld.lf' Then click OK.

    \n

    \n \n \n

    \n

    You will see a new run/debug config added to the top-level menu bar, as shown below.\nYou can always change the config, for example, changing the --args, by clicking Edit Configurations via a drop-down menu.

    \n

    \n \n \n

    \n

    Running and Debugging

    \n

    Using the newly added config, you can run and debug the code generator by clicking the play button and the debug button.

    \n

    \n \n \n

    \n

    Set up breakpoints before starting the debugger by clicking the space right next to the line numbers.\nWhile debugging, you can run code step-by-step by using the debugger tools.

    \n

    \n to the targetTest command or -DsingleTest=... to your singleTest command to specify the target (e.g., C) or the specific test that you would like to run.

    ","headings":[{"value":"Prerequisites","depth":2},{"value":"Cloning lingua-franca repository","depth":2},{"value":"Opening lingua-franca as IntelliJ Project","depth":2},{"value":"Setting up run configurations","depth":2},{"value":"Running and Debugging","depth":2},{"value":"Integration Tests","depth":2}],"frontmatter":{"permalink":"/docs/handbook/intellij","title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Developer Setup","oneline":"Setting up Lingua Franca for developers.","permalink":"/docs/handbook/developer-setup"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","permalink":"/docs/handbook/regression-tests"}}}},"pageContext":{"id":"5-developer-intellij-setup","slug":"/docs/handbook/intellij","repoPath":"/packages/documentation/copy/en/developer/Developer IntelliJ Setup.md","previousID":"81b7347d-2806-53c1-91ad-c7b12d062d3c","nextID":"b004db16-2d0f-54e3-a2a8-d9d6f510eea1","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/language-specification/page-data.json b/page-data/docs/handbook/language-specification/page-data.json index 5ffc7d0e0..342c647a2 100644 --- a/page-data/docs/handbook/language-specification/page-data.json +++ b/page-data/docs/handbook/language-specification/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/language-specification","result":{"data":{"markdownRemark":{"id":"71ea83b3-e77d-5dfb-a0d9-c5f2dfa023e6","excerpt":"A Lingua Franca file, which has a .lf extension, contains the following: One target specification. Zero or more import statements. One or more reactor blocks…","html":"

    A Lingua Franca file, which has a .lf extension, contains the following:

    \n\n

    If one of the reactors in the file is designated main or federated, then the file defines an executable application. Otherwise, it defines one or more library reactors that can be imported into other LF files. For example, an LF file might be structured like this:

    \n
    target C;\nmain reactor C {\n    a = new A();\n    b = new B();\n    a.y -> b.x;\n}\nreactor A {\n    output y;\n    ...\n}\nreactor B {\n    input x;\n    ...\n}\n
    \n

    The name of the main reactor (C above) is optional. If given, it must match the filename (C.lf in the above example).

    \n

    This example specifies and instantiates two reactors, one of which sends messages to the other. A minimal but complete Lingua Franca file with one reactor is this:

    \n
    target C;\nmain reactor HelloWorld {\n    reaction(startup) {=\n        printf("Hello World.\\n");\n    =}\n}\n
    \n

    See the C target documentation for details about this example.

    \n

    Target Language Specification

    \n

    Every Lingua Franca program begins with a target language specification that specifies the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca compiler.

    \n

    Import Statement

    \n

    An import statement has the form:

    \n
    \n

    import { reactor1, reactor2 as alias2, […] } frompath“;

    \n
    \n

    where path specifies another Lingua Franca file relative to the location of the current file.

    \n

    Reactor Block

    \n

    A reactor is a software component that reacts to input events, timer events, and internal events. It has private state variables that are not visible to any other reactor. Its reactions can consist of altering its own state, sending messages to other reactors, or affecting the environment through some kind of actuation or side effect.

    \n

    The general structure of a reactor block is as follows:

    \n
    \n

    reactor name (parameters) {
    >   state declarations
    >   method declarations
    >   input declarations
    >   output declarations
    >   timer declarations
    >   action declarations
    >   reaction declarations
    >   contained reactors
    >    …
    \n}

    \n
    \n

    Parameter, inputs, outputs, timers, actions, and contained reactors all have names, and the names are required to be distinct from one another.

    \n

    If the reactor keyword is preceded by main, then this reactor will be instantiated and run by the generated code. If an imported LF file contains a main reactor, that reactor is ignored. Only reactors that not designated main are imported. This makes it easy to create a library of reusable reactors that each come with a test case or demonstration in the form of a main reactor.

    \n

    Parameter Declaration

    \n

    A reactor class definition can define parameters as follows:

    \n
    \n

    reactor ClassName(paramName1:type(expr), paramName2:type(expr)) {
    >    …
    \n}

    \n
    \n

    Each parameter may have a type annotation, written :type, and must have a default value, written (expr).

    \n

    The type annotation specifies a type in the target language, which is necessary for some target languages. For instance in C you might write

    \n
    reactor Foo(size: int(100)) {\n    ...\n}\n
    \n
    \nIntroduction to basic LF types and expressions... click to expand\n

    One useful type predefined by LF is the time type, which represents time durations. Values of this type may be written with time expressions, like 100 msec or 1 second (see Basic expressions for a reference).

    \n

    For instance, you can write the following in any target language:

    \n
    reactor Foo(period: time(100 msec)) {\n    ...\n}\n
    \n

    Container types may also be written e.g. int[], which is translated to a target-specific array or list type. The acceptable expressions for these types vary across targets (see Complex expressions), for instance in C, you can initialize an array parameter as follows:

    \n
    reactor Foo(my_array:int[](1, 2, 3)) {\n   ...\n}\n
    \n

    If the type or expression uses syntax that Lingua Franca does not support, you can use {= ... =} delimiters to enclose them and escape them. For instance to have a 2-dimensional array as a parameter in C:

    \n
    reactor Foo(param:{= int[][] =}({= { {1}, {2} } =})) {\n    ...\n}\n
    \n

    Both int[][] and {% raw %}{{1}, {2}} {% endraw %} are C fragments here, not LF.

    \n
    \n

    Other forms for types and expressions are described in LF types and LF expressions.

    \n

    How parameters may be used in the body of a reaction depends on the target. For example, in the C target, a self struct is provided that contains the parameter values. The following example illustrates this:

    \n
    target C;\nreactor Gain(scale:int(2)) {\n    input x:int;\n    output y:int;\n    reaction(x) -> y {=\n        lf_set(y, x->value * self->scale);\n    =}\n}\n
    \n

    This reactor, given any input event x will produce an output y with value equal to the input scaled by the scale parameter. The default value of the scale parameter is 2, but this can be changed when the Gain reactor is instantiated. The lf_set() is the mechanism provided by the C target for setting the value of outputs. The parameter scale and input x are just referenced in the C code as shown above.

    \n

    State Declaration

    \n

    A state declaration has one of the forms:

    \n
    \n

    state name:type(initial_value);
    \nstate name(parameter);

    \n
    \n

    In the first form, the type annotation is only required in some targets. The initial value may be any expression, including a special initializer forms.

    \n

    In the second form, the state variable inherits its type from the specified parameter, which also provides the initial value for the state variable.

    \n

    How state variables may be used in the body of a reaction depends on the target. For example, in the C target, a self struct is provided that contains the state values. The following example illustrates this:

    \n
    reactor Count {\n\toutput c:int;\n\ttimer t(0, 1 sec);\n\tstate i:int(0);\n\treaction(t) -> c {=\n\t\t(self->i)++;\n\t\tlf_set(c, self->i);\n\t=}\n}\n
    \n

    Method Declaration

    \n

    A method declaration has one of the forms:

    \n
    \n

    method name();
    \nmethod name():type;
    \nmethod name(arg1_name:arg1type, _arg2_name:arg2type, …);\nmethod _name(arg1_name:arg1type, _arg2_name:arg2type, …):_type;

    \n
    \n

    The first form defines a method with no arguments and no return value. The second form defines a method with the return type type but no arguments. The third form defines a method with arguments given by their name and type, but without a return value. Finally, the fourth form is similar to the third, but adds a return type.

    \n

    The method keyword can optionally be prefixed with the const qualifier, which indicates that the method is “read-only”. This is relevant for some target languages such as C++.

    \n

    See the C++ documentation for a usage example.

    \n

    Input Declaration

    \n

    An input declaration has the form:

    \n
    \n

    input name:type;

    \n
    \n

    The Gain reactor given above provides an example. The type is just like parameter types.

    \n

    An input may have the modifier mutable, as follows:

    \n
    \n

    mutable input name:type

    \n
    \n

    This is a directive to the code generator indicating that reactions that read this input will also modify the value of the input. Without this modifier, inputs are immutable; modifying them is disallowed. The precise mechanism for making use of mutable inputs is target-language specific. See, for example, the C language target.

    \n

    An input port may have more than one channel. See multiports documentation.

    \n

    Output Declaration

    \n

    An output declaration has the form:

    \n
    \n

    output name:type;

    \n
    \n

    The Gain reactor given above provides an example. The type is just like parameter types.

    \n

    An output port may have more than one channel. See multiports documentation.

    \n

    Timer Declaration

    \n

    A timer, like an input and an action, causes reactions to be invoked. Unlike an action, it is triggered automatically by the scheduler. This declaration is used when you want to invoke reactions once at specific times or periodically. A timer declaration has the form:

    \n
    \n

    timer name(offset, period);

    \n
    \n

    For example,

    \n
    timer foo(10 msec, 100 msec);\n
    \n

    This specifies a timer named foo that will first trigger 10 milliseconds after the start of execution and then repeatedly trigger at intervals of 100 ms. The units are optional, and if they are not included, then the number will be interpreted in a target-dependent way. The units supported are the same as in parameter declarations described above.

    \n

    The times specified are logical times. Specifically, if two timers have the same offset and period, then they are logically simultaneous. No observer will be able to see that one timer has triggered and the other has not. Even though these are logical times, the runtime system will make an effort to align those times to physical times. Such alignment can never be perfect, and its accuracy will depend on the execution platform.

    \n

    Both arguments are optional, with both having default value zero. An offset of zero or greater specifies the minimum time delay between the time at the start of execution and when the action is triggered. The period is zero or greater, where a value of zero specifies that the reactions should be triggered exactly once,\nwhereas a value greater than zero specifies that they should be triggered repeatedly with the period given.

    \n

    To cause a reaction to be invoked at the start of execution, a special startup trigger is provided:

    \n
    reactor Foo {\n    reaction(startup) {=\n        ... perform initialization ...\n    =}\n}\n
    \n

    The startup trigger is equivalent to a timer with no offset or period.

    \n

    Action Declaration

    \n

    An action, like an input, can cause reactions to be invoked. Whereas inputs are provided by other reactors, actions are scheduled by this reactor itself, either in response to some observed external event or as a delayed response to some input event. The action can be scheduled by a reactor by invoking a schedule function in a reaction or in an asynchronous callback function.

    \n

    An action declaration is either physical or logical:

    \n
    \n

    physical action name(min_delay, min_spacing, policy):type;
    > logical action name(min_delay, min_spacing, policy):type;

    \n
    \n

    The min_delay, min_spacing, and policy are all optional. If only one argument is given in parentheses, then it is interpreted as an min_delay, if two are given, then they are interpreted as min_delay and min_spacing, etc. The min_delay and min_spacing have to be a time value. The policy argument is a string that can be one of the following: 'defer' (default), 'drop', or 'replace'.

    \n

    An action will trigger at a logical time that depends on the arguments given to the schedule function, the min_delay, min_spacing, and policy arguments above, and whether the action is physical or logical.

    \n

    If the logical keyword is given, then the tag assigned to the event resulting from a call to schedule function is computed as follows. First, let t be the current logical time. For a logical action, the schedule function must be invoked from within a reaction (synchronously), so t is just the logical time of that reaction.

    \n

    The (preliminary) tag of the action is then just t plus min_delay plus the offset argument to schedule function.

    \n

    If the physical keyword is given, then the physical clock on the local platform is used as the timestamp assigned to the action. Moreover, for a physical action, unlike a logical action, the schedule function can be invoked from outside of any reaction (asynchronously), e.g. from an interrupt service routine or callback function.

    \n

    If a min_spacing has been declared, then a minimum distance between the tags of two subsequently scheduled events on the same action is enforced. If the preliminary tag is closer to the tag of the previously scheduled event (if there is one), then policy determines how the given constraints is enforced.

    \n
      \n
    • 'drop': the new event is dropped and schedule returns without having modified the event queue.
    • \n
    • 'replace': the payload of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default 'defer' policy is applied.
    • \n
    • 'defer': the event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the tag of the preceding event is t_prev, then the tag of the new event simply becomes t_prev + min_spacing.
    • \n
    \n

    Note that while the 'defer' policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.

    \n

    In all cases, the logical time of a new event will always be strictly greater than the logical time at which it is scheduled by at least one microstep (see the Time section).

    \n

    The default min_delay is zero. The default min_spacing is undefined (meaning that no minimum spacing constraint is enforced). If a min_spacing is defined, it has to be strictly greater than zero, and greater than or equal to the time precision of the target (for the C target, it is one nanosecond).

    \n

    The min_delay parameter in the action declaration is static (set at compile time), while the offset parameter given to the schedule function may be dynamically set at runtime. Hence, for static analysis and scheduling, the action’s’ min_delay parameter can be assumed to be a minimum delay for analysis purposes.

    \n

    Discussion

    \n

    Logical actions are used to schedule events at a future logical time relative to the current logical time. Physical time is ignored. They must be scheduled within reactions, and the timestamp of the scheduled event will be relative to the current logical time of the reaction that schedules them. It is an error to schedule a logical action asynchronously, outside of the context of a reaction. Asynchronous actions are required to be physical.

    \n

    Physical actions are typically used to assign timestamps to externally triggered events, such as the arrival of a network message or the acquisition of sensor data, where the time at which these external events occurs is of interest. There are (at least) three interesting use cases:

    \n
      \n
    1. An asynchronous event, such as a callback function or interrupt service routine (ISR), is invoked at a physical time t and schedules an action with timestamp T=t. To get this behavior, just set the physical action to have min_delay = 0 and call the schedule function with offset = 0. The min_spacing can be useful here to prevent these external events from overwhelming the software system.
    2. \n
    3. A periodic task that is occasionally modified by a sporadic sensor. In this case, you can set min_delay = period and call schedule with offset = 0. The resulting timestamp of the sporadic sensor event will always align with the periodic events. This is similar to periodic polling, but without the overhead of polling the sensor when nothing interesting is happening.
    4. \n
    5. You can impose a minimum physical time delay between an event’s occurrence, such as a push of a button, and system response by adjusting the offset.
    6. \n
    \n

    Actions With Values

    \n

    If an action is declared with a type, then it can carry a value, a data value passed to the schedule function. This value will be available to any reaction that is triggered by the action. The specific mechanism, however, is target-language dependent. See the C target for an example.

    \n

    Reaction Declaration

    \n

    A reaction is defined within a reactor using the following syntax:

    \n
    \n

    reaction(triggers) uses -> effects {=
    >    … target language code …
    \n=}

    \n
    \n

    The uses and effects fields are optional. A simple example appears in the “hello world” example given above:

    \n
        reaction(t) {=\n        printf("Hello World.\\n");\n    =}\n
    \n

    In this example, t is a trigger (a timer named t). When that timer fires, the reaction will be invoked. Triggers can be timers, inputs, outputs of contained reactors, or actions. A comma-separated list of triggers can be given, in which case any of the specified triggers can trigger the reaction. If, at any logical time instant, more than one of the triggers fires, the reaction will nevertheless be invoked only once.

    \n

    The uses field specifies inputs that the reaction observes but that do not trigger the reaction. This field can also be a comma-separated list of inputs. Since the input does not trigger the reaction, the body of the reaction will normally need to test for presence of the input before using it. How to do this is target specific. See how this is done in the C target.

    \n

    The effects field, occurring after the right arrow, declares which outputs and actions the target code may produce or schedule. The effects field may also specify inputs of contained reactors, provided that those inputs do not have any other sources of data. These declarations make it possible for the reaction to send outputs or enable future actions, but they do not require that the reaction code do that.

    \n

    Target Code

    \n

    The body of the reaction is code in the target language surrounded by {= and =}. This code is not parsed by the Lingua Franca compiler. It is used verbatim in the program that is generated.

    \n

    The target provides language-dependent mechanisms for referring to inputs, outputs, and actions in the target code. These mechanisms can be different in each target language, but all target languages provide the same basic set of mechanisms. These mechanisms include:

    \n
      \n
    • Obtaining the current logical time (logical time does not advance during the execution of a reaction, so the execution of a reaction is logically instantaneous).
    • \n
    • Determining whether inputs are present at the current logical time and reading their value if they are. If a reaction is triggered by exactly one input, then that input will always be present. But if there are multiple triggers, or if the input is specified in the uses field, then the input may not be present when the reaction is invoked.
    • \n
    • Setting output values. Reactions in a reactor may set an output value more than once at any instant of logical time, but only the last of the values set will be sent on the output port.
    • \n
    • Scheduling future actions.
    • \n
    \n

    In the C target, for example, the following reactor will add two inputs if they are present at the time of a reaction:

    \n
    reactor Add {\n    input in1:int;\n    input in2:int;\n    output out:int;\n    reaction(in1, in2) -> out {=\n        int result = 0;\n        if (in1->is_present) result += in1->value;\n        if (in2->is_present) result += in2->value;\n        lf_set(out, result);\n    =}\n}\n
    \n

    See the C target for an example of how these things are specified in C.

    \n

    NOTE: if a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is undefined and may be target dependent. In the C target, as of this writing, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the data type of the input. In the TS target, the value will be undefined, a legitimate value in TypeScript.

    \n

    Scheduling Future Reactions

    \n

    Each target language provides some mechanism for scheduling future reactions. Typically, this takes the form of a schedule function that takes as an argument an action, a time interval, and (perhaps optionally), a payload. For example, in the C target, in the following program, each reaction to the timer t schedules another reaction to occur 100 msec later:

    \n
    target C;\nmain reactor Schedule {\n    timer t(0, 1 sec);\n    logical action a;\n    reaction(t) -> a {=\n        schedule(a, MSEC(100));\n    =}\n    reaction(a) {=\n        printf("Nanoseconds since start: %lld.\\n", lf_time_logical_elapsed());\n    =}\n}\n
    \n

    When executed, this will produce the following output:

    \n
    Start execution at time Sun Aug 11 04:11:57 2019\nplus 919310000 nanoseconds.\nNanoseconds since start: 100000000.\nNanoseconds since start: 1100000000.\nNanoseconds since start: 2100000000.\n...
    \n

    This action has no data type and carries no value, but, as explained below, an action can carry a value.

    \n

    Asynchronous Callbacks

    \n

    In targets that support multitasking, the schedule function, which schedules future reactions, may be safely invoked on a physical action in code that is not part of a reaction. For example, in the multithreaded version of the C target, schedule may be invoked in an interrupt service routine. The reaction(s) that are scheduled are guaranteed to occur at a time that is strictly larger than the current logical time of any reactions that are being interrupted.

    \n

    Superdense Time

    \n

    Lingua Franca uses a concept known as superdense time, where two time values that appear to be the same are not logically simultaneous. At every logical time value, for example midnight on January 1, 1970, there exist a logical sequence of microsteps that are not simultaneous. The Microsteps example illustrates this:

    \n
    target C;\nreactor Destination {\n    input x:int;\n    input y:int;\n    reaction(x, y) {=\n        printf("Time since start: %lld.\\n", lf_time_logical_elapsed());\n        if (x->is_present) {\n            printf("  x is present.\\n");\n        }\n        if (y->is_present) {\n            printf("  y is present.\\n");\n        }\n    =}\n}\nmain reactor Microsteps {\n    timer start;\n    logical action repeat;\n    d = new Destination();\n    reaction(start) -> d.x, repeat {=\n        lf_set(d.x, 1);\n        schedule(repeat, 0);\n    =}\n    reaction(repeat) -> d.y {=\n        lf_set(d.y, 1);\n    =}\n}\n
    \n

    The Destination reactor has two inputs, x and y, and it simply reports at each logical time where either is present what is the logical time and which is present. The Microsteps reactor initializes things with a reaction to the one-time timer event start by sending data to the x input of Destination. It then schedules a repeat action.

    \n

    Note that time delay in the call to schedule is zero. However, any reaction scheduled by schedule is required to occur strictly later than current logical time. In Lingua Franca, this is handled by scheduling the repeat reaction to occur one microstep later. The output printed, therefore, will look like this:

    \n
    Time since start: 0.\n  x is present.\nTime since start: 0.\n  y is present.
    \n

    Note that the numerical time reported by get_elapsed_logical_time() has not advanced in the second reaction, but the fact that x is not present in the second reaction proves that the first reaction and the second are not logically simultaneous. The second occurs one microstep later.

    \n

    Note that it is possible to write code that will prevent logical time from advancing except by microsteps. For example, we could replace the reaction to repeat in Main with this one:

    \n
        reaction(repeat) -> d.y, repeat {=\n        lf_set(d.y, 1);\n        schedule(repeat, 0);\n    =}\n
    \n

    This would create what is known as a stuttering Zeno condition, where logical time cannot advance. The output will be an unbounded sequence like this:

    \n
    Time since start: 0.\n  x is present.\nTime since start: 0.\n  y is present.\nTime since start: 0.\n  y is present.\nTime since start: 0.\n  y is present.\n...
    \n

    Startup and Shutdown Reactions

    \n

    Two special triggers are supported, startup and shutdown. A reaction that specifies the startup trigger will be invoked at the start of execution of the model. The following two syntaxes have exactly the same effect:

    \n
        reaction(startup) {= ... =}\n
    \n

    and

    \n
        timer t;\n    reaction(t) {= ... =}\n
    \n

    In other words, startup is a timer that triggers once at the first logical time of execution. As with any other reaction, the reaction can also be triggered by inputs and can produce outputs or schedule actions.

    \n

    The shutdown trigger is slightly different. A shutdown reaction is specified as follows:

    \n
       reaction(shutdown) {= ... =}\n
    \n

    This reaction will be invoked when the program terminates normally (there are no more events, some reaction has called a request_stop() utility provided in the target language, or the execution was specified to last a finite logical time). The reaction will be invoked at a logical time one microstep later than the last logical time of the execution. In other words, the presence of this reaction means that the program will execute one extra logical time cycle beyond what it would have otherwise, and that logical time is one microstep later than what would have otherwise been the last logical time.

    \n

    If the reaction produces outputs, then downstream reactors will also be invoked at that later logical time. If the reaction schedules future reactions, those will be ignored. After the completion of this final logical time cycle, one microstep later than the normal termination, the program will exit.

    \n

    Contained Reactors

    \n

    Reactors can contain instances of other reactors defined in the same file or in an imported file. Assuming the above Count reactor is stored in a file Count.lf, then CountTest is an example that imports and instantiates it to test the reactor:

    \n
    target C;\nimport Count.lf;\nreactor Test {\n    input c:int;\n    state i:int(0);\n    reaction(c) {=\n        printf("Received %d.\\n", c->value);\n        (self->i)++;\n        if (c->value != self->i) {\n            printf("ERROR: Expected %d but got %d\\n.", self->i, c->value);\n            exit(1);\n        }\n    =}\n    reaction(shutdown) {=\n        if (self->i != 4) {\n            printf("ERROR: Test should have reacted 4 times, but reacted %d times.\\n", self->i);\n            exit(2);\n        }\n    =}\n}\n\nmain reactor CountTest {\n    count = new Count();\n    test = new Test();\n    count.out -> test.c;\n}\n
    \n

    An instance is created with the syntax:

    \n
    \n

    instance_name = new class_name(parameters);

    \n
    \n

    A bank with several instances can be created in one such statement, as explained in the banks of reactors documentation.

    \n

    The parameters argument has the form:

    \n
    \n

    parameter1_name = parameter1_value, parameter2_name = parameter2_value, …

    \n
    \n

    Connections between ports are specified with the syntax:

    \n
    \n

    output_port -> input_port

    \n
    \n

    where the ports are either instance_name.port_name or just port_name, where the latter form denotes a port belonging to the reactor that contains the instances.

    \n

    Physical Connections

    \n

    A subtle and rarely used variant is a physical connection, denoted ~>. In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender, so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

    \n

    Connections with Delays

    \n

    Connections may include a logical delay using the after keyword, as follows:

    \n
    \n

    output_port -> input_port after 10 msec

    \n
    \n

    This means that the logical time of the message delivered to the input_port will be 10 milliseconds larger than the logical time of the reaction that wrote to output_port. If the time value is greater than zero, then the event will appear at microstep 0. If it is equal to zero, then it will appear at the current microstep plus one.

    \n

    When there are multiports or banks of reactors, several channels can be connected with a single connection statement. See Multiports and Banks of Reactors.

    \n

    The following example defines a reactor that adds a counting sequence to its input. It uses the above Count and Add reactors (see Hierarchy2):

    \n
    import Count.lf;\nimport Add.lf;\nreactor AddCount {\n    input in:int;\n    output out:int;\n    count = new Count();\n    add = new Add();\n    in -> add.in1;\n    count.out -> add.in2;\n    add.out -> out;\n}\n
    \n

    A reactor that contains other reactors may, within a reaction, send data to the contained reactor. The following example illustrates this (see SendingInside):

    \n
    target C;\nreactor Printer {\n\tinput x:int;\n\treaction(x) {=\n\t\tprintf("Inside reactor received: %d\\n", x->value);\n\t=}\n}\nmain reactor SendingInside {\n\tp = new Printer();\n\treaction(startup) -> p.x {=\n\t\tlf_set(p.x, 1);\n\t=}\n}\n
    \n

    Running this will print:

    \n
    Inside reactor received: 1
    \n

    Deadlines

    \n

    Lingua Franca includes a notion of a deadline, which is a relation between logical time and physical time. Specifically, a program may specify that the invocation of a reaction must occur within some physical-time interval of the logical timestamp of the message. If a reaction is invoked at logical time 12 noon, for example, and the reaction has a deadline of one hour, then the reaction is required to be invoked before the physical-time clock of the execution platform reaches 1 PM. If the deadline is violated, then the specified deadline handler is invoked instead of the reaction. For example (see Deadline):

    \n
    reactor Deadline() {\n    input x:int;\n    output d:int; // Produced if the deadline is violated.\n    reaction(x) -> d {=\n        printf("Normal reaction.\\n");\n    =} deadline(10 msec) {=\n        printf("Deadline violation detected.\\n");\n        lf_set(d, x->value);\n    =}\n
    \n

    This reactor specifies a deadline of 10 milliseconds (this can be a parameter of the reactor). If the reaction to x is triggered later in physical time than 10 msec past the timestamp of x, then the second body of code is executed instead of the first. That second body of code has access to anything the first body of code has access to, including the input x and the output d. The output can be used to notify the rest of the system that a deadline violation occurred.

    \n

    The amount of the deadline, of course, can be given by a parameter.

    \n

    A sometimes useful pattern is when a container reactor reacts to deadline violations in a contained reactor. The DeadlineHandledAbove example illustrates this:

    \n
    target C;\nreactor Deadline() {\n    input x:int;\n    output deadline_violation:bool;\n    reaction(x) -> deadline_violation {=\n        ... normal code to execute ...\n    =} deadline(100 msec) {=\n        printf("Deadline violation detected.\\n");\n        lf_set(deadline_violation, true);\n    =}\n}\nmain reactor DeadlineHandledAbove {\n    d = new Deadline();\n    ...\n    reaction(d.deadline_violation) {=\n        ... handle the deadline violation ...\n    =}\n}\n
    \n

    Comments

    \n

    Lingua Franca files can have C/C++/Java-style comments and/or Python-style comments. All of the following are valid comments:

    \n
        // Single-line C-style comment.\n    /*\n       Multi-line C-style comment.\n     */\n    # Single-line Python-style comment.\n    '''\n       Multi-line Python-style comment.\n    '''\n
    \n

    Appendix: LF types

    \n

    Type annotations may be written in many places in LF, including parameter declarations, state variable declarations, input and output declarations. In some targets, they are required, because the target language requires them too.

    \n

    Assigning meaning to type annotations is entirely offloaded to the target compiler, as LF does not feature a type system (yet?). However, LF’s syntax for types supports a few idioms that have target-specific meaning. Types may have the following forms:

    \n
      \n
    • the time type is reserved by LF, its values represent time durations. The time type accepts time expressions for values, e.g. 100 msec, or 0 (see Basic expressions for a reference).
    • \n
    • identifiers are valid types (e.g. int, size_t), and may be followed by type arguments (e.g. vector<int>).
    • \n
    • the syntactic forms type[] and type[integer] correspond to target-specific array types. The second form is available only in languages which support fixed-size array types (e.g. in C++, std::array<5>).
    • \n
    • the syntactic form {= some type =} allows writing an arbitrary type as target code. This is useful in target languages which have complex type grammar (e.g. in TypeScript, {= int | null =}).
    • \n
    \n

    Also note that to use strings conveniently in the C target, the “type” string is an alias for {=char*=}.

    \n

    (Types ending with a * are treated specially by the C target. See Sending and Receiving Arrays and Structs in the C target documentation.)

    \n

    Appendix: LF expressions

    \n

    A subset of LF syntax is used to write expressions, which represent target language values. Expressions are used in state variable initializers, default values for parameters, and parameter assignments.

    \n

    Expressions in LF support only simple forms, that are intended to be common across languages. Their precise meaning (e.g. the target language types they are compatible with) is target-specific and not specified here.

    \n

    Basic expressions

    \n

    The most basic expression forms, which are supported by all target languages, are the following:

    \n
      \n
    • \n

      Literals:

      \n
        \n
      • Numeric literals, e.g. 1, -120, 1.5. Note that the sign, if any, is part of the literal and must not be separated by whitespace.
      • \n
      • String literals, e.g. \"abcd\". String literals always use double-quotes, even in languages which support other forms (like Python).
      • \n
      • Character literals. e.g. 'a'. Single-quoted literals must be exactly one character long —even in Python.
      • \n
      • Boolean literals: true, false, True, False. The latter two are there for Python.
      • \n
      \n
    • \n
    • \n

      Parameter references, which are simple identifiers (e.g. foo). Any identifier in expression position must refer to a parameter of the enclosing reactor.

      \n
    • \n
    • \n

      Time values, e.g. 1 msec or 10 seconds. The syntax of time values is integer time_unit, where time_unit is one of the following

      \n
        \n
      • nsec: nanoseconds
      • \n
      • usec: microseconds
      • \n
      • msec: milliseconds
      • \n
      • sec or second: seconds
      • \n
      • minute: 60 seconds
      • \n
      • hour: 60 minutes
      • \n
      • day: 24 hours
      • \n
      • week: 7 days
      • \n
      \n

      Each of these units also support a pluralized version (e.g. nsecs, minutes, days), which means the same thing.

      \n

      The time value 0 may have no unit. Except in this specific case, the unit is always required.

      \n

      Time values are compatible with the time type.

      \n
    • \n
    • \n

      Escaped target-language expression, e.g. {= foo() =}. This syntax is used to write any expression which does not fall into one of the other forms described here. The contents are not parsed and are used verbatim in the generated file.

      \n

      The variables in scope are target-specific.

      \n
    • \n
    \n

    Complex expressions

    \n

    Some targets may make use of a few other syntactic forms for expressions. These syntactic forms may be ascribed a different meaning by different targets, to keep the source language close in meaning to the target language.

    \n

    We describe here these syntactic forms and what meaning they have in each target.

    \n
      \n
    • Bracket-list syntax, e.g. [1, 2, 3]. This syntax is used to create a list in Python. It is not supported by any other target at the moment.\n
      state x([1,2,3])\n
      \n
    • \n
    \n

    Initializer pseudo-expressions

    \n

    Some “expression” forms are only acceptable as the initializer of a state variable or parameter, but not in other places (like inside a list expression). These are

    \n
      \n
    • \n

      Tuple syntax, e.g. (1, 2, 3). This syntax is used:

      \n
        \n
      • \n

        in the Python target, to create a tuple value. Tuples are different from lists in that they are immutable.

        \n
      • \n
      • \n

        in C++, to pass arguments to a constructor:

        \n
        state x: int[](1,2);\n
        \n

        In that example, the initializer expression is translated to new std::vector(1,2). See also C++ target documentation.

        \n
      • \n
      • \n

        in C and all other targets, to create a target-specific array value. In the Python target, this is accomplished by the bracket-list syntax [1,2,3] instead. Note that to create a zero- or one-element array, fat braces are usually required. For instance in C:

        \n
      • \n
      \n
        state x: int[](1,2,3); // creates an int array, basically `int x[] = {1,2,3};`\n  state x: int[](1);     // `int x[] = 1;` - type error!\n  state x: int[]({= {1} =})  // one element array: `int x[] = {1};`\n
      \n
    • \n
    • \n

      Brace-list syntax, e.g. {1, 2, 3}. This syntax is at the moment only supported by the C++ target. It’s used to initialize a vector with the initializer list syntax instead of a constructor call.

      \n
    • \n
    ","headings":[{"value":"Target Language Specification","depth":2},{"value":"Import Statement","depth":2},{"value":"Reactor Block","depth":2},{"value":"Parameter Declaration","depth":3},{"value":"State Declaration","depth":3},{"value":"Method Declaration","depth":3},{"value":"Input Declaration","depth":3},{"value":"Output Declaration","depth":3},{"value":"Timer Declaration","depth":3},{"value":"Action Declaration","depth":3},{"value":"Discussion","depth":4},{"value":"Actions With Values","depth":3},{"value":"Reaction Declaration","depth":2},{"value":"Target Code","depth":3},{"value":"Scheduling Future Reactions","depth":3},{"value":"Asynchronous Callbacks","depth":3},{"value":"Superdense Time","depth":3},{"value":"Startup and Shutdown Reactions","depth":3},{"value":"Contained Reactors","depth":2},{"value":"Physical Connections","depth":3},{"value":"Connections with Delays","depth":3},{"value":"Deadlines","depth":2},{"value":"Comments","depth":2},{"value":"Appendix: LF types","depth":2},{"value":"Appendix: LF expressions","depth":2},{"value":"Basic expressions","depth":3},{"value":"Complex expressions","depth":3},{"value":"Initializer pseudo-expressions","depth":4}],"frontmatter":{"permalink":"/docs/handbook/language-specification","title":"Language Specification","oneline":"Language Specification for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/language-specification","repoPath":"/packages/documentation/copy/en/obsolete/Language Specification.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/language-specification","result":{"data":{"markdownRemark":{"id":"71ea83b3-e77d-5dfb-a0d9-c5f2dfa023e6","excerpt":"A Lingua Franca file, which has a .lf extension, contains the following: One target specification. Zero or more import statements. One or more reactor blocks…","html":"

    A Lingua Franca file, which has a .lf extension, contains the following:

    \n\n

    If one of the reactors in the file is designated main or federated, then the file defines an executable application. Otherwise, it defines one or more library reactors that can be imported into other LF files. For example, an LF file might be structured like this:

    \n
    target C;\nmain reactor C {\n    a = new A();\n    b = new B();\n    a.y -> b.x;\n}\nreactor A {\n    output y;\n    ...\n}\nreactor B {\n    input x;\n    ...\n}\n
    \n

    The name of the main reactor (C above) is optional. If given, it must match the filename (C.lf in the above example).

    \n

    This example specifies and instantiates two reactors, one of which sends messages to the other. A minimal but complete Lingua Franca file with one reactor is this:

    \n
    target C;\nmain reactor HelloWorld {\n    reaction(startup) {=\n        printf("Hello World.\\n");\n    =}\n}\n
    \n

    See the C target documentation for details about this example.

    \n

    Target Language Specification

    \n

    Every Lingua Franca program begins with a target language specification that specifies the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca compiler.

    \n

    Import Statement

    \n

    An import statement has the form:

    \n
    \n

    import { reactor1, reactor2 as alias2, […] } frompath“;

    \n
    \n

    where path specifies another Lingua Franca file relative to the location of the current file.

    \n

    Reactor Block

    \n

    A reactor is a software component that reacts to input events, timer events, and internal events. It has private state variables that are not visible to any other reactor. Its reactions can consist of altering its own state, sending messages to other reactors, or affecting the environment through some kind of actuation or side effect.

    \n

    The general structure of a reactor block is as follows:

    \n
    \n

    reactor name (parameters) {
    >   state declarations
    >   method declarations
    >   input declarations
    >   output declarations
    >   timer declarations
    >   action declarations
    >   reaction declarations
    >   contained reactors
    >    …
    \n}

    \n
    \n

    Parameter, inputs, outputs, timers, actions, and contained reactors all have names, and the names are required to be distinct from one another.

    \n

    If the reactor keyword is preceded by main, then this reactor will be instantiated and run by the generated code. If an imported LF file contains a main reactor, that reactor is ignored. Only reactors that not designated main are imported. This makes it easy to create a library of reusable reactors that each come with a test case or demonstration in the form of a main reactor.

    \n

    Parameter Declaration

    \n

    A reactor class definition can define parameters as follows:

    \n
    \n

    reactor ClassName(paramName1:type(expr), paramName2:type(expr)) {
    >    …
    \n}

    \n
    \n

    Each parameter may have a type annotation, written :type, and must have a default value, written (expr).

    \n

    The type annotation specifies a type in the target language, which is necessary for some target languages. For instance in C you might write

    \n
    reactor Foo(size: int(100)) {\n    ...\n}\n
    \n
    \nIntroduction to basic LF types and expressions... click to expand\n

    One useful type predefined by LF is the time type, which represents time durations. Values of this type may be written with time expressions, like 100 msec or 1 second (see Basic expressions for a reference).

    \n

    For instance, you can write the following in any target language:

    \n
    reactor Foo(period: time(100 msec)) {\n    ...\n}\n
    \n

    Container types may also be written e.g. int[], which is translated to a target-specific array or list type. The acceptable expressions for these types vary across targets (see Complex expressions), for instance in C, you can initialize an array parameter as follows:

    \n
    reactor Foo(my_array:int[](1, 2, 3)) {\n   ...\n}\n
    \n

    If the type or expression uses syntax that Lingua Franca does not support, you can use {= ... =} delimiters to enclose them and escape them. For instance to have a 2-dimensional array as a parameter in C:

    \n
    reactor Foo(param:{= int[][] =}({= { {1}, {2} } =})) {\n    ...\n}\n
    \n

    Both int[][] and {% raw %}{{1}, {2}} {% endraw %} are C fragments here, not LF.

    \n
    \n

    Other forms for types and expressions are described in LF types and LF expressions.

    \n

    How parameters may be used in the body of a reaction depends on the target. For example, in the C target, a self struct is provided that contains the parameter values. The following example illustrates this:

    \n
    target C;\nreactor Gain(scale:int(2)) {\n    input x:int;\n    output y:int;\n    reaction(x) -> y {=\n        lf_set(y, x->value * self->scale);\n    =}\n}\n
    \n

    This reactor, given any input event x will produce an output y with value equal to the input scaled by the scale parameter. The default value of the scale parameter is 2, but this can be changed when the Gain reactor is instantiated. The lf_set() is the mechanism provided by the C target for setting the value of outputs. The parameter scale and input x are just referenced in the C code as shown above.

    \n

    State Declaration

    \n

    A state declaration has one of the forms:

    \n
    \n

    state name:type(initial_value);
    \nstate name(parameter);

    \n
    \n

    In the first form, the type annotation is only required in some targets. The initial value may be any expression, including a special initializer forms.

    \n

    In the second form, the state variable inherits its type from the specified parameter, which also provides the initial value for the state variable.

    \n

    How state variables may be used in the body of a reaction depends on the target. For example, in the C target, a self struct is provided that contains the state values. The following example illustrates this:

    \n
    reactor Count {\n\toutput c:int;\n\ttimer t(0, 1 sec);\n\tstate i:int(0);\n\treaction(t) -> c {=\n\t\t(self->i)++;\n\t\tlf_set(c, self->i);\n\t=}\n}\n
    \n

    Method Declaration

    \n

    A method declaration has one of the forms:

    \n
    \n

    method name();
    \nmethod name():type;
    \nmethod name(arg1_name:arg1type, _arg2_name:arg2type, …);\nmethod _name(arg1_name:arg1type, _arg2_name:arg2type, …):_type;

    \n
    \n

    The first form defines a method with no arguments and no return value. The second form defines a method with the return type type but no arguments. The third form defines a method with arguments given by their name and type, but without a return value. Finally, the fourth form is similar to the third, but adds a return type.

    \n

    The method keyword can optionally be prefixed with the const qualifier, which indicates that the method is “read-only”. This is relevant for some target languages such as C++.

    \n

    See the C++ documentation for a usage example.

    \n

    Input Declaration

    \n

    An input declaration has the form:

    \n
    \n

    input name:type;

    \n
    \n

    The Gain reactor given above provides an example. The type is just like parameter types.

    \n

    An input may have the modifier mutable, as follows:

    \n
    \n

    mutable input name:type

    \n
    \n

    This is a directive to the code generator indicating that reactions that read this input will also modify the value of the input. Without this modifier, inputs are immutable; modifying them is disallowed. The precise mechanism for making use of mutable inputs is target-language specific. See, for example, the C language target.

    \n

    An input port may have more than one channel. See multiports documentation.

    \n

    Output Declaration

    \n

    An output declaration has the form:

    \n
    \n

    output name:type;

    \n
    \n

    The Gain reactor given above provides an example. The type is just like parameter types.

    \n

    An output port may have more than one channel. See multiports documentation.

    \n

    Timer Declaration

    \n

    A timer, like an input and an action, causes reactions to be invoked. Unlike an action, it is triggered automatically by the scheduler. This declaration is used when you want to invoke reactions once at specific times or periodically. A timer declaration has the form:

    \n
    \n

    timer name(offset, period);

    \n
    \n

    For example,

    \n
    timer foo(10 msec, 100 msec);\n
    \n

    This specifies a timer named foo that will first trigger 10 milliseconds after the start of execution and then repeatedly trigger at intervals of 100 ms. The units are optional, and if they are not included, then the number will be interpreted in a target-dependent way. The units supported are the same as in parameter declarations described above.

    \n

    The times specified are logical times. Specifically, if two timers have the same offset and period, then they are logically simultaneous. No observer will be able to see that one timer has triggered and the other has not. Even though these are logical times, the runtime system will make an effort to align those times to physical times. Such alignment can never be perfect, and its accuracy will depend on the execution platform.

    \n

    Both arguments are optional, with both having default value zero. An offset of zero or greater specifies the minimum time delay between the time at the start of execution and when the action is triggered. The period is zero or greater, where a value of zero specifies that the reactions should be triggered exactly once,\nwhereas a value greater than zero specifies that they should be triggered repeatedly with the period given.

    \n

    To cause a reaction to be invoked at the start of execution, a special startup trigger is provided:

    \n
    reactor Foo {\n    reaction(startup) {=\n        ... perform initialization ...\n    =}\n}\n
    \n

    The startup trigger is equivalent to a timer with no offset or period.

    \n

    Action Declaration

    \n

    An action, like an input, can cause reactions to be invoked. Whereas inputs are provided by other reactors, actions are scheduled by this reactor itself, either in response to some observed external event or as a delayed response to some input event. The action can be scheduled by a reactor by invoking a schedule function in a reaction or in an asynchronous callback function.

    \n

    An action declaration is either physical or logical:

    \n
    \n

    physical action name(min_delay, min_spacing, policy):type;
    > logical action name(min_delay, min_spacing, policy):type;

    \n
    \n

    The min_delay, min_spacing, and policy are all optional. If only one argument is given in parentheses, then it is interpreted as an min_delay, if two are given, then they are interpreted as min_delay and min_spacing, etc. The min_delay and min_spacing have to be a time value. The policy argument is a string that can be one of the following: 'defer' (default), 'drop', or 'replace'.

    \n

    An action will trigger at a logical time that depends on the arguments given to the schedule function, the min_delay, min_spacing, and policy arguments above, and whether the action is physical or logical.

    \n

    If the logical keyword is given, then the tag assigned to the event resulting from a call to schedule function is computed as follows. First, let t be the current logical time. For a logical action, the schedule function must be invoked from within a reaction (synchronously), so t is just the logical time of that reaction.

    \n

    The (preliminary) tag of the action is then just t plus min_delay plus the offset argument to schedule function.

    \n

    If the physical keyword is given, then the physical clock on the local platform is used as the timestamp assigned to the action. Moreover, for a physical action, unlike a logical action, the schedule function can be invoked from outside of any reaction (asynchronously), e.g. from an interrupt service routine or callback function.

    \n

    If a min_spacing has been declared, then a minimum distance between the tags of two subsequently scheduled events on the same action is enforced. If the preliminary tag is closer to the tag of the previously scheduled event (if there is one), then policy determines how the given constraints is enforced.

    \n
      \n
    • 'drop': the new event is dropped and schedule returns without having modified the event queue.
    • \n
    • 'replace': the payload of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default 'defer' policy is applied.
    • \n
    • 'defer': the event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the tag of the preceding event is t_prev, then the tag of the new event simply becomes t_prev + min_spacing.
    • \n
    \n

    Note that while the 'defer' policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.

    \n

    In all cases, the logical time of a new event will always be strictly greater than the logical time at which it is scheduled by at least one microstep (see the Time section).

    \n

    The default min_delay is zero. The default min_spacing is undefined (meaning that no minimum spacing constraint is enforced). If a min_spacing is defined, it has to be strictly greater than zero, and greater than or equal to the time precision of the target (for the C target, it is one nanosecond).

    \n

    The min_delay parameter in the action declaration is static (set at compile time), while the offset parameter given to the schedule function may be dynamically set at runtime. Hence, for static analysis and scheduling, the action’s’ min_delay parameter can be assumed to be a minimum delay for analysis purposes.

    \n

    Discussion

    \n

    Logical actions are used to schedule events at a future logical time relative to the current logical time. Physical time is ignored. They must be scheduled within reactions, and the timestamp of the scheduled event will be relative to the current logical time of the reaction that schedules them. It is an error to schedule a logical action asynchronously, outside of the context of a reaction. Asynchronous actions are required to be physical.

    \n

    Physical actions are typically used to assign timestamps to externally triggered events, such as the arrival of a network message or the acquisition of sensor data, where the time at which these external events occurs is of interest. There are (at least) three interesting use cases:

    \n
      \n
    1. An asynchronous event, such as a callback function or interrupt service routine (ISR), is invoked at a physical time t and schedules an action with timestamp T=t. To get this behavior, just set the physical action to have min_delay = 0 and call the schedule function with offset = 0. The min_spacing can be useful here to prevent these external events from overwhelming the software system.
    2. \n
    3. A periodic task that is occasionally modified by a sporadic sensor. In this case, you can set min_delay = period and call schedule with offset = 0. The resulting timestamp of the sporadic sensor event will always align with the periodic events. This is similar to periodic polling, but without the overhead of polling the sensor when nothing interesting is happening.
    4. \n
    5. You can impose a minimum physical time delay between an event’s occurrence, such as a push of a button, and system response by adjusting the offset.
    6. \n
    \n

    Actions With Values

    \n

    If an action is declared with a type, then it can carry a value, a data value passed to the schedule function. This value will be available to any reaction that is triggered by the action. The specific mechanism, however, is target-language dependent. See the C target for an example.

    \n

    Reaction Declaration

    \n

    A reaction is defined within a reactor using the following syntax:

    \n
    \n

    reaction(triggers) uses -> effects {=
    >    … target language code …
    \n=}

    \n
    \n

    The uses and effects fields are optional. A simple example appears in the “hello world” example given above:

    \n
        reaction(t) {=\n        printf("Hello World.\\n");\n    =}\n
    \n

    In this example, t is a trigger (a timer named t). When that timer fires, the reaction will be invoked. Triggers can be timers, inputs, outputs of contained reactors, or actions. A comma-separated list of triggers can be given, in which case any of the specified triggers can trigger the reaction. If, at any logical time instant, more than one of the triggers fires, the reaction will nevertheless be invoked only once.

    \n

    The uses field specifies inputs that the reaction observes but that do not trigger the reaction. This field can also be a comma-separated list of inputs. Since the input does not trigger the reaction, the body of the reaction will normally need to test for presence of the input before using it. How to do this is target specific. See how this is done in the C target.

    \n

    The effects field, occurring after the right arrow, declares which outputs and actions the target code may produce or schedule. The effects field may also specify inputs of contained reactors, provided that those inputs do not have any other sources of data. These declarations make it possible for the reaction to send outputs or enable future actions, but they do not require that the reaction code do that.

    \n

    Target Code

    \n

    The body of the reaction is code in the target language surrounded by {= and =}. This code is not parsed by the Lingua Franca compiler. It is used verbatim in the program that is generated.

    \n

    The target provides language-dependent mechanisms for referring to inputs, outputs, and actions in the target code. These mechanisms can be different in each target language, but all target languages provide the same basic set of mechanisms. These mechanisms include:

    \n
      \n
    • Obtaining the current logical time (logical time does not advance during the execution of a reaction, so the execution of a reaction is logically instantaneous).
    • \n
    • Determining whether inputs are present at the current logical time and reading their value if they are. If a reaction is triggered by exactly one input, then that input will always be present. But if there are multiple triggers, or if the input is specified in the uses field, then the input may not be present when the reaction is invoked.
    • \n
    • Setting output values. Reactions in a reactor may set an output value more than once at any instant of logical time, but only the last of the values set will be sent on the output port.
    • \n
    • Scheduling future actions.
    • \n
    \n

    In the C target, for example, the following reactor will add two inputs if they are present at the time of a reaction:

    \n
    reactor Add {\n    input in1:int;\n    input in2:int;\n    output out:int;\n    reaction(in1, in2) -> out {=\n        int result = 0;\n        if (in1->is_present) result += in1->value;\n        if (in2->is_present) result += in2->value;\n        lf_set(out, result);\n    =}\n}\n
    \n

    See the C target for an example of how these things are specified in C.

    \n

    NOTE: if a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is undefined and may be target dependent. In the C target, as of this writing, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the data type of the input. In the TS target, the value will be undefined, a legitimate value in TypeScript.

    \n

    Scheduling Future Reactions

    \n

    Each target language provides some mechanism for scheduling future reactions. Typically, this takes the form of a schedule function that takes as an argument an action, a time interval, and (perhaps optionally), a payload. For example, in the C target, in the following program, each reaction to the timer t schedules another reaction to occur 100 msec later:

    \n
    target C;\nmain reactor Schedule {\n    timer t(0, 1 sec);\n    logical action a;\n    reaction(t) -> a {=\n        schedule(a, MSEC(100));\n    =}\n    reaction(a) {=\n        printf("Nanoseconds since start: %lld.\\n", lf_time_logical_elapsed());\n    =}\n}\n
    \n

    When executed, this will produce the following output:

    \n
    Start execution at time Sun Aug 11 04:11:57 2019\nplus 919310000 nanoseconds.\nNanoseconds since start: 100000000.\nNanoseconds since start: 1100000000.\nNanoseconds since start: 2100000000.\n...
    \n

    This action has no data type and carries no value, but, as explained below, an action can carry a value.

    \n

    Asynchronous Callbacks

    \n

    In targets that support multitasking, the schedule function, which schedules future reactions, may be safely invoked on a physical action in code that is not part of a reaction. For example, in the multithreaded version of the C target, schedule may be invoked in an interrupt service routine. The reaction(s) that are scheduled are guaranteed to occur at a time that is strictly larger than the current logical time of any reactions that are being interrupted.

    \n

    Superdense Time

    \n

    Lingua Franca uses a concept known as superdense time, where two time values that appear to be the same are not logically simultaneous. At every logical time value, for example midnight on January 1, 1970, there exist a logical sequence of microsteps that are not simultaneous. The Microsteps example illustrates this:

    \n
    target C;\nreactor Destination {\n    input x:int;\n    input y:int;\n    reaction(x, y) {=\n        printf("Time since start: %lld.\\n", lf_time_logical_elapsed());\n        if (x->is_present) {\n            printf("  x is present.\\n");\n        }\n        if (y->is_present) {\n            printf("  y is present.\\n");\n        }\n    =}\n}\nmain reactor Microsteps {\n    timer start;\n    logical action repeat;\n    d = new Destination();\n    reaction(start) -> d.x, repeat {=\n        lf_set(d.x, 1);\n        schedule(repeat, 0);\n    =}\n    reaction(repeat) -> d.y {=\n        lf_set(d.y, 1);\n    =}\n}\n
    \n

    The Destination reactor has two inputs, x and y, and it simply reports at each logical time where either is present what is the logical time and which is present. The Microsteps reactor initializes things with a reaction to the one-time timer event start by sending data to the x input of Destination. It then schedules a repeat action.

    \n

    Note that time delay in the call to schedule is zero. However, any reaction scheduled by schedule is required to occur strictly later than current logical time. In Lingua Franca, this is handled by scheduling the repeat reaction to occur one microstep later. The output printed, therefore, will look like this:

    \n
    Time since start: 0.\n  x is present.\nTime since start: 0.\n  y is present.
    \n

    Note that the numerical time reported by get_elapsed_logical_time() has not advanced in the second reaction, but the fact that x is not present in the second reaction proves that the first reaction and the second are not logically simultaneous. The second occurs one microstep later.

    \n

    Note that it is possible to write code that will prevent logical time from advancing except by microsteps. For example, we could replace the reaction to repeat in Main with this one:

    \n
        reaction(repeat) -> d.y, repeat {=\n        lf_set(d.y, 1);\n        schedule(repeat, 0);\n    =}\n
    \n

    This would create what is known as a stuttering Zeno condition, where logical time cannot advance. The output will be an unbounded sequence like this:

    \n
    Time since start: 0.\n  x is present.\nTime since start: 0.\n  y is present.\nTime since start: 0.\n  y is present.\nTime since start: 0.\n  y is present.\n...
    \n

    Startup and Shutdown Reactions

    \n

    Two special triggers are supported, startup and shutdown. A reaction that specifies the startup trigger will be invoked at the start of execution of the model. The following two syntaxes have exactly the same effect:

    \n
        reaction(startup) {= ... =}\n
    \n

    and

    \n
        timer t;\n    reaction(t) {= ... =}\n
    \n

    In other words, startup is a timer that triggers once at the first logical time of execution. As with any other reaction, the reaction can also be triggered by inputs and can produce outputs or schedule actions.

    \n

    The shutdown trigger is slightly different. A shutdown reaction is specified as follows:

    \n
       reaction(shutdown) {= ... =}\n
    \n

    This reaction will be invoked when the program terminates normally (there are no more events, some reaction has called a request_stop() utility provided in the target language, or the execution was specified to last a finite logical time). The reaction will be invoked at a logical time one microstep later than the last logical time of the execution. In other words, the presence of this reaction means that the program will execute one extra logical time cycle beyond what it would have otherwise, and that logical time is one microstep later than what would have otherwise been the last logical time.

    \n

    If the reaction produces outputs, then downstream reactors will also be invoked at that later logical time. If the reaction schedules future reactions, those will be ignored. After the completion of this final logical time cycle, one microstep later than the normal termination, the program will exit.

    \n

    Contained Reactors

    \n

    Reactors can contain instances of other reactors defined in the same file or in an imported file. Assuming the above Count reactor is stored in a file Count.lf, then CountTest is an example that imports and instantiates it to test the reactor:

    \n
    target C;\nimport Count.lf;\nreactor Test {\n    input c:int;\n    state i:int(0);\n    reaction(c) {=\n        printf("Received %d.\\n", c->value);\n        (self->i)++;\n        if (c->value != self->i) {\n            printf("ERROR: Expected %d but got %d\\n.", self->i, c->value);\n            exit(1);\n        }\n    =}\n    reaction(shutdown) {=\n        if (self->i != 4) {\n            printf("ERROR: Test should have reacted 4 times, but reacted %d times.\\n", self->i);\n            exit(2);\n        }\n    =}\n}\n\nmain reactor CountTest {\n    count = new Count();\n    test = new Test();\n    count.out -> test.c;\n}\n
    \n

    An instance is created with the syntax:

    \n
    \n

    instance_name = new class_name(parameters);

    \n
    \n

    A bank with several instances can be created in one such statement, as explained in the banks of reactors documentation.

    \n

    The parameters argument has the form:

    \n
    \n

    parameter1_name = parameter1_value, parameter2_name = parameter2_value, …

    \n
    \n

    Connections between ports are specified with the syntax:

    \n
    \n

    output_port -> input_port

    \n
    \n

    where the ports are either instance_name.port_name or just port_name, where the latter form denotes a port belonging to the reactor that contains the instances.

    \n

    Physical Connections

    \n

    A subtle and rarely used variant is a physical connection, denoted ~>. In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender, so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).

    \n

    Connections with Delays

    \n

    Connections may include a logical delay using the after keyword, as follows:

    \n
    \n

    output_port -> input_port after 10 msec

    \n
    \n

    This means that the logical time of the message delivered to the input_port will be 10 milliseconds larger than the logical time of the reaction that wrote to output_port. If the time value is greater than zero, then the event will appear at microstep 0. If it is equal to zero, then it will appear at the current microstep plus one.

    \n

    When there are multiports or banks of reactors, several channels can be connected with a single connection statement. See Multiports and Banks of Reactors.

    \n

    The following example defines a reactor that adds a counting sequence to its input. It uses the above Count and Add reactors (see Hierarchy2):

    \n
    import Count.lf;\nimport Add.lf;\nreactor AddCount {\n    input in:int;\n    output out:int;\n    count = new Count();\n    add = new Add();\n    in -> add.in1;\n    count.out -> add.in2;\n    add.out -> out;\n}\n
    \n

    A reactor that contains other reactors may, within a reaction, send data to the contained reactor. The following example illustrates this (see SendingInside):

    \n
    target C;\nreactor Printer {\n\tinput x:int;\n\treaction(x) {=\n\t\tprintf("Inside reactor received: %d\\n", x->value);\n\t=}\n}\nmain reactor SendingInside {\n\tp = new Printer();\n\treaction(startup) -> p.x {=\n\t\tlf_set(p.x, 1);\n\t=}\n}\n
    \n

    Running this will print:

    \n
    Inside reactor received: 1
    \n

    Deadlines

    \n

    Lingua Franca includes a notion of a deadline, which is a relation between logical time and physical time. Specifically, a program may specify that the invocation of a reaction must occur within some physical-time interval of the logical timestamp of the message. If a reaction is invoked at logical time 12 noon, for example, and the reaction has a deadline of one hour, then the reaction is required to be invoked before the physical-time clock of the execution platform reaches 1 PM. If the deadline is violated, then the specified deadline handler is invoked instead of the reaction. For example (see Deadline):

    \n
    reactor Deadline() {\n    input x:int;\n    output d:int; // Produced if the deadline is violated.\n    reaction(x) -> d {=\n        printf("Normal reaction.\\n");\n    =} deadline(10 msec) {=\n        printf("Deadline violation detected.\\n");\n        lf_set(d, x->value);\n    =}\n
    \n

    This reactor specifies a deadline of 10 milliseconds (this can be a parameter of the reactor). If the reaction to x is triggered later in physical time than 10 msec past the timestamp of x, then the second body of code is executed instead of the first. That second body of code has access to anything the first body of code has access to, including the input x and the output d. The output can be used to notify the rest of the system that a deadline violation occurred.

    \n

    The amount of the deadline, of course, can be given by a parameter.

    \n

    A sometimes useful pattern is when a container reactor reacts to deadline violations in a contained reactor. The DeadlineHandledAbove example illustrates this:

    \n
    target C;\nreactor Deadline() {\n    input x:int;\n    output deadline_violation:bool;\n    reaction(x) -> deadline_violation {=\n        ... normal code to execute ...\n    =} deadline(100 msec) {=\n        printf("Deadline violation detected.\\n");\n        lf_set(deadline_violation, true);\n    =}\n}\nmain reactor DeadlineHandledAbove {\n    d = new Deadline();\n    ...\n    reaction(d.deadline_violation) {=\n        ... handle the deadline violation ...\n    =}\n}\n
    \n

    Comments

    \n

    Lingua Franca files can have C/C++/Java-style comments and/or Python-style comments. All of the following are valid comments:

    \n
        // Single-line C-style comment.\n    /*\n       Multi-line C-style comment.\n     */\n    # Single-line Python-style comment.\n    '''\n       Multi-line Python-style comment.\n    '''\n
    \n

    Appendix: LF types

    \n

    Type annotations may be written in many places in LF, including parameter declarations, state variable declarations, input and output declarations. In some targets, they are required, because the target language requires them too.

    \n

    Assigning meaning to type annotations is entirely offloaded to the target compiler, as LF does not feature a type system (yet?). However, LF’s syntax for types supports a few idioms that have target-specific meaning. Types may have the following forms:

    \n
      \n
    • the time type is reserved by LF, its values represent time durations. The time type accepts time expressions for values, e.g. 100 msec, or 0 (see Basic expressions for a reference).
    • \n
    • identifiers are valid types (e.g. int, size_t), and may be followed by type arguments (e.g. vector<int>).
    • \n
    • the syntactic forms type[] and type[integer] correspond to target-specific array types. The second form is available only in languages which support fixed-size array types (e.g. in C++, std::array<5>).
    • \n
    • the syntactic form {= some type =} allows writing an arbitrary type as target code. This is useful in target languages which have complex type grammar (e.g. in TypeScript, {= int | null =}).
    • \n
    \n

    Also note that to use strings conveniently in the C target, the “type” string is an alias for {=char*=}.

    \n

    (Types ending with a * are treated specially by the C target. See Sending and Receiving Arrays and Structs in the C target documentation.)

    \n

    Appendix: LF expressions

    \n

    A subset of LF syntax is used to write expressions, which represent target language values. Expressions are used in state variable initializers, default values for parameters, and parameter assignments.

    \n

    Expressions in LF support only simple forms, that are intended to be common across languages. Their precise meaning (e.g. the target language types they are compatible with) is target-specific and not specified here.

    \n

    Basic expressions

    \n

    The most basic expression forms, which are supported by all target languages, are the following:

    \n
      \n
    • \n

      Literals:

      \n
        \n
      • Numeric literals, e.g. 1, -120, 1.5. Note that the sign, if any, is part of the literal and must not be separated by whitespace.
      • \n
      • String literals, e.g. \"abcd\". String literals always use double-quotes, even in languages which support other forms (like Python).
      • \n
      • Character literals. e.g. 'a'. Single-quoted literals must be exactly one character long —even in Python.
      • \n
      • Boolean literals: true, false, True, False. The latter two are there for Python.
      • \n
      \n
    • \n
    • \n

      Parameter references, which are simple identifiers (e.g. foo). Any identifier in expression position must refer to a parameter of the enclosing reactor.

      \n
    • \n
    • \n

      Time values, e.g. 1 msec or 10 seconds. The syntax of time values is integer time_unit, where time_unit is one of the following

      \n
        \n
      • nsec: nanoseconds
      • \n
      • usec: microseconds
      • \n
      • msec: milliseconds
      • \n
      • sec or second: seconds
      • \n
      • minute: 60 seconds
      • \n
      • hour: 60 minutes
      • \n
      • day: 24 hours
      • \n
      • week: 7 days
      • \n
      \n

      Each of these units also support a pluralized version (e.g. nsecs, minutes, days), which means the same thing.

      \n

      The time value 0 may have no unit. Except in this specific case, the unit is always required.

      \n

      Time values are compatible with the time type.

      \n
    • \n
    • \n

      Escaped target-language expression, e.g. {= foo() =}. This syntax is used to write any expression which does not fall into one of the other forms described here. The contents are not parsed and are used verbatim in the generated file.

      \n

      The variables in scope are target-specific.

      \n
    • \n
    \n

    Complex expressions

    \n

    Some targets may make use of a few other syntactic forms for expressions. These syntactic forms may be ascribed a different meaning by different targets, to keep the source language close in meaning to the target language.

    \n

    We describe here these syntactic forms and what meaning they have in each target.

    \n
      \n
    • Bracket-list syntax, e.g. [1, 2, 3]. This syntax is used to create a list in Python. It is not supported by any other target at the moment.\n
      state x([1,2,3])\n
      \n
    • \n
    \n

    Initializer pseudo-expressions

    \n

    Some “expression” forms are only acceptable as the initializer of a state variable or parameter, but not in other places (like inside a list expression). These are

    \n
      \n
    • \n

      Tuple syntax, e.g. (1, 2, 3). This syntax is used:

      \n
        \n
      • \n

        in the Python target, to create a tuple value. Tuples are different from lists in that they are immutable.

        \n
      • \n
      • \n

        in C++, to pass arguments to a constructor:

        \n
        state x: int[](1,2);\n
        \n

        In that example, the initializer expression is translated to new std::vector(1,2). See also C++ target documentation.

        \n
      • \n
      • \n

        in C and all other targets, to create a target-specific array value. In the Python target, this is accomplished by the bracket-list syntax [1,2,3] instead. Note that to create a zero- or one-element array, fat braces are usually required. For instance in C:

        \n
      • \n
      \n
        state x: int[](1,2,3); // creates an int array, basically `int x[] = {1,2,3};`\n  state x: int[](1);     // `int x[] = 1;` - type error!\n  state x: int[]({= {1} =})  // one element array: `int x[] = {1};`\n
      \n
    • \n
    • \n

      Brace-list syntax, e.g. {1, 2, 3}. This syntax is at the moment only supported by the C++ target. It’s used to initialize a vector with the initializer list syntax instead of a constructor call.

      \n
    • \n
    ","headings":[{"value":"Target Language Specification","depth":2},{"value":"Import Statement","depth":2},{"value":"Reactor Block","depth":2},{"value":"Parameter Declaration","depth":3},{"value":"State Declaration","depth":3},{"value":"Method Declaration","depth":3},{"value":"Input Declaration","depth":3},{"value":"Output Declaration","depth":3},{"value":"Timer Declaration","depth":3},{"value":"Action Declaration","depth":3},{"value":"Discussion","depth":4},{"value":"Actions With Values","depth":3},{"value":"Reaction Declaration","depth":2},{"value":"Target Code","depth":3},{"value":"Scheduling Future Reactions","depth":3},{"value":"Asynchronous Callbacks","depth":3},{"value":"Superdense Time","depth":3},{"value":"Startup and Shutdown Reactions","depth":3},{"value":"Contained Reactors","depth":2},{"value":"Physical Connections","depth":3},{"value":"Connections with Delays","depth":3},{"value":"Deadlines","depth":2},{"value":"Comments","depth":2},{"value":"Appendix: LF types","depth":2},{"value":"Appendix: LF expressions","depth":2},{"value":"Basic expressions","depth":3},{"value":"Complex expressions","depth":3},{"value":"Initializer pseudo-expressions","depth":4}],"frontmatter":{"permalink":"/docs/handbook/language-specification","title":"Language Specification","oneline":"Language Specification for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/language-specification","repoPath":"/packages/documentation/copy/en/obsolete/Language Specification.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/logical-execution-time/page-data.json b/page-data/docs/handbook/logical-execution-time/page-data.json index 69a8643f1..8de6856ce 100644 --- a/page-data/docs/handbook/logical-execution-time/page-data.json +++ b/page-data/docs/handbook/logical-execution-time/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/logical-execution-time","result":{"data":{"markdownRemark":{"id":"6f6a2de3-be2a-5e00-ba14-2bb0974cdec1","excerpt":"FIXME","html":"

    FIXME

    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/logical-execution-time","title":"Logical Execution Time","oneline":"Reactions that take non-zero logical time to execute.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/logical-execution-time","repoPath":"/packages/documentation/copy/en/less-developed/Logical Execution Time.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/logical-execution-time","result":{"data":{"markdownRemark":{"id":"6f6a2de3-be2a-5e00-ba14-2bb0974cdec1","excerpt":"FIXME","html":"

    FIXME

    ","headings":[],"frontmatter":{"permalink":"/docs/handbook/logical-execution-time","title":"Logical Execution Time","oneline":"Reactions that take non-zero logical time to execute.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/logical-execution-time","repoPath":"/packages/documentation/copy/en/less-developed/Logical Execution Time.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/methods/page-data.json b/page-data/docs/handbook/methods/page-data.json index 35ed57569..d009e43b4 100644 --- a/page-data/docs/handbook/methods/page-data.json +++ b/page-data/docs/handbook/methods/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/methods","result":{"data":{"markdownRemark":{"id":"dd7daec6-0b6f-5466-ad7a-8b8332d3f262","excerpt":"Method Declaration The $target-language$ target does not currently support methods. Sometimes logic needs to be shared between reactions. In this case, methods…","html":"

    Method Declaration

    \n
    \n

    The $target-language$ target does not currently support methods.

    \n
    \n
    \n
    \n

    Sometimes logic needs to be shared between reactions. In this case, methods can be used to implement the shared logic, and these methods can then be called from reaction bodies. A method declaration has one of the forms:

    \n
      method <name>() {= ... =}\n  method <name>():<type> {= ... =}\n  method <name>(<argument_name>:<type>, ...) {= ... =}\n  method <name>(<argument_name>:<type>, ...):<type> {= ... =}\n
    \n

    The first form defines a method with no arguments and no return value. The second form defines a method with the return type <type> but no arguments. The third form defines a method with a comma-separated list of arguments given by their name and type, but without a return value. Finally, the fourth form is similar to the third, but adds a return type.

    \n
    \n
    \n

    A method declaration has the forms:

    \n
      method <name>() {= ... =}\n
    \n
    \n
    \n

    The $method$ keyword can optionally be prefixed with the $const$ qualifier, which indicates that the method has only read access to the reactor’s state.

    \n
    \n

    Methods are particularly useful in reactors that need to perform certain operations on state variables and/or parameters that are shared between reactions or that are too complex to be implemented in a single reaction. Analogous to class methods, methods in LF can access all state variables and parameters, and can be invoked from all reaction bodies or from other methods. Methods may also recursively invoke themselves. Consider the following example:

    \n

    $start(Methods)$

    \n
    target C\nmain reactor Methods {\n  state foo: int = 2\n  method getFoo(): int {=\n    return self->foo;\n  =}\n  method add(x: int) {=\n    self->foo += x;\n  =}\n  reaction(startup) {=\n    lf_print("Foo is initialized to %d", getFoo());\n    add(40);\n    lf_print("2 + 40 = %d", getFoo());\n  =}\n}\n
    \n
    target Cpp\nmain reactor Methods {\n  state foo: int(2)\n  const method getFoo(): int {=\n    return foo;\n  =}\n  method add(x: int) {=\n    foo += x;\n  =}\n  reaction(startup) {=\n    std::cout << "Foo is initialized to " << getFoo() << '\\n';\n    add(40);\n    std::cout << "2 + 40 = " << getFoo() << '\\n';\n  =}\n}\n
    \n
    target Python\nmain reactor Methods {\n  state foo = 2\n  method getFoo() {=\n    return self.foo\n  =}\n  method add(x) {=\n    self.foo += x\n  =}\n  reaction(startup) {=\n    print(f"Foo is initialized to {self.getFoo()}.")\n    self.add(40)\n    print(f"2 + 40 = {self.getFoo()}.")\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/Methods.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/Methods.lf\n
    \n

    $end(Methods)$

    \n

    This reactor defines two methods getFoo and add.\n\ngetFoo is qualified as a const method, which indicates that it has read-only\naccess to the state variables. This is directly translated to a C++ const method\nin the code generation process.\n\nThe getFoo method receives no arguments and returns an integer (int)\nindicating the current value of the foo state variable. The add method\nreturns nothing\n\n(void)\n\nand receives one integer argument, which it uses to increment foo. Both\nmethods are visible in all reactions of the reactor. In this example, the\nreaction to startup calls both methods in order to read and modify its state.

    \n
    ","headings":[{"value":"Method Declaration","depth":2}],"frontmatter":{"permalink":"/docs/handbook/methods","title":"Methods","oneline":"Methods in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Reactions","oneline":"Reactions in Lingua Franca.","permalink":"/docs/handbook/reactions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","permalink":"/docs/handbook/causality-loops"}}}},"pageContext":{"id":"1-methods","slug":"/docs/handbook/methods","repoPath":"/packages/documentation/copy/en/topics/Methods.md","previousID":"25ac2513-8979-52dd-9176-b0db61f55dc9","nextID":"de5af6b0-de72-5890-9668-c4f000ffdb2c","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/methods","result":{"data":{"markdownRemark":{"id":"dd7daec6-0b6f-5466-ad7a-8b8332d3f262","excerpt":"Method Declaration The $target-language$ target does not currently support methods. Sometimes logic needs to be shared between reactions. In this case, methods…","html":"

    Method Declaration

    \n
    \n

    The $target-language$ target does not currently support methods.

    \n
    \n
    \n
    \n

    Sometimes logic needs to be shared between reactions. In this case, methods can be used to implement the shared logic, and these methods can then be called from reaction bodies. A method declaration has one of the forms:

    \n
      method <name>() {= ... =}\n  method <name>():<type> {= ... =}\n  method <name>(<argument_name>:<type>, ...) {= ... =}\n  method <name>(<argument_name>:<type>, ...):<type> {= ... =}\n
    \n

    The first form defines a method with no arguments and no return value. The second form defines a method with the return type <type> but no arguments. The third form defines a method with a comma-separated list of arguments given by their name and type, but without a return value. Finally, the fourth form is similar to the third, but adds a return type.

    \n
    \n
    \n

    A method declaration has the forms:

    \n
      method <name>() {= ... =}\n
    \n
    \n
    \n

    The $method$ keyword can optionally be prefixed with the $const$ qualifier, which indicates that the method has only read access to the reactor’s state.

    \n
    \n

    Methods are particularly useful in reactors that need to perform certain operations on state variables and/or parameters that are shared between reactions or that are too complex to be implemented in a single reaction. Analogous to class methods, methods in LF can access all state variables and parameters, and can be invoked from all reaction bodies or from other methods. Methods may also recursively invoke themselves. Consider the following example:

    \n

    $start(Methods)$

    \n
    target C\nmain reactor Methods {\n  state foo: int = 2\n  method getFoo(): int {=\n    return self->foo;\n  =}\n  method add(x: int) {=\n    self->foo += x;\n  =}\n  reaction(startup) {=\n    lf_print("Foo is initialized to %d", getFoo());\n    add(40);\n    lf_print("2 + 40 = %d", getFoo());\n  =}\n}\n
    \n
    target Cpp\nmain reactor Methods {\n  state foo: int(2)\n  const method getFoo(): int {=\n    return foo;\n  =}\n  method add(x: int) {=\n    foo += x;\n  =}\n  reaction(startup) {=\n    std::cout << "Foo is initialized to " << getFoo() << '\\n';\n    add(40);\n    std::cout << "2 + 40 = " << getFoo() << '\\n';\n  =}\n}\n
    \n
    target Python\nmain reactor Methods {\n  state foo = 2\n  method getFoo() {=\n    return self.foo\n  =}\n  method add(x) {=\n    self.foo += x\n  =}\n  reaction(startup) {=\n    print(f"Foo is initialized to {self.getFoo()}.")\n    self.add(40)\n    print(f"2 + 40 = {self.getFoo()}.")\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/Methods.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/Methods.lf\n
    \n

    $end(Methods)$

    \n

    This reactor defines two methods getFoo and add.\n\ngetFoo is qualified as a const method, which indicates that it has read-only\naccess to the state variables. This is directly translated to a C++ const method\nin the code generation process.\n\nThe getFoo method receives no arguments and returns an integer (int)\nindicating the current value of the foo state variable. The add method\nreturns nothing\n\n(void)\n\nand receives one integer argument, which it uses to increment foo. Both\nmethods are visible in all reactions of the reactor. In this example, the\nreaction to startup calls both methods in order to read and modify its state.

    \n
    ","headings":[{"value":"Method Declaration","depth":2}],"frontmatter":{"permalink":"/docs/handbook/methods","title":"Methods","oneline":"Methods in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Reactions","oneline":"Reactions in Lingua Franca.","permalink":"/docs/handbook/reactions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Causality Loops","oneline":"Causality loops in Lingua Franca.","permalink":"/docs/handbook/causality-loops"}}}},"pageContext":{"id":"1-methods","slug":"/docs/handbook/methods","repoPath":"/packages/documentation/copy/en/topics/Methods.md","previousID":"25ac2513-8979-52dd-9176-b0db61f55dc9","nextID":"de5af6b0-de72-5890-9668-c4f000ffdb2c","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/modal-models/page-data.json b/page-data/docs/handbook/modal-models/page-data.json index b373db4c6..7268de0f9 100644 --- a/page-data/docs/handbook/modal-models/page-data.json +++ b/page-data/docs/handbook/modal-models/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/modal-models","result":{"data":{"markdownRemark":{"id":"3ab36bba-9f0e-54a5-bfd0-55f5e5259ca5","excerpt":"Modal Reactors are currently not supported in $target-language$. $page-showing-target$ The basic idea of modal reactors is to partition a reactor into disjoint…","html":"
    \n

    Modal Reactors are currently not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    The basic idea of modal reactors is to partition a reactor into disjoint subsets of reactions (or other components) that are associated with mutually exclusive modes.\nIn a modal reactor, only a single mode can be active at a particular logical time instant, meaning that activity in other modes is automatically suspended.\nTransitioning between modes switches the reactor’s behavior and provides control over continuing or resetting the previous history of the entered mode.

    \n

    Syntax

    \n

    Modes can be defined in any reactor, except federated ones.\nEach mode requires a unique (per reactor) name and can declare contents that are local to this mode.\nThere must be exactly one mode marked as $initial$.

    \n
    reactor TwoModes {\n  ...\n  initial mode One {\n    ...\n  }\n  mode Two {\n    ...\n  }\n}\n
    \n

    A mode can contain local state variables, timers, actions, reactions, reactor instantiations, and connections.\nWhile modes cannot be nested in modes directly, hierarchical composition is possible through the instantiation of modal reactors.\nThe main exception in allowed contents in modes are port declarations, as these are only possible on reactor level.\nYet, modes share the scope with their reactor and, hence, can access ports, state variables, and parameters of the reactor.\nOnly the contents of other modes are excluded.\nA modal reactor can still have reactions, reactor instantiations, etc., that are not located in modes and will consequently be executed independently from mode activity.

    \n

    Mode transitions are declared within reactions.\nIf a reactor has modes, reactions inside modes are allowed to list them as effects.\nSuch an effect enables the use of the target language API to set the next mode.

    \n
    reaction(trig) -> Two {=\n  if (trig->value) {\n    lf_set_mode(Two)\n  }\n=}\n
    \n
    reaction(trig) -> Two {=\n  if trig.value:\n    Two.set()\n=}\n
    \n

    You can also specify the type of the transition by adding the modifier reset(<mode>) or history(<mode>) in the effects of the\nreaction signature (i.e., after the ->). For example, a history transition to the state Two is enabled by listing history(Two)\namong the effects of the reaction. The reset variant is implicitly assumed when the mode is listed without modifier.

    \n

    Execution Semantics

    \n

    The basic effect of modes is that only parts that are contained in the currently active mode (or outside any mode) are executed at any point in time.\nThis also holds for parts that are nested in multiple ancestor modes due to hierarchy; consequently, all those ancestors must be active in order to execute.\nReactions in inactive modes are simply not executed, while all components that model timing behavior, namely timers, scheduled actions, and delayed connections, are subject to a concept of local time.\nThat means while a mode is inactive, the progress of time is suspended locally.\nHow the timing components behave when a mode becomes active depends on the transition type.\nA mode can be reset upon entry, returning it to its initial state.\nAlternatively, it may be entered preserving its history, which only has an actual effect if the mode was active before.\nIn the latter case all timing components will continue their delays or period as if no time had passed during inactivity of the mode.\nThe following section will provide a more detailed explanation of this effect.

    \n

    Upon reactor startup, the initial mode of each modal reactor is active, others are inactive.\nIf at a tag (t, m), all reactions of this reactor and all its contents have finished executing, and a new mode was set in a reaction, the current mode will be deactivated and the new one will be activated for future execution.\nThis means no reaction of the newly active mode will execute at tag (t, m); the earliest possible reaction in the new mode occurs one microstep later, at (t, m+1).\nHence, if the newly active mode has for example a timer that will elapse with an offset of zero, it will trigger at (t, m+1).\nIn case the mode itself does not require an immediate execution in the next microstep, it depends on future events, just as in the normal behavior of LF.\nHence, modes in the same reactor are always mutually exclusive w.r.t. superdense time.

    \n

    A transition is triggered if a new mode is set in a reaction body.\nAs with setting output ports in reaction, a new mode can be set multiple times in the same or different reactions.\nIn the end, the fixed ordering of reactions determines the last effective value that will be used.\nThe new mode does not have to be a different one; it is possible for a mode to reset itself via a reset transition.

    \n

    In case a mode is entered with the reset behavior:

    \n
      \n
    • all contained modal reactors are reset to their initial mode (recursively),
    • \n
    • all contained timers are reset and start again awaiting their initial offset,
    • \n
    • all contained state variables that are marked for automatic reset are reset to their initial value,
    • \n
    • any contained reactions triggered by reset are executed, and
    • \n
    • all events (actions, timers, delayed connections) that were previously scheduled from within this mode are discarded.
    • \n
    \n

    Note that contained refers to all contents defined locally in the mode and in local reactor instances (recursively) that are not otherwise enclosed in modes of lower levels.

    \n

    Whenever a mode is entered with a reset transition, the subsequent timing behavior is as if the mode was never executed before.\nIf there are state variables that need to be reset or reinitialized, then this can be done in a reaction triggered by reset or by marking the state variable for automatic reset (e.g.,\nreset state x:int(0)\nFIXME\nreset state x(0)\nFIXME\nFIXME\n).\nState variables are not reset automatically to their initial conditions because it is idiomatic for reactors to allocate resources or initialize subsystems (e.g., allocate memory or sockets, register an interrupt, or start a server) in reactions triggered by the startup, and to store references to these resources in state variables.\nIf these were to be automatically reset, those references would be lost.

    \n

    On the other hand, if a mode has been active prior and is then re-entered via a history transition, no reset is performed.\nEvents originating from timers, scheduled actions, and delayed connections are adjusted to reflect a remaining delay equal to the remaining delay recorded at the instant the mode was previously deactivated.\nAs a consequence, a mode has a notion of local time that elapses only when the mode is active.

    \n

    Local Time

    \n

    From the perspective of timers and actions, time is suspended when a mode is inactive.\nThis also applies to indirectly nested reactors within modes and connections with logical delays, if their source lies within a mode.

    \n\"Illustration\n

    The above LF model illustrates the different characteristics of local time affecting timers and actions in the presence of the two transition types.

    \n

    It consists of two modes One (the initial mode) and Two, both in the Modal reactor.\nThe next input toggles between these modes and is controlled by a reaction at the top level that is triggered by the timer T.\nAfter one second, a mode switch is triggered periodically with a period one second.\nEach mode has a timer T1/T2 that triggers a reaction after an initial offset of 100 msec and then periodically after 750 msec.\nThis reaction then schedules a logical action with a delay of 500 msec (the actual target code does not add an additional delay over the minimum specified).\nThis action triggers the second reaction, which writes to the output out.\nThe main difference between the modes is that One is entered via a history transition, continuing its behavior, while Two is reset.\n(History behavior is indicated by an “H” on the transition edge because it enters into the entire history of the mode.)

    \n\"Illustration\n

    Above is the execution trace of the first 4 seconds of this program.\nBelow the timeline is the currently active mode and above the timeline are the model elements that are executed at certain points in time, together with indicating triggering and their relation through time.\nFor example, at 100 msec, the initial offset of timer T1 elapses, which leads to the scheduling of the logical action in this mode.\nThe action triggers the reaction 500 msec later, at 600 msec, and thus causes an output.\nThe timing diagram illustrates the different handling of time between history transitions and reset transitions.\nSpecifically, when mode One is re-entered via a history transition, at time 2000 msec, the action triggered by T1 before, at time 850 msec, resumes.\nIn contrast, when mode Two is re-entered via a reset transition, at time 3000 msec, the action triggered by T2 before, at time 1850 msec, gets discarded.

    \n\"Illustration\n

    The above plot illustrates the relation between global time in the environment and the localized time for each timer in the model.\nSince the top-level reactor TimingExample is not enclosed by any mode, its time always corresponds to the global time.\nMode One is the initial mode and hence progresses in sync with TimingExample for the first second.\nDuring inactivity of mode One the timer is suspended and does not advance in time.\nAt 2000 msec it continues relative to this time.\nT2 only starts advancing when the mode becomes active at 1000 msec.\nThe reentry via reset at 3000 msec causes the local time to be reset to zero.

    \n

    Startup and Shutdown

    \n

    A challenge for modal execution is the handling startup and shutdown behavior.\nThese are commonly used for managing memory for state variables, handling connections to sensors or actuators, or starting/joining external threads.\nIf reactions to these triggers are located inside modes they are subject to a special execution regime.

    \n

    First, startup reactions are invoked at most once at the first activation of a mode.\nSecond, shutdown reactions are executed when the reactor shuts down, irrespective of mode activity, but only if the enclosing modes have been activated at least once.\nHence, every startup has a corresponding shutdown.\nThird, as mentioned before, the new reset trigger for reactions can be used, if a startup behavior should be re-executed if a mode is entered with a reset transition.

    \n

    Note that this may have unexpected implications:

    \n
      \n
    • Startup behavior inside modes may occur during execution and not only at program start.
    • \n
    • Multiple shutdown reactions may be executed, bypassing mutual exclusion of modes.
    • \n
    • Reactors that are designed without consideration of modes and use only startup (not reset) to trigger an execution chain, may not work in modes and cease to function if re-entered with a reset.
    • \n
    \n
    ","headings":[{"value":"Syntax","depth":2},{"value":"Execution Semantics","depth":2},{"value":"Local Time","depth":3},{"value":"Startup and Shutdown","depth":3}],"frontmatter":{"permalink":"/docs/handbook/modal-models","title":"Modal Reactors","oneline":"Modal Reactors","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","permalink":"/docs/handbook/superdense-time"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Deadlines","oneline":"Deadlines in Lingua Franca.","permalink":"/docs/handbook/deadlines"}}}},"pageContext":{"id":"1-modal-reactors","slug":"/docs/handbook/modal-models","repoPath":"/packages/documentation/copy/en/topics/Modal Models.md","previousID":"1986cb1b-8feb-57bc-8088-50548df2f061","nextID":"cb17ff3f-7a86-5f3b-95f7-e7d1f4e920c0","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/modal-models","result":{"data":{"markdownRemark":{"id":"3ab36bba-9f0e-54a5-bfd0-55f5e5259ca5","excerpt":"Modal Reactors are currently not supported in $target-language$. $page-showing-target$ The basic idea of modal reactors is to partition a reactor into disjoint…","html":"
    \n

    Modal Reactors are currently not supported in $target-language$.

    \n
    \n
    \n

    $page-showing-target$

    \n

    The basic idea of modal reactors is to partition a reactor into disjoint subsets of reactions (or other components) that are associated with mutually exclusive modes.\nIn a modal reactor, only a single mode can be active at a particular logical time instant, meaning that activity in other modes is automatically suspended.\nTransitioning between modes switches the reactor’s behavior and provides control over continuing or resetting the previous history of the entered mode.

    \n

    Syntax

    \n

    Modes can be defined in any reactor, except federated ones.\nEach mode requires a unique (per reactor) name and can declare contents that are local to this mode.\nThere must be exactly one mode marked as $initial$.

    \n
    reactor TwoModes {\n  ...\n  initial mode One {\n    ...\n  }\n  mode Two {\n    ...\n  }\n}\n
    \n

    A mode can contain local state variables, timers, actions, reactions, reactor instantiations, and connections.\nWhile modes cannot be nested in modes directly, hierarchical composition is possible through the instantiation of modal reactors.\nThe main exception in allowed contents in modes are port declarations, as these are only possible on reactor level.\nYet, modes share the scope with their reactor and, hence, can access ports, state variables, and parameters of the reactor.\nOnly the contents of other modes are excluded.\nA modal reactor can still have reactions, reactor instantiations, etc., that are not located in modes and will consequently be executed independently from mode activity.

    \n

    Mode transitions are declared within reactions.\nIf a reactor has modes, reactions inside modes are allowed to list them as effects.\nSuch an effect enables the use of the target language API to set the next mode.

    \n
    reaction(trig) -> Two {=\n  if (trig->value) {\n    lf_set_mode(Two)\n  }\n=}\n
    \n
    reaction(trig) -> Two {=\n  if trig.value:\n    Two.set()\n=}\n
    \n

    You can also specify the type of the transition by adding the modifier reset(<mode>) or history(<mode>) in the effects of the\nreaction signature (i.e., after the ->). For example, a history transition to the state Two is enabled by listing history(Two)\namong the effects of the reaction. The reset variant is implicitly assumed when the mode is listed without modifier.

    \n

    Execution Semantics

    \n

    The basic effect of modes is that only parts that are contained in the currently active mode (or outside any mode) are executed at any point in time.\nThis also holds for parts that are nested in multiple ancestor modes due to hierarchy; consequently, all those ancestors must be active in order to execute.\nReactions in inactive modes are simply not executed, while all components that model timing behavior, namely timers, scheduled actions, and delayed connections, are subject to a concept of local time.\nThat means while a mode is inactive, the progress of time is suspended locally.\nHow the timing components behave when a mode becomes active depends on the transition type.\nA mode can be reset upon entry, returning it to its initial state.\nAlternatively, it may be entered preserving its history, which only has an actual effect if the mode was active before.\nIn the latter case all timing components will continue their delays or period as if no time had passed during inactivity of the mode.\nThe following section will provide a more detailed explanation of this effect.

    \n

    Upon reactor startup, the initial mode of each modal reactor is active, others are inactive.\nIf at a tag (t, m), all reactions of this reactor and all its contents have finished executing, and a new mode was set in a reaction, the current mode will be deactivated and the new one will be activated for future execution.\nThis means no reaction of the newly active mode will execute at tag (t, m); the earliest possible reaction in the new mode occurs one microstep later, at (t, m+1).\nHence, if the newly active mode has for example a timer that will elapse with an offset of zero, it will trigger at (t, m+1).\nIn case the mode itself does not require an immediate execution in the next microstep, it depends on future events, just as in the normal behavior of LF.\nHence, modes in the same reactor are always mutually exclusive w.r.t. superdense time.

    \n

    A transition is triggered if a new mode is set in a reaction body.\nAs with setting output ports in reaction, a new mode can be set multiple times in the same or different reactions.\nIn the end, the fixed ordering of reactions determines the last effective value that will be used.\nThe new mode does not have to be a different one; it is possible for a mode to reset itself via a reset transition.

    \n

    In case a mode is entered with the reset behavior:

    \n
      \n
    • all contained modal reactors are reset to their initial mode (recursively),
    • \n
    • all contained timers are reset and start again awaiting their initial offset,
    • \n
    • all contained state variables that are marked for automatic reset are reset to their initial value,
    • \n
    • any contained reactions triggered by reset are executed, and
    • \n
    • all events (actions, timers, delayed connections) that were previously scheduled from within this mode are discarded.
    • \n
    \n

    Note that contained refers to all contents defined locally in the mode and in local reactor instances (recursively) that are not otherwise enclosed in modes of lower levels.

    \n

    Whenever a mode is entered with a reset transition, the subsequent timing behavior is as if the mode was never executed before.\nIf there are state variables that need to be reset or reinitialized, then this can be done in a reaction triggered by reset or by marking the state variable for automatic reset (e.g.,\nreset state x:int(0)\nFIXME\nreset state x(0)\nFIXME\nFIXME\n).\nState variables are not reset automatically to their initial conditions because it is idiomatic for reactors to allocate resources or initialize subsystems (e.g., allocate memory or sockets, register an interrupt, or start a server) in reactions triggered by the startup, and to store references to these resources in state variables.\nIf these were to be automatically reset, those references would be lost.

    \n

    On the other hand, if a mode has been active prior and is then re-entered via a history transition, no reset is performed.\nEvents originating from timers, scheduled actions, and delayed connections are adjusted to reflect a remaining delay equal to the remaining delay recorded at the instant the mode was previously deactivated.\nAs a consequence, a mode has a notion of local time that elapses only when the mode is active.

    \n

    Local Time

    \n

    From the perspective of timers and actions, time is suspended when a mode is inactive.\nThis also applies to indirectly nested reactors within modes and connections with logical delays, if their source lies within a mode.

    \n\"Illustration\n

    The above LF model illustrates the different characteristics of local time affecting timers and actions in the presence of the two transition types.

    \n

    It consists of two modes One (the initial mode) and Two, both in the Modal reactor.\nThe next input toggles between these modes and is controlled by a reaction at the top level that is triggered by the timer T.\nAfter one second, a mode switch is triggered periodically with a period one second.\nEach mode has a timer T1/T2 that triggers a reaction after an initial offset of 100 msec and then periodically after 750 msec.\nThis reaction then schedules a logical action with a delay of 500 msec (the actual target code does not add an additional delay over the minimum specified).\nThis action triggers the second reaction, which writes to the output out.\nThe main difference between the modes is that One is entered via a history transition, continuing its behavior, while Two is reset.\n(History behavior is indicated by an “H” on the transition edge because it enters into the entire history of the mode.)

    \n\"Illustration\n

    Above is the execution trace of the first 4 seconds of this program.\nBelow the timeline is the currently active mode and above the timeline are the model elements that are executed at certain points in time, together with indicating triggering and their relation through time.\nFor example, at 100 msec, the initial offset of timer T1 elapses, which leads to the scheduling of the logical action in this mode.\nThe action triggers the reaction 500 msec later, at 600 msec, and thus causes an output.\nThe timing diagram illustrates the different handling of time between history transitions and reset transitions.\nSpecifically, when mode One is re-entered via a history transition, at time 2000 msec, the action triggered by T1 before, at time 850 msec, resumes.\nIn contrast, when mode Two is re-entered via a reset transition, at time 3000 msec, the action triggered by T2 before, at time 1850 msec, gets discarded.

    \n\"Illustration\n

    The above plot illustrates the relation between global time in the environment and the localized time for each timer in the model.\nSince the top-level reactor TimingExample is not enclosed by any mode, its time always corresponds to the global time.\nMode One is the initial mode and hence progresses in sync with TimingExample for the first second.\nDuring inactivity of mode One the timer is suspended and does not advance in time.\nAt 2000 msec it continues relative to this time.\nT2 only starts advancing when the mode becomes active at 1000 msec.\nThe reentry via reset at 3000 msec causes the local time to be reset to zero.

    \n

    Startup and Shutdown

    \n

    A challenge for modal execution is the handling startup and shutdown behavior.\nThese are commonly used for managing memory for state variables, handling connections to sensors or actuators, or starting/joining external threads.\nIf reactions to these triggers are located inside modes they are subject to a special execution regime.

    \n

    First, startup reactions are invoked at most once at the first activation of a mode.\nSecond, shutdown reactions are executed when the reactor shuts down, irrespective of mode activity, but only if the enclosing modes have been activated at least once.\nHence, every startup has a corresponding shutdown.\nThird, as mentioned before, the new reset trigger for reactions can be used, if a startup behavior should be re-executed if a mode is entered with a reset transition.

    \n

    Note that this may have unexpected implications:

    \n
      \n
    • Startup behavior inside modes may occur during execution and not only at program start.
    • \n
    • Multiple shutdown reactions may be executed, bypassing mutual exclusion of modes.
    • \n
    • Reactors that are designed without consideration of modes and use only startup (not reset) to trigger an execution chain, may not work in modes and cease to function if re-entered with a reset.
    • \n
    \n
    ","headings":[{"value":"Syntax","depth":2},{"value":"Execution Semantics","depth":2},{"value":"Local Time","depth":3},{"value":"Startup and Shutdown","depth":3}],"frontmatter":{"permalink":"/docs/handbook/modal-models","title":"Modal Reactors","oneline":"Modal Reactors","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","permalink":"/docs/handbook/superdense-time"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Deadlines","oneline":"Deadlines in Lingua Franca.","permalink":"/docs/handbook/deadlines"}}}},"pageContext":{"id":"1-modal-reactors","slug":"/docs/handbook/modal-models","repoPath":"/packages/documentation/copy/en/topics/Modal Models.md","previousID":"1986cb1b-8feb-57bc-8088-50548df2f061","nextID":"cb17ff3f-7a86-5f3b-95f7-e7d1f4e920c0","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/multiports-and-banks/page-data.json b/page-data/docs/handbook/multiports-and-banks/page-data.json index e5a6acce0..413de603d 100644 --- a/page-data/docs/handbook/multiports-and-banks/page-data.json +++ b/page-data/docs/handbook/multiports-and-banks/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/multiports-and-banks","result":{"data":{"markdownRemark":{"id":"9347e103-d59f-5190-ba87-89f54110e437","excerpt":"$page-showing-target$ Lingua Franca provides a compact syntax for ports that can send or receive over multiple channels and another syntax for multiple…","html":"

    $page-showing-target$

    \n

    Lingua Franca provides a compact syntax for ports that can send or receive over multiple channels and another syntax for multiple instances of a reactor class. These are respectively called multiports and banks of reactors.

    \n

    Multiports

    \n

    To declare an input or output port to be a multiport, use the following syntax:

    \n
    \n
      input[<width>] <name>:<type>;\n  output[<width>] <name>:<type>;\n
    \n
    \n
    \n
      input[<width>] <name>\n  output[<width>] <name>\n
    \n
    \n

    where <width> is a positive integer. This can be given either as an integer literal or a parameter name. The width can also be given by target code enclosed in {=...=}. Consider the following example:

    \n

    $start(Multiport)$

    \n
    target C;\nreactor Source {\n  output[4] out:int;\n  reaction(startup) -> out {=\n    for(int i = 0; i < out_width; i++) {\n      lf_set(out[i], i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] in:int;\n  reaction(in) {=\n    int sum = 0;\n    for (int i = 0; i < in_width; i++) {\n      if (in[i]->is_present) sum += in[i]->value;\n    }\n    printf("Sum of received: %d.\\n", sum);\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.in;\n}\n
    \n
    target Cpp;\nreactor Source {\n  output[4] out:int;\n  reaction(startup) -> out {=\n    for(auto i = 0ul; i < out.size(); i++) {\n      out[i].set(i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] in:int;\n  reaction(in) {=\n    int sum = 0;\n    for (auto i = 0ul; i < in.size(); i++) {\n      if (in[i].is_present()){\n        sum += *in[i].get();\n      }\n    }\n    std::cout << "Sum of received: " << sum << std::endl;\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.in;\n}\n
    \n
    target Python;\nreactor Source {\n  output[4] out;\n  reaction(startup) -> out {=\n    for i, port in enumerate(out):\n      port.set(i)\n  =}\n}\nreactor Destination {\n  input[4] inp;\n  reaction(inp) {=\n    sum = 0\n    for port in inp:\n      if port.is_present: sum += port.value\n    print(f"Sum of received: {sum}.")\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.inp;\n}\n
    \n
    target TypeScript\nreactor Source {\n  output[4] out:number\n  reaction(startup) -> out {=\n    for (let i = 0 ; i < out.length; i++) {\n      out[i] = i\n    }\n  =}\n}\nreactor Destination {\n  input[4] inp:number\n  reaction(inp) {=\n    let sum = 0\n    for (let i = 0 ; i < inp.length; i++) {\n      const val = inp[i]\n      if (val) sum += val\n    }\n    console.log(`Sum of received: ${sum}`)\n  =}\n}\nmain reactor {\n  a = new Source()\n  b = new Destination()\n  a.out -> b.inp\n}\n
    \n
    target Rust;\nreactor Source {\n  output[4] out:usize;\n  reaction(startup) -> out {=\n    for (i, o) in out.into_iter().enumerate() {\n      ctx.set(o, i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] inp:usize;\n  reaction(inp) {=\n    let mut sum = 0;\n    for i in inp {\n      if let Some(v) = ctx.get(&i) {\n        sum += v;\n      }\n    }\n    println!("Sum of received: {}.", sum);\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.inp;\n}\n
    \n

    $end(Multiport)$

    \n\"Lingua\n

    Executing this program will yield:

    \n
    Sum of received: 6.
    \n

    The Source reactor has a four-way multiport output and the Destination reactor has a four-way multiport input. These channels are connected all at once on one line, the second line from the last. Notice that the generated diagram shows multiports with hollow triangles. Whether it shows the widths is controlled by an option in the diagram generator.

    \n

    The Source reactor specifies out as an effect of its reaction using the syntax -> out. This brings into scope of the reaction body a way to access the width of the port and a way to write to each channel of the port.

    \n

    NOTE: In Destination, the reaction is triggered by in, not by some individual channel of the multiport input. Hence, it is important when using multiport inputs to test for presence of the input on each channel, as done above with the syntax:

    \n
        if (in[i]->is_present) ...\n
    \n
        if (in[i]->is_present()) ...\n
    \n
        if port.is_present: ...\n
    \n
        if (val) ...\n
    \n
        if let Some(v) = ctx.get(&i) ...\n
    \n

    An event on any one of the channels is sufficient to trigger the reaction.

    \n
    \n

    In the Python target, multiports can be iterated on in a for loop (e.g., for p in out) or enumerated (e.g., for i, p in enumerate(out)) and the length of the multiport can be obtained by using the len() (e.g., len(out)) expression.

    \n
    \n
    \n

    Sparse Inputs

    \n

    Sometimes, a program needs a wide multiport input, but when reactions are triggered by this input, few of the channels are present.\nIn this case, it can be inefficient to iterate over all the channels to determine which are present.\nIf you know that a multiport input will be sparse in this way, then you can provide a hint to the compiler and use a more efficient iterator to access the port. For example:

    \n

    $start(Sparse)$

    \n
    target C;\nreactor Sparse {\n  @sparse\n  input[100] in:int;\n  reaction(in) {=\n    // Create an iterator over the input channels.\n    struct lf_multiport_iterator_t i = lf_multiport_iterator(in);\n    // Get the least index of a channel with present inputs.\n    int channel = lf_multiport_next(&i);\n    // Iterate until no more channels have present inputs.\n    while(channel >= 0) {\n      printf("Received %d on channel %d\\n", in[channel]->value, channel);\n      // Get the next channel with a present input.\n      channel = lf_multiport_next(&i);\n    }\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/py/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/Sparse.lf\n
    \n

    $end(Sparse)$

    \n

    Notice the @sparse annotation on the input declaration.\nThis provides a hint to the compiler to optimize for sparse inputs.\nThen, instead of iterating over all input channels, this code uses the built-in function lf_multiport_iterator() to construct an iterator. The function lf_multiport_next() returns the first (and later, the next) channel index that is present. It returns -1 when no more channels have present inputs.

    \n

    The multiport iterator can be used for any input multiport, even if it is not marked sparse.\nBut if it is not marked sparse, then the lf_multiport_next() function will not optimize for sparse inputs and will simply iterate over the channels until it finds one that is present.

    \n
    \n

    Parameterized Widths

    \n

    The width of a port may be given by a parameter. For example, the above Source reactor can be rewritten

    \n
    reactor Source(width:int = 4) {\n  output[width] out:int;\n  reaction(startup) -> out {=\n    ...\n  =}\n}\n
    \n
    \n

    Parameters to the main reactor can be overwritten on the command line interface when running the generated program. As a consequence, the scale of the application can be determined at run time rather than at compile time.

    \n
    \n

    Connecting Reactors with Different Widths

    \n

    Assume that the Source and Destination reactors above both use a parameter width to specify the width of their ports. Then the following connection is valid:

    \n
    main reactor {\n  a1 = new Source(width = 3);\n  a2 = new Source(width = 2);\n  b = new Destination(width = 5);\n  a1.out, a2.out -> b.in;\n}\n
    \n

    The first three ports of b will received input from a1, and the last two ports will receive input from a2. Parallel composition can appear on either side of a connection. For example:

    \n
      a1.out, a2.out -> b1.out, b2.out, b3.out;\n
    \n

    If the total width on the left does not match the total width on the right, then a warning is issued. If the left side is wider than the right, then output data will be discarded. If the right side is wider than the left, then input channels will be absent.

    \n

    Any given port can appear only once on the right side of the -> connection operator, so all connections to a multiport destination must be made in one single connection statement.

    \n

    Banks of Reactors

    \n

    Using a similar notation, it is possible to create a bank of reactors. For example, we can create a bank of four instances of Source and four instances of Destination and connect them as follows:

    \n
    main reactor {\n  a = new[4] Source();\n  b = new[4] Destination();\n  a.out -> b.in;\n}\n
    \n\"Lingua\n

    If the Source and Destination reactors have multiport inputs and outputs, as in the examples above, then a warning will be issued if the total width on the left does not match the total width on the right. For example, the following is balanced:

    \n
    main reactor {\n  a = new[3] Source(width = 4);\n  b = new[4] Destination(width = 3);\n  a.out -> b.in;\n}\n
    \n

    There will be three instances of Source, each with an output of width four, and four instances of Destination, each with an input of width 3, for a total of 12 connections.

    \n

    To distinguish the instances in a bank of reactors, the reactor can define a parameter called bank_index with any type that can be assigned a non-negative integer value (for example, int, size_t, or uint32_t). If such a parameter is defined for the reactor, then when the reactor is instantiated in a bank, each instance will be assigned a number between 0 and n-1, where n is the number of reactor instances in the bank. For example, the following source reactor increments the output it produces by the value of bank_index on each reaction to the timer:

    \n

    $start(MultiportSource)$

    \n
    target C\nreactor MultiportSource(bank_index: int = 0) {\n  timer t(0, 200 msec)\n  output out: int\n  state s: int = 0\n  reaction(t) -> out {=\n    lf_set(out, self->s);\n    self->s += self->bank_index;\n  =}\n}\n
    \n
    target Cpp\nreactor MultiportSource(bank_index: int(0)) {\n  timer t(0, 200 ms)\n  output out: int\n  state s: int(0)\n  reaction(t) -> out {=\n    out.set(s);\n    s += bank_index;\n  =}\n}\n
    \n
    target Python\nreactor MultiportSource(bank_index=0) {\n  timer t(0, 200 msec)\n  output out\n  state s = 0\n  reaction(t) -> out {=\n    out.set(self.s)\n    self.s += self.bank_index\n  =}\n}\n
    \n
    target TypeScript\nreactor MultiportSource {\n  timer t(0, 200 msec)\n  output out: number\n  state s: number = 0\n  reaction(t) -> out {=\n    out = s\n    s += this.getBankIndex()\n  =}\n}\n
    \n
    target Rust\nreactor MultiportSource(bank_index: u32 = 0) {\n  state bank_index = bank_index\n  timer t(0, 200 msec)\n  output out: u32\n  state s: u32 = 0\n  reaction(t) -> out {=\n    ctx.set(out, self.s);\n    self.s += self.bank_index;\n  =}\n}\n
    \n

    $end(MultiportSource)$

    \n

    The width of a bank may also be given by a parameter, as in

    \n
    main reactor(\n  source_bank_width:int = 3,\n  destination_bank_width:int = 4\n) {\n  a = new[source_bank_width] Source(width = 4);\n  b = new[destination_bank_width] Destination(width = 3);\n  a.out -> b.in;\n}\n
    \n
    \n

    Initializing Bank Members from a Table

    \n

    It is often convenient to initialize parameters of bank members from a table.\nHere is an example:

    \n

    $start(BankIndex)$

    \n
    target C;\npreamble {=\n  int table[] = {4, 3, 2, 1};\n=}\nreactor A(bank_index:int = 0, value:int = 0) {\n  reaction (startup) {=\n    printf("bank_index: %d, value: %d\\n", self->bank_index, self->value);\n  =}\n}\nmain reactor {\n  a = new[4] A(value = {= table[bank_index] =});\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/BankIndex.lf\n
    \n
    target Python;\npreamble {=\n  table = [4, 3, 2, 1]\n=}\nreactor A(bank_index = 0, value = 0) {\n  reaction (startup) {=\n    print("bank_index: {:d}, value: {:d}".format(self.bank_index, self.value))\n  =}\n}\nmain reactor {\n  a = new[4] A(value = {= table[bank_index] =})\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/BankIndex.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/BankIndex.lf\n
    \n

    $end(BankIndex)$

    \n

    The global table defined in the $preamble$ is used to initialize the value parameter of each bank member. The result of running this is something like:

    \n
    bank_index: 0, value: 4\nbank_index: 1, value: 3\nbank_index: 2, value: 2\nbank_index: 3, value: 1
    \n
    \n

    Contained Banks

    \n

    Banks of reactors can be nested. For example, note the following program:

    \n

    $start(ChildBank)$

    \n
    target C;\nreactor Child (\n  bank_index:int = 0\n) {\n  reaction(startup) {=\n    printf("My bank index: %d.\\n", self->bank_index);\n  =}\n}\nreactor Parent (\n  bank_index:int = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target Cpp;\nreactor Child (\n  bank_index:int = 0\n) {\n  reaction(startup) {=\n    std::cout << "My bank index:" << bank_index << std::endl;\n  =}\n}\nreactor Parent (\n  bank_index:int = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target Python;\nreactor Child (\n  bank_index = 0\n) {\n  reaction(startup) {=\n    print(f"My bank index: {self.bank_index}.")\n  =}\n}\nreactor Parent (\n  bank_index = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target TypeScript\nreactor Child {\n  reaction(startup) {=\n    console.log(`My bank index ${this.getBankIndex()}`)\n  =}\n}\nreactor Parent {\n  c = new[2] Child()\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Rust;\nreactor Child (\n  bank_index:usize = 0\n) {\n  state bank_index = bank_index;\n  reaction(startup) {=\n    println!("My bank index: {}.", self.bank_index);\n  =}\n}\nreactor Parent (\n  bank_index:usize = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n

    $end(ChildBank)$

    \n\"Lingua\n

    In this program, the Parent reactor contains a bank of Child reactor instances\nwith a width of 2. In the main reactor, a bank of Parent reactors is\ninstantiated with a width of 2, therefore, creating 4 Child instances in the program in total.\nThe output of this program will be:

    \n
    My bank index: 0.\nMy bank index: 1.\nMy bank index: 0.\nMy bank index: 1.
    \n

    The order of these outputs will be nondeterministic if the execution is multithreaded (which it will be by default) because there is no dependence between the reactions, and, hence, they can execute in parallel.

    \n

    The bank index of a container (parent) reactor can be passed down to\ncontained (child) reactors. For example, note the following program:

    \n

    $start(ChildParentBank)$

    \n
    target C\nreactor Child(bank_index: int = 0, parent_bank_index: int = 0) {\n  reaction(startup) {=\n    printf(\n        "My bank index: %d. My parent's bank index: %d.\\n",\n        self->bank_index, self->parent_bank_index\n    );\n  =}\n}\nreactor Parent(bank_index: int = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Cpp\nreactor Child(bank_index: int(0), parent_bank_index: int(0)) {\n  reaction(startup) {=\n    std::cout <<"My bank index: " << bank_index << " My parent's bank index: " << parent_bank_index << std::endl;\n  =}\n}\nreactor Parent(bank_index: int(0)) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Python\nreactor Child(bank_index=0, parent_bank_index=0) {\n  reaction(startup) {=\n    print(\n        f"My bank index: {self.bank_index}. "\n        f"My parent's bank index: {self.parent_bank_index}."\n    )\n  =}\n}\nreactor Parent(bank_index=0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target TypeScript\nreactor Child(parentBankIndex: number = 0) {\n  reaction(startup) {=\n    console.log(`My bank index: ${this.getBankIndex()} My parent's bank index: ${parentBankIndex}`)\n  =}\n}\nreactor Parent {\n  c = new[2] Child(parentBankIndex = {= this.getBankIndex() =})\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Rust\nreactor Child(bank_index: usize = 0, parent_bank_index: usize = 0) {\n  state bank_index = bank_index\n  state parent_bank_index = parent_bank_index\n  reaction(startup) {=\n    println!(\n        "My bank index: {}. My parent's bank index: {}.",\n        self.bank_index,\n        self.parent_bank_index,\n    );\n  =}\n}\nreactor Parent(bank_index: usize = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n

    $end(ChildParentBank)$

    \n

    In this example, the bank index of the Parent reactor is passed to the\nparent_bank_index parameter of the Child reactor instances.\nThe output from this program will be:

    \n
    My bank index: 1. My parent's bank index: 1.\nMy bank index: 0. My parent's bank index: 0.\nMy bank index: 0. My parent's bank index: 1.\nMy bank index: 1. My parent's bank index: 0.
    \n

    Again, note that the order of these outputs is nondeterministic.

    \n

    Finally, members of contained banks of reactors can be individually addressed in\nthe body of reactions of the parent reactor if their input/output port appears\nin the reaction signature. For example, note the following program:

    \n

    $start(ChildParentBank2)$

    \n
    target C\nreactor Child(bank_index: int = 0, parent_bank_index: int = 0) {\n  output out: int\n  reaction(startup) -> out {=\n    lf_set(out, self->parent_bank_index * 2 + self->bank_index);\n  =}\n}\nreactor Parent(bank_index: int = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for (int i=0; i < c_width; i++) {\n        printf("Received %d from child %d.\\n", c[i].out->value, i);\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Cpp\nreactor Child(bank_index: int(0), parent_bank_index: int(0)) {\n  output out: int\n  reaction(startup) -> out {=\n    out.set(parent_bank_index * 2 + bank_index);\n  =}\n}\nreactor Parent(bank_index: int(0)) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for (auto i = 0ul; i < c.size(); i++) {\n        std::cout << "Received " << *c[i].out.get() <<" from child " << i << std::endl;\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Python\nreactor Child(bank_index=0, parent_bank_index=0) {\n  output out\n  reaction(startup) -> out {=\n    out.set(self.parent_bank_index * 2 + self.bank_index)\n  =}\n}\nreactor Parent(bank_index=0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for i, child in enumerate(c):\n        print(f"Received {child.out.value} from child {i}.")\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target TypeScript\nreactor Child(parentBankIndex: number = 0) {\n  output out: number\n  reaction(startup) -> out {=\n    out = parentBankIndex * 2 + this.getBankIndex()\n  =}\n}\nreactor Parent {\n  c = new[2] Child(parentBankIndex = {= this.getBankIndex() =})\n  reaction(c.out) {=\n    for (let i = 0; i < c.length; i++) {\n        console.log(`Received ${c[i].out} from child ${i}`)\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/ChildParentBank2.lf\n
    \n

    $end(ChildParentBank2)$

    \n\"Lingua\n

    Running this program will give something like the following:

    \n
    Received 0 from child 0.\nReceived 1 from child 1.\nReceived 2 from child 0.\nReceived 3 from child 1.
    \n
    \n

    Note the usage of c_width, which holds the width of the c bank of reactors.

    \n
    \n
    \n

    Note that len(c) can be used to get the width of the bank, and for p in c or for (i, p) in enumerate(c) can be used to iterate over the bank members.

    \n
    \n
    \n

    Note that c.size() can be used to get the width of the bank c.

    \n
    \n
    \n

    Note that that bank instance c in TypeScript is an array, so c.length is the width of the bank, and the bank members are referenced by indexing the array, as in c[i].

    \n
    \n
    \n

    FIXME: How to get the width of the bank in target code?

    \n
    \n

    Combining Banks and Multiports

    \n

    Banks of reactors may be combined with multiports, as in the following example:

    \n

    $start(MultiportToBank)$

    \n
    target C\nreactor Source {\n  output[3] out: int\n  reaction(startup) -> out {=\n    for(int i = 0; i < out_width; i++) {\n      lf_set(out[i], i);\n    }\n  =}\n}\nreactor Destination(bank_index: int = 0) {\n  input in: int\n  reaction(in) {=\n    printf("Destination %d received %d.\\n", self->bank_index, in->value);\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.in\n}\n
    \n
    target Cpp\nreactor Source {\n  output[3] out: int\n  reaction(startup) -> out {=\n    for(int i = 0; i < out.size(); i++) {\n      out[i].set(i);\n    }\n  =}\n}\nreactor Destination(bank_index: int(0)) {\n  input in: int\n  reaction(in) {=\n    std::cout << "Destination " << bank_index << " received " << *in.get() << std::endl;\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.in\n}\n
    \n
    target Python\nreactor Source {\n  output[3] out\n  reaction(startup) -> out {=\n    for i, port in enumerate(out):\n      port.set(i)\n  =}\n}\nreactor Destination(bank_index=0) {\n  input inp\n  reaction(inp) {=\n    print(f"Destination {self.bank_index} received {inp.value}.")\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n
    target TypeScript\nreactor Source {\n  output[3] out: number\n  reaction(startup) -> out {=\n     for (let i = 0 ; i < out.length; i++) {\n        out[i] = i\n    }\n  =}\n}\nreactor Destination {\n  input inp: number\n  reaction(inp) {=\n    console.log(`Destination ${this.getBankIndex()} received ${inp}`)\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n
    target Rust\nreactor Source {\n  output[3] out: usize\n  reaction(startup) -> out {=\n    for (i, o) in out.into_iter().enumerate() {\n      ctx.set(o, i);\n    }\n  =}\n}\nreactor Destination(bank_index: usize = 0) {\n  state bank_index = bank_index\n  input inp: usize\n  reaction(inp) {=\n    println!(\n        "Destination {} received {}.",\n        self.bank_index,\n        ctx.get(inp).unwrap(),\n    );\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n

    $end(MultiportToBank)$

    \n\"Lingua\n

    The three outputs from the Source instance a will be sent, respectively, to each of three instances of Destination, b[0], b[1], and b[2]. The result of the program will be something like the following:

    \n
    Destination 0 received 0.\nDestination 1 received 1.\nDestination 2 received 2.
    \n

    Again, the order is nondeterministic in a multithreaded context.

    \n

    The reactors in a bank may themselves have multiports. In all cases, the number of ports on the left of a connection must match the number on the right, unless the ones on the left are iterated, as explained next.

    \n

    Broadcast Connections

    \n

    Occasionally, you will want to have fewer ports on the left of a connection and have their outputs used repeatedly to broadcast to the ports on the right. In the following example, the outputs from an ordinary port are broadcast to the inputs of all instances of a bank of reactors:

    \n
    reactor Source {\n  output out:int;\n  reaction(startup) -> out {=\n    ... write to out ...\n  =}\n}\nreactor Destination {\n  input in:int;\n  reaction(in) {=\n    ... read from in ...\n  =}\n}\nmain reactor ThreadedThreaded(width:int(4)) {\n  a = new Source();\n  d = new[width] Destination();\n  (a.out)+ -> d.in;\n}\n
    \n

    The syntax (a.out)+ means “repeat the output port a.out one or more times as needed to supply all the input ports of d.in.” The content inside the parentheses can be a comma-separated list of ports, the ports inside can be ordinary ports or multiports, and the reactors inside can be ordinary reactors or banks of reactors. In all cases, the number of ports inside the parentheses on the left must divide the number of ports on the right.

    \n

    Interleaved Connections

    \n
    \n

    Sometimes, we don’t want to broadcast messages to all reactors, but need more fine-grained control as to which reactor within a bank receives a message. If we have separate source and destination reactors, this can be done by combining multiports and banks as was shown in Combining Banks and Multiports. Setting a value on the index n of the output multiport, will result in a message to the n-th reactor instance within the destination bank. However, this pattern gets slightly more complicated, if we want to exchange addressable messages between instances of the same bank. This pattern is shown in the following example:

    \n

    $start(Interleaved)$

    \n
    target C\nreactor Node(num_nodes: size_t = 4, bank_index: int = 0) {\n  input[num_nodes] in: int\n  output[num_nodes] out: int\n  reaction(startup) -> out {=\n    lf_set(out[1], 42);\n    printf("Bank index %d sent 42 on channel 1.\\n", self->bank_index);\n  =}\n  reaction(in) {=\n    for (int i = 0; i < in_width; i++) {\n      if (in[i]->is_present) {\n        printf("Bank index %d received %d on channel %d.\\n",\n          self->bank_index, in[i]->value, i\n        );\n      }\n    }\n  =}\n}\nmain reactor(num_nodes: size_t = 4) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.in)\n}\n
    \n
    target Cpp\nreactor Node(num_nodes: size_t(4), bank_index: int(0)) {\n  input[num_nodes] in: int\n  output[num_nodes] out: int\n  reaction(startup) -> out {=\n    out[1].set(42);\n    std::cout << "Bank index " << bank_index << " sent 42 on channel 1." << std::endl;\n  =}\n  reaction(in) {=\n    for (auto i = 0ul; i < in.size(); i++) {\n      if (in[i].is_present()) {\n        std::cout << "Bank index " << bank_index\n          << " received " << *in[i].get() << " on channel" << std::endl;\n      }\n    }\n  =}\n}\nmain reactor(num_nodes: size_t(4)) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.in)\n}\n
    \n
    target Python\nreactor Node(num_nodes=4, bank_index=0) {\n  input[num_nodes] inp\n  output[num_nodes] out\n  reaction(startup) -> out {=\n    out[1].set(42)\n    print(f"Bank index {self.bank_index} sent 42 on channel 1.")\n  =}\n  reaction(inp) {=\n    for i, port in enumerate(inp):\n      if port.is_present:\n        print(\n          f"Bank index {self.bank_index} received {port.value} on channel {i}.",\n        )\n  =}\n}\nmain reactor(num_nodes=4) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.inp)\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/Interleaved.lf\ntarget TypeScript\nreactor Node(numNodes: number(4)) {\n    input[numNodes] inp: number\n    output[numNodes] out: number\n    reaction (startup) -> out {=\n        out[1] = 42\n        console.log(`Bank index ${this.getBankIndex()} sent 42 on channel 1.`)\n    =}\n    reaction (inp) {=\n        for (let i = 0; i < in.length; i++) {\n            if (in[i] !== undefined) {\n                console.log(`Bank index ${this.getBankIndex()} received ${in[i]} on channel ${i}`)\n            }\n        }\n    =}\n}\nmain reactor(numNodes: number(4)) {\n    nodes = new[numNodes] Node(numNodes=numNodes);\n    nodes.out -> interleaved(nodes.inp)\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Interleaved.lf\n
    \n

    $end(Interleaved)$

    \n\"Lingua\n

    In the above program, four instance of Node are created, and, at startup, each instance sends 42 to its second (index 1) output channel. The result is that the second bank member (bank_index 1) will receive the number 42 on each input channel of its multiport input. Running this program gives something like the following:

    \n
    Bank index 0 sent 42 on channel 1.\nBank index 1 sent 42 on channel 1.\nBank index 2 sent 42 on channel 1.\nBank index 3 sent 42 on channel 1.\nBank index 1 received 42 on channel 0.\nBank index 1 received 42 on channel 1.\nBank index 1 received 42 on channel 2.\nBank index 1 received 42 on channel 3.
    \n

    In bank index 1, the 0-th channel receives from bank_index 0, the 1-th channel from bank_index 1, etc. In effect, the choice of output channel specifies the destination reactor in the bank, and the input channel specifies the source reactor from which the input comes.

    \n

    This style of connection is accomplished using the new keyword $interleaved$ in the connection. Normally, a port reference such as nodes.out where nodes is a bank and out is a multiport, would list all the individual ports by first iterating over the banks and then, for each bank index, iterating over the ports. If we consider the tuple (b,p) to denote the index b within the bank and the index p within the multiport, then the following list is created: (0,0), (0,1), (0,2), (0,3), (1,0), (1,1), (1,2), (1,3), (2,0), (2,1), (2,2), (2,3), (3,0), (3,1), (3,2), (3,3). However, if we use $interleaved$(nodes.out) instead, the connection logic will iterate over the ports first and then the banks, creating the following list: (0,0), (1,0), (2,0), (3,0), (0,1), (1,1), (2,1), (3,1), (0,2), (1,2), (2,2), (3,2), (0,3), (1,3), (2,3), (3,3). By combining a normal port reference with a interleaved reference, we can construct a fully connected network. The figure below visualizes this how this pattern would look without banks or multiports:

    \n\n \n \"Lingua\n \n

    If we were to use a normal connection nodes.out -> nodes.in; instead of the $interleaved$ connection, then the following pattern would be created:

    \n\n \n \"Lingua\n \n

    Effectively, this connects each reactor instance to itself, which isn’t very useful.

    \n
    \n
    \n

    The $interleaved$ keyword is not supported by $target-language$.

    \n
    ","headings":[{"value":"Multiports","depth":2},{"value":"Sparse Inputs","depth":2},{"value":"Parameterized Widths","depth":2},{"value":"Connecting Reactors with Different Widths","depth":2},{"value":"Banks of Reactors","depth":2},{"value":"Initializing Bank Members from a Table","depth":2},{"value":"Contained Banks","depth":2},{"value":"Combining Banks and Multiports","depth":2},{"value":"Broadcast Connections","depth":2},{"value":"Interleaved Connections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/multiports-and-banks","title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Deadlines","oneline":"Deadlines in Lingua Franca.","permalink":"/docs/handbook/deadlines"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","permalink":"/docs/handbook/generics"}}}},"pageContext":{"id":"1-multiports-and-banks","slug":"/docs/handbook/multiports-and-banks","repoPath":"/packages/documentation/copy/en/topics/Multiports and Banks.md","previousID":"cb17ff3f-7a86-5f3b-95f7-e7d1f4e920c0","nextID":"2a0b9619-72b6-5ee7-8a38-83ff8d48005a","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/multiports-and-banks","result":{"data":{"markdownRemark":{"id":"9347e103-d59f-5190-ba87-89f54110e437","excerpt":"$page-showing-target$ Lingua Franca provides a compact syntax for ports that can send or receive over multiple channels and another syntax for multiple…","html":"

    $page-showing-target$

    \n

    Lingua Franca provides a compact syntax for ports that can send or receive over multiple channels and another syntax for multiple instances of a reactor class. These are respectively called multiports and banks of reactors.

    \n

    Multiports

    \n

    To declare an input or output port to be a multiport, use the following syntax:

    \n
    \n
      input[<width>] <name>:<type>;\n  output[<width>] <name>:<type>;\n
    \n
    \n
    \n
      input[<width>] <name>\n  output[<width>] <name>\n
    \n
    \n

    where <width> is a positive integer. This can be given either as an integer literal or a parameter name. The width can also be given by target code enclosed in {=...=}. Consider the following example:

    \n

    $start(Multiport)$

    \n
    target C;\nreactor Source {\n  output[4] out:int;\n  reaction(startup) -> out {=\n    for(int i = 0; i < out_width; i++) {\n      lf_set(out[i], i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] in:int;\n  reaction(in) {=\n    int sum = 0;\n    for (int i = 0; i < in_width; i++) {\n      if (in[i]->is_present) sum += in[i]->value;\n    }\n    printf("Sum of received: %d.\\n", sum);\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.in;\n}\n
    \n
    target Cpp;\nreactor Source {\n  output[4] out:int;\n  reaction(startup) -> out {=\n    for(auto i = 0ul; i < out.size(); i++) {\n      out[i].set(i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] in:int;\n  reaction(in) {=\n    int sum = 0;\n    for (auto i = 0ul; i < in.size(); i++) {\n      if (in[i].is_present()){\n        sum += *in[i].get();\n      }\n    }\n    std::cout << "Sum of received: " << sum << std::endl;\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.in;\n}\n
    \n
    target Python;\nreactor Source {\n  output[4] out;\n  reaction(startup) -> out {=\n    for i, port in enumerate(out):\n      port.set(i)\n  =}\n}\nreactor Destination {\n  input[4] inp;\n  reaction(inp) {=\n    sum = 0\n    for port in inp:\n      if port.is_present: sum += port.value\n    print(f"Sum of received: {sum}.")\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.inp;\n}\n
    \n
    target TypeScript\nreactor Source {\n  output[4] out:number\n  reaction(startup) -> out {=\n    for (let i = 0 ; i < out.length; i++) {\n      out[i] = i\n    }\n  =}\n}\nreactor Destination {\n  input[4] inp:number\n  reaction(inp) {=\n    let sum = 0\n    for (let i = 0 ; i < inp.length; i++) {\n      const val = inp[i]\n      if (val) sum += val\n    }\n    console.log(`Sum of received: ${sum}`)\n  =}\n}\nmain reactor {\n  a = new Source()\n  b = new Destination()\n  a.out -> b.inp\n}\n
    \n
    target Rust;\nreactor Source {\n  output[4] out:usize;\n  reaction(startup) -> out {=\n    for (i, o) in out.into_iter().enumerate() {\n      ctx.set(o, i);\n    }\n  =}\n}\nreactor Destination {\n  input[4] inp:usize;\n  reaction(inp) {=\n    let mut sum = 0;\n    for i in inp {\n      if let Some(v) = ctx.get(&i) {\n        sum += v;\n      }\n    }\n    println!("Sum of received: {}.", sum);\n  =}\n}\nmain reactor {\n  a = new Source();\n  b = new Destination();\n  a.out -> b.inp;\n}\n
    \n

    $end(Multiport)$

    \n\"Lingua\n

    Executing this program will yield:

    \n
    Sum of received: 6.
    \n

    The Source reactor has a four-way multiport output and the Destination reactor has a four-way multiport input. These channels are connected all at once on one line, the second line from the last. Notice that the generated diagram shows multiports with hollow triangles. Whether it shows the widths is controlled by an option in the diagram generator.

    \n

    The Source reactor specifies out as an effect of its reaction using the syntax -> out. This brings into scope of the reaction body a way to access the width of the port and a way to write to each channel of the port.

    \n

    NOTE: In Destination, the reaction is triggered by in, not by some individual channel of the multiport input. Hence, it is important when using multiport inputs to test for presence of the input on each channel, as done above with the syntax:

    \n
        if (in[i]->is_present) ...\n
    \n
        if (in[i]->is_present()) ...\n
    \n
        if port.is_present: ...\n
    \n
        if (val) ...\n
    \n
        if let Some(v) = ctx.get(&i) ...\n
    \n

    An event on any one of the channels is sufficient to trigger the reaction.

    \n
    \n

    In the Python target, multiports can be iterated on in a for loop (e.g., for p in out) or enumerated (e.g., for i, p in enumerate(out)) and the length of the multiport can be obtained by using the len() (e.g., len(out)) expression.

    \n
    \n
    \n

    Sparse Inputs

    \n

    Sometimes, a program needs a wide multiport input, but when reactions are triggered by this input, few of the channels are present.\nIn this case, it can be inefficient to iterate over all the channels to determine which are present.\nIf you know that a multiport input will be sparse in this way, then you can provide a hint to the compiler and use a more efficient iterator to access the port. For example:

    \n

    $start(Sparse)$

    \n
    target C;\nreactor Sparse {\n  @sparse\n  input[100] in:int;\n  reaction(in) {=\n    // Create an iterator over the input channels.\n    struct lf_multiport_iterator_t i = lf_multiport_iterator(in);\n    // Get the least index of a channel with present inputs.\n    int channel = lf_multiport_next(&i);\n    // Iterate until no more channels have present inputs.\n    while(channel >= 0) {\n      printf("Received %d on channel %d\\n", in[channel]->value, channel);\n      // Get the next channel with a present input.\n      channel = lf_multiport_next(&i);\n    }\n  =}\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/py/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/ts/src/Sparse.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/Sparse.lf\n
    \n

    $end(Sparse)$

    \n

    Notice the @sparse annotation on the input declaration.\nThis provides a hint to the compiler to optimize for sparse inputs.\nThen, instead of iterating over all input channels, this code uses the built-in function lf_multiport_iterator() to construct an iterator. The function lf_multiport_next() returns the first (and later, the next) channel index that is present. It returns -1 when no more channels have present inputs.

    \n

    The multiport iterator can be used for any input multiport, even if it is not marked sparse.\nBut if it is not marked sparse, then the lf_multiport_next() function will not optimize for sparse inputs and will simply iterate over the channels until it finds one that is present.

    \n
    \n

    Parameterized Widths

    \n

    The width of a port may be given by a parameter. For example, the above Source reactor can be rewritten

    \n
    reactor Source(width:int = 4) {\n  output[width] out:int;\n  reaction(startup) -> out {=\n    ...\n  =}\n}\n
    \n
    \n

    Parameters to the main reactor can be overwritten on the command line interface when running the generated program. As a consequence, the scale of the application can be determined at run time rather than at compile time.

    \n
    \n

    Connecting Reactors with Different Widths

    \n

    Assume that the Source and Destination reactors above both use a parameter width to specify the width of their ports. Then the following connection is valid:

    \n
    main reactor {\n  a1 = new Source(width = 3);\n  a2 = new Source(width = 2);\n  b = new Destination(width = 5);\n  a1.out, a2.out -> b.in;\n}\n
    \n

    The first three ports of b will received input from a1, and the last two ports will receive input from a2. Parallel composition can appear on either side of a connection. For example:

    \n
      a1.out, a2.out -> b1.out, b2.out, b3.out;\n
    \n

    If the total width on the left does not match the total width on the right, then a warning is issued. If the left side is wider than the right, then output data will be discarded. If the right side is wider than the left, then input channels will be absent.

    \n

    Any given port can appear only once on the right side of the -> connection operator, so all connections to a multiport destination must be made in one single connection statement.

    \n

    Banks of Reactors

    \n

    Using a similar notation, it is possible to create a bank of reactors. For example, we can create a bank of four instances of Source and four instances of Destination and connect them as follows:

    \n
    main reactor {\n  a = new[4] Source();\n  b = new[4] Destination();\n  a.out -> b.in;\n}\n
    \n\"Lingua\n

    If the Source and Destination reactors have multiport inputs and outputs, as in the examples above, then a warning will be issued if the total width on the left does not match the total width on the right. For example, the following is balanced:

    \n
    main reactor {\n  a = new[3] Source(width = 4);\n  b = new[4] Destination(width = 3);\n  a.out -> b.in;\n}\n
    \n

    There will be three instances of Source, each with an output of width four, and four instances of Destination, each with an input of width 3, for a total of 12 connections.

    \n

    To distinguish the instances in a bank of reactors, the reactor can define a parameter called bank_index with any type that can be assigned a non-negative integer value (for example, int, size_t, or uint32_t). If such a parameter is defined for the reactor, then when the reactor is instantiated in a bank, each instance will be assigned a number between 0 and n-1, where n is the number of reactor instances in the bank. For example, the following source reactor increments the output it produces by the value of bank_index on each reaction to the timer:

    \n

    $start(MultiportSource)$

    \n
    target C\nreactor MultiportSource(bank_index: int = 0) {\n  timer t(0, 200 msec)\n  output out: int\n  state s: int = 0\n  reaction(t) -> out {=\n    lf_set(out, self->s);\n    self->s += self->bank_index;\n  =}\n}\n
    \n
    target Cpp\nreactor MultiportSource(bank_index: int(0)) {\n  timer t(0, 200 ms)\n  output out: int\n  state s: int(0)\n  reaction(t) -> out {=\n    out.set(s);\n    s += bank_index;\n  =}\n}\n
    \n
    target Python\nreactor MultiportSource(bank_index=0) {\n  timer t(0, 200 msec)\n  output out\n  state s = 0\n  reaction(t) -> out {=\n    out.set(self.s)\n    self.s += self.bank_index\n  =}\n}\n
    \n
    target TypeScript\nreactor MultiportSource {\n  timer t(0, 200 msec)\n  output out: number\n  state s: number = 0\n  reaction(t) -> out {=\n    out = s\n    s += this.getBankIndex()\n  =}\n}\n
    \n
    target Rust\nreactor MultiportSource(bank_index: u32 = 0) {\n  state bank_index = bank_index\n  timer t(0, 200 msec)\n  output out: u32\n  state s: u32 = 0\n  reaction(t) -> out {=\n    ctx.set(out, self.s);\n    self.s += self.bank_index;\n  =}\n}\n
    \n

    $end(MultiportSource)$

    \n

    The width of a bank may also be given by a parameter, as in

    \n
    main reactor(\n  source_bank_width:int = 3,\n  destination_bank_width:int = 4\n) {\n  a = new[source_bank_width] Source(width = 4);\n  b = new[destination_bank_width] Destination(width = 3);\n  a.out -> b.in;\n}\n
    \n
    \n

    Initializing Bank Members from a Table

    \n

    It is often convenient to initialize parameters of bank members from a table.\nHere is an example:

    \n

    $start(BankIndex)$

    \n
    target C;\npreamble {=\n  int table[] = {4, 3, 2, 1};\n=}\nreactor A(bank_index:int = 0, value:int = 0) {\n  reaction (startup) {=\n    printf("bank_index: %d, value: %d\\n", self->bank_index, self->value);\n  =}\n}\nmain reactor {\n  a = new[4] A(value = {= table[bank_index] =});\n}\n
    \n
    WARNING: No source file found: ../code/cpp/src/BankIndex.lf\n
    \n
    target Python;\npreamble {=\n  table = [4, 3, 2, 1]\n=}\nreactor A(bank_index = 0, value = 0) {\n  reaction (startup) {=\n    print("bank_index: {:d}, value: {:d}".format(self.bank_index, self.value))\n  =}\n}\nmain reactor {\n  a = new[4] A(value = {= table[bank_index] =})\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/BankIndex.lf\n
    \n
    WARNING: No source file found: ../code/rs/src/BankIndex.lf\n
    \n

    $end(BankIndex)$

    \n

    The global table defined in the $preamble$ is used to initialize the value parameter of each bank member. The result of running this is something like:

    \n
    bank_index: 0, value: 4\nbank_index: 1, value: 3\nbank_index: 2, value: 2\nbank_index: 3, value: 1
    \n
    \n

    Contained Banks

    \n

    Banks of reactors can be nested. For example, note the following program:

    \n

    $start(ChildBank)$

    \n
    target C;\nreactor Child (\n  bank_index:int = 0\n) {\n  reaction(startup) {=\n    printf("My bank index: %d.\\n", self->bank_index);\n  =}\n}\nreactor Parent (\n  bank_index:int = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target Cpp;\nreactor Child (\n  bank_index:int = 0\n) {\n  reaction(startup) {=\n    std::cout << "My bank index:" << bank_index << std::endl;\n  =}\n}\nreactor Parent (\n  bank_index:int = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target Python;\nreactor Child (\n  bank_index = 0\n) {\n  reaction(startup) {=\n    print(f"My bank index: {self.bank_index}.")\n  =}\n}\nreactor Parent (\n  bank_index = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n
    target TypeScript\nreactor Child {\n  reaction(startup) {=\n    console.log(`My bank index ${this.getBankIndex()}`)\n  =}\n}\nreactor Parent {\n  c = new[2] Child()\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Rust;\nreactor Child (\n  bank_index:usize = 0\n) {\n  state bank_index = bank_index;\n  reaction(startup) {=\n    println!("My bank index: {}.", self.bank_index);\n  =}\n}\nreactor Parent (\n  bank_index:usize = 0\n) {\n  c = new[2] Child();\n}\nmain reactor {\n  p = new[2] Parent();\n}\n
    \n

    $end(ChildBank)$

    \n\"Lingua\n

    In this program, the Parent reactor contains a bank of Child reactor instances\nwith a width of 2. In the main reactor, a bank of Parent reactors is\ninstantiated with a width of 2, therefore, creating 4 Child instances in the program in total.\nThe output of this program will be:

    \n
    My bank index: 0.\nMy bank index: 1.\nMy bank index: 0.\nMy bank index: 1.
    \n

    The order of these outputs will be nondeterministic if the execution is multithreaded (which it will be by default) because there is no dependence between the reactions, and, hence, they can execute in parallel.

    \n

    The bank index of a container (parent) reactor can be passed down to\ncontained (child) reactors. For example, note the following program:

    \n

    $start(ChildParentBank)$

    \n
    target C\nreactor Child(bank_index: int = 0, parent_bank_index: int = 0) {\n  reaction(startup) {=\n    printf(\n        "My bank index: %d. My parent's bank index: %d.\\n",\n        self->bank_index, self->parent_bank_index\n    );\n  =}\n}\nreactor Parent(bank_index: int = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Cpp\nreactor Child(bank_index: int(0), parent_bank_index: int(0)) {\n  reaction(startup) {=\n    std::cout <<"My bank index: " << bank_index << " My parent's bank index: " << parent_bank_index << std::endl;\n  =}\n}\nreactor Parent(bank_index: int(0)) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Python\nreactor Child(bank_index=0, parent_bank_index=0) {\n  reaction(startup) {=\n    print(\n        f"My bank index: {self.bank_index}. "\n        f"My parent's bank index: {self.parent_bank_index}."\n    )\n  =}\n}\nreactor Parent(bank_index=0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target TypeScript\nreactor Child(parentBankIndex: number = 0) {\n  reaction(startup) {=\n    console.log(`My bank index: ${this.getBankIndex()} My parent's bank index: ${parentBankIndex}`)\n  =}\n}\nreactor Parent {\n  c = new[2] Child(parentBankIndex = {= this.getBankIndex() =})\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Rust\nreactor Child(bank_index: usize = 0, parent_bank_index: usize = 0) {\n  state bank_index = bank_index\n  state parent_bank_index = parent_bank_index\n  reaction(startup) {=\n    println!(\n        "My bank index: {}. My parent's bank index: {}.",\n        self.bank_index,\n        self.parent_bank_index,\n    );\n  =}\n}\nreactor Parent(bank_index: usize = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n

    $end(ChildParentBank)$

    \n

    In this example, the bank index of the Parent reactor is passed to the\nparent_bank_index parameter of the Child reactor instances.\nThe output from this program will be:

    \n
    My bank index: 1. My parent's bank index: 1.\nMy bank index: 0. My parent's bank index: 0.\nMy bank index: 0. My parent's bank index: 1.\nMy bank index: 1. My parent's bank index: 0.
    \n

    Again, note that the order of these outputs is nondeterministic.

    \n

    Finally, members of contained banks of reactors can be individually addressed in\nthe body of reactions of the parent reactor if their input/output port appears\nin the reaction signature. For example, note the following program:

    \n

    $start(ChildParentBank2)$

    \n
    target C\nreactor Child(bank_index: int = 0, parent_bank_index: int = 0) {\n  output out: int\n  reaction(startup) -> out {=\n    lf_set(out, self->parent_bank_index * 2 + self->bank_index);\n  =}\n}\nreactor Parent(bank_index: int = 0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for (int i=0; i < c_width; i++) {\n        printf("Received %d from child %d.\\n", c[i].out->value, i);\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Cpp\nreactor Child(bank_index: int(0), parent_bank_index: int(0)) {\n  output out: int\n  reaction(startup) -> out {=\n    out.set(parent_bank_index * 2 + bank_index);\n  =}\n}\nreactor Parent(bank_index: int(0)) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for (auto i = 0ul; i < c.size(); i++) {\n        std::cout << "Received " << *c[i].out.get() <<" from child " << i << std::endl;\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target Python\nreactor Child(bank_index=0, parent_bank_index=0) {\n  output out\n  reaction(startup) -> out {=\n    out.set(self.parent_bank_index * 2 + self.bank_index)\n  =}\n}\nreactor Parent(bank_index=0) {\n  c = new[2] Child(parent_bank_index=bank_index)\n  reaction(c.out) {=\n    for i, child in enumerate(c):\n        print(f"Received {child.out.value} from child {i}.")\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    target TypeScript\nreactor Child(parentBankIndex: number = 0) {\n  output out: number\n  reaction(startup) -> out {=\n    out = parentBankIndex * 2 + this.getBankIndex()\n  =}\n}\nreactor Parent {\n  c = new[2] Child(parentBankIndex = {= this.getBankIndex() =})\n  reaction(c.out) {=\n    for (let i = 0; i < c.length; i++) {\n        console.log(`Received ${c[i].out} from child ${i}`)\n    }\n  =}\n}\nmain reactor {\n  p = new[2] Parent()\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/ChildParentBank2.lf\n
    \n

    $end(ChildParentBank2)$

    \n\"Lingua\n

    Running this program will give something like the following:

    \n
    Received 0 from child 0.\nReceived 1 from child 1.\nReceived 2 from child 0.\nReceived 3 from child 1.
    \n
    \n

    Note the usage of c_width, which holds the width of the c bank of reactors.

    \n
    \n
    \n

    Note that len(c) can be used to get the width of the bank, and for p in c or for (i, p) in enumerate(c) can be used to iterate over the bank members.

    \n
    \n
    \n

    Note that c.size() can be used to get the width of the bank c.

    \n
    \n
    \n

    Note that that bank instance c in TypeScript is an array, so c.length is the width of the bank, and the bank members are referenced by indexing the array, as in c[i].

    \n
    \n
    \n

    FIXME: How to get the width of the bank in target code?

    \n
    \n

    Combining Banks and Multiports

    \n

    Banks of reactors may be combined with multiports, as in the following example:

    \n

    $start(MultiportToBank)$

    \n
    target C\nreactor Source {\n  output[3] out: int\n  reaction(startup) -> out {=\n    for(int i = 0; i < out_width; i++) {\n      lf_set(out[i], i);\n    }\n  =}\n}\nreactor Destination(bank_index: int = 0) {\n  input in: int\n  reaction(in) {=\n    printf("Destination %d received %d.\\n", self->bank_index, in->value);\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.in\n}\n
    \n
    target Cpp\nreactor Source {\n  output[3] out: int\n  reaction(startup) -> out {=\n    for(int i = 0; i < out.size(); i++) {\n      out[i].set(i);\n    }\n  =}\n}\nreactor Destination(bank_index: int(0)) {\n  input in: int\n  reaction(in) {=\n    std::cout << "Destination " << bank_index << " received " << *in.get() << std::endl;\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.in\n}\n
    \n
    target Python\nreactor Source {\n  output[3] out\n  reaction(startup) -> out {=\n    for i, port in enumerate(out):\n      port.set(i)\n  =}\n}\nreactor Destination(bank_index=0) {\n  input inp\n  reaction(inp) {=\n    print(f"Destination {self.bank_index} received {inp.value}.")\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n
    target TypeScript\nreactor Source {\n  output[3] out: number\n  reaction(startup) -> out {=\n     for (let i = 0 ; i < out.length; i++) {\n        out[i] = i\n    }\n  =}\n}\nreactor Destination {\n  input inp: number\n  reaction(inp) {=\n    console.log(`Destination ${this.getBankIndex()} received ${inp}`)\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n
    target Rust\nreactor Source {\n  output[3] out: usize\n  reaction(startup) -> out {=\n    for (i, o) in out.into_iter().enumerate() {\n      ctx.set(o, i);\n    }\n  =}\n}\nreactor Destination(bank_index: usize = 0) {\n  state bank_index = bank_index\n  input inp: usize\n  reaction(inp) {=\n    println!(\n        "Destination {} received {}.",\n        self.bank_index,\n        ctx.get(inp).unwrap(),\n    );\n  =}\n}\nmain reactor MultiportToBank {\n  a = new Source()\n  b = new[3] Destination()\n  a.out -> b.inp\n}\n
    \n

    $end(MultiportToBank)$

    \n\"Lingua\n

    The three outputs from the Source instance a will be sent, respectively, to each of three instances of Destination, b[0], b[1], and b[2]. The result of the program will be something like the following:

    \n
    Destination 0 received 0.\nDestination 1 received 1.\nDestination 2 received 2.
    \n

    Again, the order is nondeterministic in a multithreaded context.

    \n

    The reactors in a bank may themselves have multiports. In all cases, the number of ports on the left of a connection must match the number on the right, unless the ones on the left are iterated, as explained next.

    \n

    Broadcast Connections

    \n

    Occasionally, you will want to have fewer ports on the left of a connection and have their outputs used repeatedly to broadcast to the ports on the right. In the following example, the outputs from an ordinary port are broadcast to the inputs of all instances of a bank of reactors:

    \n
    reactor Source {\n  output out:int;\n  reaction(startup) -> out {=\n    ... write to out ...\n  =}\n}\nreactor Destination {\n  input in:int;\n  reaction(in) {=\n    ... read from in ...\n  =}\n}\nmain reactor ThreadedThreaded(width:int(4)) {\n  a = new Source();\n  d = new[width] Destination();\n  (a.out)+ -> d.in;\n}\n
    \n

    The syntax (a.out)+ means “repeat the output port a.out one or more times as needed to supply all the input ports of d.in.” The content inside the parentheses can be a comma-separated list of ports, the ports inside can be ordinary ports or multiports, and the reactors inside can be ordinary reactors or banks of reactors. In all cases, the number of ports inside the parentheses on the left must divide the number of ports on the right.

    \n

    Interleaved Connections

    \n
    \n

    Sometimes, we don’t want to broadcast messages to all reactors, but need more fine-grained control as to which reactor within a bank receives a message. If we have separate source and destination reactors, this can be done by combining multiports and banks as was shown in Combining Banks and Multiports. Setting a value on the index n of the output multiport, will result in a message to the n-th reactor instance within the destination bank. However, this pattern gets slightly more complicated, if we want to exchange addressable messages between instances of the same bank. This pattern is shown in the following example:

    \n

    $start(Interleaved)$

    \n
    target C\nreactor Node(num_nodes: size_t = 4, bank_index: int = 0) {\n  input[num_nodes] in: int\n  output[num_nodes] out: int\n  reaction(startup) -> out {=\n    lf_set(out[1], 42);\n    printf("Bank index %d sent 42 on channel 1.\\n", self->bank_index);\n  =}\n  reaction(in) {=\n    for (int i = 0; i < in_width; i++) {\n      if (in[i]->is_present) {\n        printf("Bank index %d received %d on channel %d.\\n",\n          self->bank_index, in[i]->value, i\n        );\n      }\n    }\n  =}\n}\nmain reactor(num_nodes: size_t = 4) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.in)\n}\n
    \n
    target Cpp\nreactor Node(num_nodes: size_t(4), bank_index: int(0)) {\n  input[num_nodes] in: int\n  output[num_nodes] out: int\n  reaction(startup) -> out {=\n    out[1].set(42);\n    std::cout << "Bank index " << bank_index << " sent 42 on channel 1." << std::endl;\n  =}\n  reaction(in) {=\n    for (auto i = 0ul; i < in.size(); i++) {\n      if (in[i].is_present()) {\n        std::cout << "Bank index " << bank_index\n          << " received " << *in[i].get() << " on channel" << std::endl;\n      }\n    }\n  =}\n}\nmain reactor(num_nodes: size_t(4)) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.in)\n}\n
    \n
    target Python\nreactor Node(num_nodes=4, bank_index=0) {\n  input[num_nodes] inp\n  output[num_nodes] out\n  reaction(startup) -> out {=\n    out[1].set(42)\n    print(f"Bank index {self.bank_index} sent 42 on channel 1.")\n  =}\n  reaction(inp) {=\n    for i, port in enumerate(inp):\n      if port.is_present:\n        print(\n          f"Bank index {self.bank_index} received {port.value} on channel {i}.",\n        )\n  =}\n}\nmain reactor(num_nodes=4) {\n  nodes = new[num_nodes] Node(num_nodes=num_nodes)\n  nodes.out -> interleaved(nodes.inp)\n}\n
    \n
    WARNING: No source file found: ../code/ts/src/Interleaved.lf\ntarget TypeScript\nreactor Node(numNodes: number(4)) {\n    input[numNodes] inp: number\n    output[numNodes] out: number\n    reaction (startup) -> out {=\n        out[1] = 42\n        console.log(`Bank index ${this.getBankIndex()} sent 42 on channel 1.`)\n    =}\n    reaction (inp) {=\n        for (let i = 0; i < in.length; i++) {\n            if (in[i] !== undefined) {\n                console.log(`Bank index ${this.getBankIndex()} received ${in[i]} on channel ${i}`)\n            }\n        }\n    =}\n}\nmain reactor(numNodes: number(4)) {\n    nodes = new[numNodes] Node(numNodes=numNodes);\n    nodes.out -> interleaved(nodes.inp)\n}\n
    \n
    WARNING: No source file found: ../code/rs/src/Interleaved.lf\n
    \n

    $end(Interleaved)$

    \n\"Lingua\n

    In the above program, four instance of Node are created, and, at startup, each instance sends 42 to its second (index 1) output channel. The result is that the second bank member (bank_index 1) will receive the number 42 on each input channel of its multiport input. Running this program gives something like the following:

    \n
    Bank index 0 sent 42 on channel 1.\nBank index 1 sent 42 on channel 1.\nBank index 2 sent 42 on channel 1.\nBank index 3 sent 42 on channel 1.\nBank index 1 received 42 on channel 0.\nBank index 1 received 42 on channel 1.\nBank index 1 received 42 on channel 2.\nBank index 1 received 42 on channel 3.
    \n

    In bank index 1, the 0-th channel receives from bank_index 0, the 1-th channel from bank_index 1, etc. In effect, the choice of output channel specifies the destination reactor in the bank, and the input channel specifies the source reactor from which the input comes.

    \n

    This style of connection is accomplished using the new keyword $interleaved$ in the connection. Normally, a port reference such as nodes.out where nodes is a bank and out is a multiport, would list all the individual ports by first iterating over the banks and then, for each bank index, iterating over the ports. If we consider the tuple (b,p) to denote the index b within the bank and the index p within the multiport, then the following list is created: (0,0), (0,1), (0,2), (0,3), (1,0), (1,1), (1,2), (1,3), (2,0), (2,1), (2,2), (2,3), (3,0), (3,1), (3,2), (3,3). However, if we use $interleaved$(nodes.out) instead, the connection logic will iterate over the ports first and then the banks, creating the following list: (0,0), (1,0), (2,0), (3,0), (0,1), (1,1), (2,1), (3,1), (0,2), (1,2), (2,2), (3,2), (0,3), (1,3), (2,3), (3,3). By combining a normal port reference with a interleaved reference, we can construct a fully connected network. The figure below visualizes this how this pattern would look without banks or multiports:

    \n\n \n \"Lingua\n \n

    If we were to use a normal connection nodes.out -> nodes.in; instead of the $interleaved$ connection, then the following pattern would be created:

    \n\n \n \"Lingua\n \n

    Effectively, this connects each reactor instance to itself, which isn’t very useful.

    \n
    \n
    \n

    The $interleaved$ keyword is not supported by $target-language$.

    \n
    ","headings":[{"value":"Multiports","depth":2},{"value":"Sparse Inputs","depth":2},{"value":"Parameterized Widths","depth":2},{"value":"Connecting Reactors with Different Widths","depth":2},{"value":"Banks of Reactors","depth":2},{"value":"Initializing Bank Members from a Table","depth":2},{"value":"Contained Banks","depth":2},{"value":"Combining Banks and Multiports","depth":2},{"value":"Broadcast Connections","depth":2},{"value":"Interleaved Connections","depth":2}],"frontmatter":{"permalink":"/docs/handbook/multiports-and-banks","title":"Multiports and Banks","oneline":"Multiports and Banks of Reactors.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Deadlines","oneline":"Deadlines in Lingua Franca.","permalink":"/docs/handbook/deadlines"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","permalink":"/docs/handbook/generics"}}}},"pageContext":{"id":"1-multiports-and-banks","slug":"/docs/handbook/multiports-and-banks","repoPath":"/packages/documentation/copy/en/topics/Multiports and Banks.md","previousID":"cb17ff3f-7a86-5f3b-95f7-e7d1f4e920c0","nextID":"2a0b9619-72b6-5ee7-8a38-83ff8d48005a","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/overview/page-data.json b/page-data/docs/handbook/overview/page-data.json index 792979521..b28318aed 100644 --- a/page-data/docs/handbook/overview/page-data.json +++ b/page-data/docs/handbook/overview/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/overview","result":{"data":{"markdownRemark":{"id":"8f275b71-8f6a-5273-83d2-6037d55a4966","excerpt":"Lingua Franca (LF) is a polyglot coordination language for concurrent and possibly time-sensitive applications ranging from low-level embedded code to…","html":"

    Lingua Franca (LF) is a polyglot coordination language for concurrent and possibly time-sensitive applications ranging from low-level embedded code to distributed cloud and edge applications. An LF program specifies the interactions between components called reactors. The emphasis of the framework is on ensuring deterministic interaction with explicit management of timing. The logic of each reactor is written in one of a suite of target languages (currently C, C++, Python, and TypeScript) and can integrate legacy code in those languages. A code generator synthesizes one or more programs in the target language, which are then compiled using standard toolchains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.

    \n

    Lingua Franca programs are compositions of reactors, whose functionality is decomposed into reaction, which are written in the target languages. Reactors are similar to actors, software components that send each other messages, but unlike classical actors, messages are timestamped, and concurrent composition of reactors is deterministic by default. When nondeterministic interactions are tolerable or desired, they must be explicitly coded. LF itself is a polyglot composition language, not a complete programming language. LF describes the interfaces and composition of reactors. See our publications and presentations on reactors and Lingua Franca.

    \n

    The language and compiler infrastructure is very much under development. An IDE based on Eclipse and Xtext is under development, and command-line tools are also provided. LF is, by design, extensible. To support a new target language, a code generator and a runtime system capable of coordinating the execution of a composition of reactors must be developed.

    \n

    The C runtime consists of a few thousand lines of extensively commented code, occupies tens of kilobytes for a minimal application, and is extremely fast, making it suitable even for deeply embedded microcontroller platforms. It has been tested on Linux, Windows, and Mac platforms, as well as some bare-iron platforms. On POSIX-compliant platforms, it supports multithreaded execution, automatically exploiting multiple cores while preserving determinism. It includes features for real-time execution and is particularly well suited to take advantage of platforms with predictable execution times, such as PRET machines. A distributed execution mechanism is under development that takes advantage of clock synchronization when that is available to achieve truly distributed coordination while maintaining determinism.

    \n

    Reactors

    \n

    Reactors are informally described via the following principles:

    \n
      \n
    1. Components — Reactors can have input ports, actions, and timers, all of which are triggers. They can also have output ports, local state, parameters, and an ordered list of reactions.
    2. \n
    3. Composition — A reactor may contain other reactors and manage their connections. The connections define the flow of messages, and two reactors can be connected if they are contained by the same reactor or one is directly contained in the other (i.e., connections span at most one level of hierarchy). An output port may be connected to multiple input ports, but an input port can only be connected to a single output port.
    4. \n
    5. Events — Messages sent from one reactor to another, and timer and action events each have a timestamp, a value on a logical time line. These are timestamped events that can trigger reactions. Each port, timer, and action can have at most one such event at any logical time. An event may carry a value that will be passed as an argument to triggered reactions.
    6. \n
    7. Reactions — A reaction is a procedure in a target language that is invoked in response to a trigger event, and only in response to a trigger event. A reaction can read input ports, even those that do not trigger it, and can produce outputs, but it must declare all inputs that it may read and output ports to which it may write. All inputs that it reads and outputs that it produces bear the same timestamp as its triggering event. I.e., the reaction itself is logically instantaneous, so any output events it produces are logically simultaneous with the triggering event (the two events bear the same timestamp).
    8. \n
    9. Flow of Time — Successive invocations of any single reaction occur at strictly increasing logical times. Any messages that are not read by a reaction triggered at the timestamp of the message are lost.
    10. \n
    11. Mutual Exclusion — The execution of any two reactions of the same reactor are mutually exclusive (atomic with respect to one another). Moreover, any two reactions that are invoked at the same logical time are invoked in the order specified by the reactor definition. This avoids race conditions between reactions accessing the reactor state variables.
    12. \n
    13. Determinism — A Lingua Franca program is deterministic unless the programmer explicit uses nondeterministic constructs. Given the same input data, a composition of reactors has exactly one correct behavior. This makes Lingua Franca programs testable.
    14. \n
    15. Concurrency — Dependencies between reactions are explicitly declared in a Lingua Franca program, and reactions that are not dependent on one another can be executed in parallel on a multicore machine. If the target provides a distributed runtime, using Ptides for example, then execution can also be distributed across networks.
    16. \n
    \n

    Time

    \n

    Lingua Franca has a notion of logical time, where every message occurs at a logical instant and reactions to messages are logically instantaneous. At a logical time instant, each reactor input will either have a message (the input is present) or will not (the input is absent). Reactions belonging to the reactor may be triggered by a present input. Reactions may also be triggered by timers or actions. A reaction may produce outputs, in which case, inputs to which the output is connected will become present at the same logical time instant. Outputs, therefore, are logically simultaneous with the inputs that cause them. A reaction may also schedule actions which will trigger reactions of the same reactor at a later logical time.

    \n

    In the C target,\na timestamp is an unsigned 64-bit integer which, on most platforms,\nspecifies the number of nanoseconds since January 1, 1970.\nSince a 64-bit number has a limited range,\nthis measure of time instants will overflow in approximately the year 2554.\nWhen an LF program starts executing, logical time is (normally) set to the current physical time provided by the operating system.\n(On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    At the starting logical time, reactions that specify a startup trigger will execute. Also, any reactions that are triggered by a timer with a zero offset will execute. Any outputs produced by these reactions will have the same logical time and will trigger execution of any downstream reactions. Those downstream reactions will be invoked at the same logical time unless some connection to the downstream reactor uses the after keyword to specify a time delay. After all reactions at the starting logical time have completed, then time will advance to the logical time of the earliest next event. The earliest next event may be specified by a timer, by an after keyword on a connection, or by a logical or physical action. Once logical time advances, any reactions that are triggered by events at that logical will be invoked, as will any reactions triggered by outputs produced by those reactions.

    \n

    Time in Lingua Franca is actually superdense time, meaning that a logical time may have the same numerical value but also be strictly later than another logical time. When an action is scheduled with a delay of zero, it occurs at such a strictly later time, one microstep later. See the actions description. The after keyword with a time delay of 0 (zero) will also cause the downstream reactions to execute in the next microstep.

    \n

    At any logical time, if any two reactions belonging to the same reactor are triggered, they will be executed\natomically in the order in which they are defined in the reactor. Dependencies across reactors (connections with no after) will also result in sequential execution. Specifically, if any reaction of reactor A produces an output that triggers a reaction of reactor B, then B’s reaction will execute only after A’s reaction has completed execution. Modulo these two ordering constraints, reactions may execute in parallel on multiple cores or even across networks.

    \n

    Reactions are given in a target language, whereas inputs, outputs, actions, and the dependencies among them are defined in Lingua Franca. LF is, therefore, a kind of coordination language rather than a programming language.

    \n

    Real-Time Systems

    \n

    Reactions may have delays and deadlines associated with them. This information can be used to perform earliest deadline first (EDF) scheduling. Also, in combination with execution-time analysis of reactions, it should be possible to determine at compile time whether the imposed deadlines can be met, although these analysis tools have not yet been developed. Of particular interest is to deploy reactors on platforms such as FlexPRET and Patmos, which are designed for predictable timing; execution-time estimates for these architectures will be much tighter than currently possible with ordinary microprocessors.

    \n

    Schedulability Analysis of LF Programs

    \n
      \n
    • Start with classic schedulability test: critical instant (Liu and Layland)\n
        \n
      • Possible not the worst-case?
      • \n
      • Question: can not producing an output lead to a timing anomaly?
      • \n
      \n
    • \n
    • Classic schedulability analysis becomes messy when deadline with locks for communication (priority inversion, locking protocols to avoid it, recursive schedulability analysis)\n
        \n
      • We do not use shared data protected by locks +1
      • \n
      \n
    • \n
    • First (pessimistic) approach: all input events and timers fire at the same time, check all execution chains (reactions in actors) need to finish before the actuator deadlines.
    • \n
    • Delays (timer, after, scheduled actions) break the dependency chain\n
        \n
      • Schedulability analysis can be broken up into sub-chains +1
      • \n
      \n
    • \n
    • For now assume no preemptions, this also enables WCET analysis of reactions
    • \n
    • We could also use a big hammer: model the LF program as timed automata and do model checking (e.g., UupAal)
    • \n
    \n

    To Do List

    \n

    Lingua Franca is a work in progress. See our project page for an overview of ongoing and future work.

    ","headings":[{"value":"Reactors","depth":2},{"value":"Time","depth":2},{"value":"Real-Time Systems","depth":2},{"value":"Schedulability Analysis of LF Programs","depth":3},{"value":"To Do List","depth":2}],"frontmatter":{"permalink":"/docs/handbook/overview","title":"Overview","oneline":"Overview of Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Tutorial Video","oneline":"Tutorial video presented by the Lingua Franca team.","permalink":"/docs/handbook/tutorial-video"}}}},"pageContext":{"id":"0-overview","slug":"/docs/handbook/overview","repoPath":"/packages/documentation/copy/en/topics/Overview.md","nextID":"9c60c2f7-2bab-51e5-bbc5-e7957b224b3a","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/overview","result":{"data":{"markdownRemark":{"id":"8f275b71-8f6a-5273-83d2-6037d55a4966","excerpt":"Lingua Franca (LF) is a polyglot coordination language for concurrent and possibly time-sensitive applications ranging from low-level embedded code to…","html":"

    Lingua Franca (LF) is a polyglot coordination language for concurrent and possibly time-sensitive applications ranging from low-level embedded code to distributed cloud and edge applications. An LF program specifies the interactions between components called reactors. The emphasis of the framework is on ensuring deterministic interaction with explicit management of timing. The logic of each reactor is written in one of a suite of target languages (currently C, C++, Python, and TypeScript) and can integrate legacy code in those languages. A code generator synthesizes one or more programs in the target language, which are then compiled using standard toolchains. If the application has exploitable parallelism, then it executes transparently on multiple cores without compromising determinacy. A distributed application translates into multiple programs and scripts to launch those programs on distributed machines. The communication fabric connecting components is synthesized as part of the programs.

    \n

    Lingua Franca programs are compositions of reactors, whose functionality is decomposed into reaction, which are written in the target languages. Reactors are similar to actors, software components that send each other messages, but unlike classical actors, messages are timestamped, and concurrent composition of reactors is deterministic by default. When nondeterministic interactions are tolerable or desired, they must be explicitly coded. LF itself is a polyglot composition language, not a complete programming language. LF describes the interfaces and composition of reactors. See our publications and presentations on reactors and Lingua Franca.

    \n

    The language and compiler infrastructure is very much under development. An IDE based on Eclipse and Xtext is under development, and command-line tools are also provided. LF is, by design, extensible. To support a new target language, a code generator and a runtime system capable of coordinating the execution of a composition of reactors must be developed.

    \n

    The C runtime consists of a few thousand lines of extensively commented code, occupies tens of kilobytes for a minimal application, and is extremely fast, making it suitable even for deeply embedded microcontroller platforms. It has been tested on Linux, Windows, and Mac platforms, as well as some bare-iron platforms. On POSIX-compliant platforms, it supports multithreaded execution, automatically exploiting multiple cores while preserving determinism. It includes features for real-time execution and is particularly well suited to take advantage of platforms with predictable execution times, such as PRET machines. A distributed execution mechanism is under development that takes advantage of clock synchronization when that is available to achieve truly distributed coordination while maintaining determinism.

    \n

    Reactors

    \n

    Reactors are informally described via the following principles:

    \n
      \n
    1. Components — Reactors can have input ports, actions, and timers, all of which are triggers. They can also have output ports, local state, parameters, and an ordered list of reactions.
    2. \n
    3. Composition — A reactor may contain other reactors and manage their connections. The connections define the flow of messages, and two reactors can be connected if they are contained by the same reactor or one is directly contained in the other (i.e., connections span at most one level of hierarchy). An output port may be connected to multiple input ports, but an input port can only be connected to a single output port.
    4. \n
    5. Events — Messages sent from one reactor to another, and timer and action events each have a timestamp, a value on a logical time line. These are timestamped events that can trigger reactions. Each port, timer, and action can have at most one such event at any logical time. An event may carry a value that will be passed as an argument to triggered reactions.
    6. \n
    7. Reactions — A reaction is a procedure in a target language that is invoked in response to a trigger event, and only in response to a trigger event. A reaction can read input ports, even those that do not trigger it, and can produce outputs, but it must declare all inputs that it may read and output ports to which it may write. All inputs that it reads and outputs that it produces bear the same timestamp as its triggering event. I.e., the reaction itself is logically instantaneous, so any output events it produces are logically simultaneous with the triggering event (the two events bear the same timestamp).
    8. \n
    9. Flow of Time — Successive invocations of any single reaction occur at strictly increasing logical times. Any messages that are not read by a reaction triggered at the timestamp of the message are lost.
    10. \n
    11. Mutual Exclusion — The execution of any two reactions of the same reactor are mutually exclusive (atomic with respect to one another). Moreover, any two reactions that are invoked at the same logical time are invoked in the order specified by the reactor definition. This avoids race conditions between reactions accessing the reactor state variables.
    12. \n
    13. Determinism — A Lingua Franca program is deterministic unless the programmer explicit uses nondeterministic constructs. Given the same input data, a composition of reactors has exactly one correct behavior. This makes Lingua Franca programs testable.
    14. \n
    15. Concurrency — Dependencies between reactions are explicitly declared in a Lingua Franca program, and reactions that are not dependent on one another can be executed in parallel on a multicore machine. If the target provides a distributed runtime, using Ptides for example, then execution can also be distributed across networks.
    16. \n
    \n

    Time

    \n

    Lingua Franca has a notion of logical time, where every message occurs at a logical instant and reactions to messages are logically instantaneous. At a logical time instant, each reactor input will either have a message (the input is present) or will not (the input is absent). Reactions belonging to the reactor may be triggered by a present input. Reactions may also be triggered by timers or actions. A reaction may produce outputs, in which case, inputs to which the output is connected will become present at the same logical time instant. Outputs, therefore, are logically simultaneous with the inputs that cause them. A reaction may also schedule actions which will trigger reactions of the same reactor at a later logical time.

    \n

    In the C target,\na timestamp is an unsigned 64-bit integer which, on most platforms,\nspecifies the number of nanoseconds since January 1, 1970.\nSince a 64-bit number has a limited range,\nthis measure of time instants will overflow in approximately the year 2554.\nWhen an LF program starts executing, logical time is (normally) set to the current physical time provided by the operating system.\n(On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    At the starting logical time, reactions that specify a startup trigger will execute. Also, any reactions that are triggered by a timer with a zero offset will execute. Any outputs produced by these reactions will have the same logical time and will trigger execution of any downstream reactions. Those downstream reactions will be invoked at the same logical time unless some connection to the downstream reactor uses the after keyword to specify a time delay. After all reactions at the starting logical time have completed, then time will advance to the logical time of the earliest next event. The earliest next event may be specified by a timer, by an after keyword on a connection, or by a logical or physical action. Once logical time advances, any reactions that are triggered by events at that logical will be invoked, as will any reactions triggered by outputs produced by those reactions.

    \n

    Time in Lingua Franca is actually superdense time, meaning that a logical time may have the same numerical value but also be strictly later than another logical time. When an action is scheduled with a delay of zero, it occurs at such a strictly later time, one microstep later. See the actions description. The after keyword with a time delay of 0 (zero) will also cause the downstream reactions to execute in the next microstep.

    \n

    At any logical time, if any two reactions belonging to the same reactor are triggered, they will be executed\natomically in the order in which they are defined in the reactor. Dependencies across reactors (connections with no after) will also result in sequential execution. Specifically, if any reaction of reactor A produces an output that triggers a reaction of reactor B, then B’s reaction will execute only after A’s reaction has completed execution. Modulo these two ordering constraints, reactions may execute in parallel on multiple cores or even across networks.

    \n

    Reactions are given in a target language, whereas inputs, outputs, actions, and the dependencies among them are defined in Lingua Franca. LF is, therefore, a kind of coordination language rather than a programming language.

    \n

    Real-Time Systems

    \n

    Reactions may have delays and deadlines associated with them. This information can be used to perform earliest deadline first (EDF) scheduling. Also, in combination with execution-time analysis of reactions, it should be possible to determine at compile time whether the imposed deadlines can be met, although these analysis tools have not yet been developed. Of particular interest is to deploy reactors on platforms such as FlexPRET and Patmos, which are designed for predictable timing; execution-time estimates for these architectures will be much tighter than currently possible with ordinary microprocessors.

    \n

    Schedulability Analysis of LF Programs

    \n
      \n
    • Start with classic schedulability test: critical instant (Liu and Layland)\n
        \n
      • Possible not the worst-case?
      • \n
      • Question: can not producing an output lead to a timing anomaly?
      • \n
      \n
    • \n
    • Classic schedulability analysis becomes messy when deadline with locks for communication (priority inversion, locking protocols to avoid it, recursive schedulability analysis)\n
        \n
      • We do not use shared data protected by locks +1
      • \n
      \n
    • \n
    • First (pessimistic) approach: all input events and timers fire at the same time, check all execution chains (reactions in actors) need to finish before the actuator deadlines.
    • \n
    • Delays (timer, after, scheduled actions) break the dependency chain\n
        \n
      • Schedulability analysis can be broken up into sub-chains +1
      • \n
      \n
    • \n
    • For now assume no preemptions, this also enables WCET analysis of reactions
    • \n
    • We could also use a big hammer: model the LF program as timed automata and do model checking (e.g., UupAal)
    • \n
    \n

    To Do List

    \n

    Lingua Franca is a work in progress. See our project page for an overview of ongoing and future work.

    ","headings":[{"value":"Reactors","depth":2},{"value":"Time","depth":2},{"value":"Real-Time Systems","depth":2},{"value":"Schedulability Analysis of LF Programs","depth":3},{"value":"To Do List","depth":2}],"frontmatter":{"permalink":"/docs/handbook/overview","title":"Overview","oneline":"Overview of Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Tutorial Video","oneline":"Tutorial video presented by the Lingua Franca team.","permalink":"/docs/handbook/tutorial-video"}}}},"pageContext":{"id":"0-overview","slug":"/docs/handbook/overview","repoPath":"/packages/documentation/copy/en/topics/Overview.md","nextID":"9c60c2f7-2bab-51e5-bbc5-e7957b224b3a","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/parameters-and-state-variables/page-data.json b/page-data/docs/handbook/parameters-and-state-variables/page-data.json index d2815f997..0678f24e9 100644 --- a/page-data/docs/handbook/parameters-and-state-variables/page-data.json +++ b/page-data/docs/handbook/parameters-and-state-variables/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/parameters-and-state-variables","result":{"data":{"markdownRemark":{"id":"6b93641e-db40-5450-88fc-265623991a01","excerpt":"$page-showing-target$ Parameter Declaration A reactor class definition can parameterized as follows: Each parameter has a type annotation, written :$page-showing-target$

    \n

    Parameter Declaration

    \n

    A reactor class definition can parameterized as follows:

    \n
    \n
    reactor <class-name>(<param-name>:<type> = <expr>, ...) {\n    ...\n}\n
    \n

    Each parameter has a type annotation, written :<type>, where <type> has one of the following forms:

    \n
      \n
    • An identifier, such as int, possibly followed by a type argument, e.g. vector<int>.
    • \n
    • An array type type[] and type[integer].
    • \n
    • The keyword $time$, which designates a time value.
    • \n
    • A code block delimited by {= ... =}, where the contents is any valid type in the target language.
    • \n
    \n
    \n
    \n
      \n
    • A pointer type, such as int*.
    • \n
    \n
    \n
    \n

    Types ending with a * are treated specially by the C target. See the Target Language Details.

    \n

    To use strings conveniently in the C target, the “type” string is an alias for {=const char*=}.

    \n
    \n
    \n

    For example, {= int | null =} defines nullable integer type in TypeScript.

    \n
    \n
    \n
    reactor <class-name>(<param-name> = <expr>, ... ) {\n    ...\n}\n
    \n
    \n

    Depending on the target, the type may be a generic type, which means that the type is parameter determined at the time the reactor class is instantiated.

    \n

    Each parameter must have a default value, written <param-name> = <expr>. An expression may be a numeric constant, a string enclosed in quotation marks, a time value such as 10 msec, a list of values, or target-language code enclosed in {= ... =}, for example. See Expressions for full details on what expressions are valid.

    \n

    For example, the Double reactor on the previous page can be replaced with a more general parameterized reactor Scale as follows:

    \n

    $start(Scale)$

    \n
    target C\nreactor Scale(factor: int = 2) {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    lf_set(y, x->value * self->factor);\n  =}\n}\n
    \n
    target Cpp\nreactor Scale(factor: int(2)) {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    y.set(factor * *x.get());\n  =}\n}\n
    \n
    target Python\nreactor Scale(factor=2) {\n  input x\n  output y\n  reaction(x) -> y {=\n    y.set(x.value * self.factor)\n  =}\n}\n
    \n
    target TypeScript\nreactor Scale(factor: number = 2) {\n  input x: number\n  output y: number\n  reaction(x) -> y {=\n    if (x !== undefined) y = x * factor\n  =}\n}\n
    \n
    target Rust\nreactor Scale(factor: u32 = 2) {\n  state factor = factor\n  input x: u32\n  output y: u32\n  reaction(x) -> y {=\n    let x = ctx.get(x).unwrap();\n    ctx.set(y, x * self.factor);\n  =}\n}\n
    \n

    $end(Scale)$

    \n

    This reactor, given any input event x will produce an output y with value equal to the input scaled by the factor parameter. The default value of the factor parameter is 2, but this can be changed when the Scale reactor is instantiated.

    \n

    Notice how, within the body of a reaction, the code accesses the parameter value. This is different for each target language. In the C target, a self struct is provided that contains the parameter values.

    \n

    State Declaration

    \n

    A reactor declares a state variable as follows:

    \n
    \n
      state <name>:<type> = <value>\n
    \n

    The type can any of the same forms as for a parameter.

    \n
    \n
    \n
      state <name> = <value>\n
    \n
    \n

    The <value> is an initial value and, like parameter values, can be given as an expression or target language code with delimiters {= ... =}. The initial value can also be given as a parameter name. The value can be accessed and modified in a target-language-dependent way as illustrated by the following example:

    \n

    $start(Count)$

    \n
    target C\nreactor Count {\n  state count: int = 0\n  output y: int\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    lf_set(y, self->count++);\n  =}\n}\n
    \n
    target Cpp\nreactor Count {\n  state count: int(0)\n  output y: int\n  timer t(0, 100 ms)\n  reaction(t) -> y {=\n    y.set(count++);\n  =}\n}\n
    \n
    target Python\nreactor Count {\n  state count = 0\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(self.count)\n    self.count += 1\n  =}\n}\n
    \n
    target TypeScript\nreactor Count {\n  state count: number = 0\n  output y: number\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y = count++\n  =}\n}\n
    \n
    target Rust\nreactor Count {\n  state count: u32 = 0\n  output y: u32\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    ctx.set(y, self.count);\n    self.count += 1;\n  =}\n}\n
    \n

    $end(Count)$

    \n

    This reactor has an integer state variable named count, and each time its reaction is invoked, it outputs the value of that state variable and increments it. The reaction is triggered by a $timer$, discussed in the next section.

    \n

    Reset State Variables

    \n
    \n

    The $reset$ keyword is not supported in $target-language$ because modal reactors are not supported.

    \n
    \n
    \n

    A state variable declaration may be qualified with a $reset$ keyword as follows:

    \n
      reset state <name>:<type> = <value>\n
    \n
      reset state <name> = <value>\n
    \n

    When this is done, if the state variable or the reactor is within a mode of a modal reactor, then when the mode is entered via a reset transition, the state variable will be reset to its initial value. For details, see the Modal Reactors section.

    \n
    ","headings":[{"value":"Parameter Declaration","depth":2},{"value":"State Declaration","depth":2},{"value":"Reset State Variables","depth":2}],"frontmatter":{"permalink":"/docs/handbook/parameters-and-state-variables","title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","permalink":"/docs/handbook/inputs-and-outputs"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","permalink":"/docs/handbook/time-and-timers"}}}},"pageContext":{"id":"1-parameters-and-state-variables","slug":"/docs/handbook/parameters-and-state-variables","repoPath":"/packages/documentation/copy/en/topics/Parameters and State Variables.md","previousID":"dcdc6b32-76b0-570a-a6f8-23bb570863c7","nextID":"bba867ed-95b9-5017-b4f1-350e621e99da","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/parameters-and-state-variables","result":{"data":{"markdownRemark":{"id":"6b93641e-db40-5450-88fc-265623991a01","excerpt":"$page-showing-target$ Parameter Declaration A reactor class definition can parameterized as follows: Each parameter has a type annotation, written :$page-showing-target$

    \n

    Parameter Declaration

    \n

    A reactor class definition can parameterized as follows:

    \n
    \n
    reactor <class-name>(<param-name>:<type> = <expr>, ...) {\n    ...\n}\n
    \n

    Each parameter has a type annotation, written :<type>, where <type> has one of the following forms:

    \n
      \n
    • An identifier, such as int, possibly followed by a type argument, e.g. vector<int>.
    • \n
    • An array type type[] and type[integer].
    • \n
    • The keyword $time$, which designates a time value.
    • \n
    • A code block delimited by {= ... =}, where the contents is any valid type in the target language.
    • \n
    \n
    \n
    \n
      \n
    • A pointer type, such as int*.
    • \n
    \n
    \n
    \n

    Types ending with a * are treated specially by the C target. See the Target Language Details.

    \n

    To use strings conveniently in the C target, the “type” string is an alias for {=const char*=}.

    \n
    \n
    \n

    For example, {= int | null =} defines nullable integer type in TypeScript.

    \n
    \n
    \n
    reactor <class-name>(<param-name> = <expr>, ... ) {\n    ...\n}\n
    \n
    \n

    Depending on the target, the type may be a generic type, which means that the type is parameter determined at the time the reactor class is instantiated.

    \n

    Each parameter must have a default value, written <param-name> = <expr>. An expression may be a numeric constant, a string enclosed in quotation marks, a time value such as 10 msec, a list of values, or target-language code enclosed in {= ... =}, for example. See Expressions for full details on what expressions are valid.

    \n

    For example, the Double reactor on the previous page can be replaced with a more general parameterized reactor Scale as follows:

    \n

    $start(Scale)$

    \n
    target C\nreactor Scale(factor: int = 2) {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    lf_set(y, x->value * self->factor);\n  =}\n}\n
    \n
    target Cpp\nreactor Scale(factor: int(2)) {\n  input x: int\n  output y: int\n  reaction(x) -> y {=\n    y.set(factor * *x.get());\n  =}\n}\n
    \n
    target Python\nreactor Scale(factor=2) {\n  input x\n  output y\n  reaction(x) -> y {=\n    y.set(x.value * self.factor)\n  =}\n}\n
    \n
    target TypeScript\nreactor Scale(factor: number = 2) {\n  input x: number\n  output y: number\n  reaction(x) -> y {=\n    if (x !== undefined) y = x * factor\n  =}\n}\n
    \n
    target Rust\nreactor Scale(factor: u32 = 2) {\n  state factor = factor\n  input x: u32\n  output y: u32\n  reaction(x) -> y {=\n    let x = ctx.get(x).unwrap();\n    ctx.set(y, x * self.factor);\n  =}\n}\n
    \n

    $end(Scale)$

    \n

    This reactor, given any input event x will produce an output y with value equal to the input scaled by the factor parameter. The default value of the factor parameter is 2, but this can be changed when the Scale reactor is instantiated.

    \n

    Notice how, within the body of a reaction, the code accesses the parameter value. This is different for each target language. In the C target, a self struct is provided that contains the parameter values.

    \n

    State Declaration

    \n

    A reactor declares a state variable as follows:

    \n
    \n
      state <name>:<type> = <value>\n
    \n

    The type can any of the same forms as for a parameter.

    \n
    \n
    \n
      state <name> = <value>\n
    \n
    \n

    The <value> is an initial value and, like parameter values, can be given as an expression or target language code with delimiters {= ... =}. The initial value can also be given as a parameter name. The value can be accessed and modified in a target-language-dependent way as illustrated by the following example:

    \n

    $start(Count)$

    \n
    target C\nreactor Count {\n  state count: int = 0\n  output y: int\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    lf_set(y, self->count++);\n  =}\n}\n
    \n
    target Cpp\nreactor Count {\n  state count: int(0)\n  output y: int\n  timer t(0, 100 ms)\n  reaction(t) -> y {=\n    y.set(count++);\n  =}\n}\n
    \n
    target Python\nreactor Count {\n  state count = 0\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(self.count)\n    self.count += 1\n  =}\n}\n
    \n
    target TypeScript\nreactor Count {\n  state count: number = 0\n  output y: number\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y = count++\n  =}\n}\n
    \n
    target Rust\nreactor Count {\n  state count: u32 = 0\n  output y: u32\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    ctx.set(y, self.count);\n    self.count += 1;\n  =}\n}\n
    \n

    $end(Count)$

    \n

    This reactor has an integer state variable named count, and each time its reaction is invoked, it outputs the value of that state variable and increments it. The reaction is triggered by a $timer$, discussed in the next section.

    \n

    Reset State Variables

    \n
    \n

    The $reset$ keyword is not supported in $target-language$ because modal reactors are not supported.

    \n
    \n
    \n

    A state variable declaration may be qualified with a $reset$ keyword as follows:

    \n
      reset state <name>:<type> = <value>\n
    \n
      reset state <name> = <value>\n
    \n

    When this is done, if the state variable or the reactor is within a mode of a modal reactor, then when the mode is entered via a reset transition, the state variable will be reset to its initial value. For details, see the Modal Reactors section.

    \n
    ","headings":[{"value":"Parameter Declaration","depth":2},{"value":"State Declaration","depth":2},{"value":"Reset State Variables","depth":2}],"frontmatter":{"permalink":"/docs/handbook/parameters-and-state-variables","title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Inputs and Outputs","oneline":"Inputs, outputs, and reactions in Lingua Franca.","permalink":"/docs/handbook/inputs-and-outputs"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","permalink":"/docs/handbook/time-and-timers"}}}},"pageContext":{"id":"1-parameters-and-state-variables","slug":"/docs/handbook/parameters-and-state-variables","repoPath":"/packages/documentation/copy/en/topics/Parameters and State Variables.md","previousID":"dcdc6b32-76b0-570a-a6f8-23bb570863c7","nextID":"bba867ed-95b9-5017-b4f1-350e621e99da","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/preambles/page-data.json b/page-data/docs/handbook/preambles/page-data.json index bdd1af72c..8f45bc94b 100644 --- a/page-data/docs/handbook/preambles/page-data.json +++ b/page-data/docs/handbook/preambles/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/preambles","result":{"data":{"markdownRemark":{"id":"7f5f499e-179a-51f4-a401-ed9dbd103eb9","excerpt":"$page-showing-target$ Preamble Reactions may contain arbitrary target-language code, but often it is convenient for that code to invoke external libraries or to…","html":"

    $page-showing-target$

    \n

    Preamble

    \n

    Reactions may contain arbitrary target-language code, but often it is convenient for that code to invoke external libraries or to share procedure definitions. For either purpose, a reactor may include a $preamble$ section.

    \n
    \n

    For example, the following reactor uses the math C library for its trigonometric functions:

    \n
    main reactor {\n  preamble {=\n    #include <math.h>\n  =}\n  reaction(startup) {=\n    printf("The cosine of 1 is %f.\\n", cos(1));\n  =}\n}\n
    \n

    This will print:

    \n
    The cosine of 1 is 0.540302.
    \n

    By putting the #include in the $preamble$, the library becomes available in all reactions of this reactor.\nIf you wish to have the library available in all reactors in the same file, you can provide the $preamble$ outside the reactor, as shown here:

    \n
    preamble {=\n  #include <math.h>\n=}\nreactor Cos {\n  reaction(startup) {=\n    printf("The cosine of 1 is %f.\\n", cos(1));\n  =}\n}\nreactor Sin {\n  reaction(startup) {=\n    printf("The sine of 1 is %f.\\n", sin(1));\n  =}\n}\nmain reactor {\n  c = new Cos()\n  s = new Sin()\n}\n
    \n

    You can also use the $preamble$ to define functions that are shared across reactions within a reactor, as in this example:

    \n
    main reactor {\n  preamble {=\n    int add_42(int i) {\n      return i + 42;\n    }\n  =}\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\n
    \n

    Not surprisingly, this will print:

    \n
    42 plus 42 is 84.\n42 plus 1 is 43.
    \n

    (The order in which these are printed is arbitrary because the reactions can execute in parallel.)

    \n

    To share a function across reactors, however, is a bit trickers.\nA $preamble$ that is put outside the $reactor$ definition can only contain\ndeclarations not definitions of functions or variables.\nThe following code, for example will fail to compile:

    \n
    preamble {=\n  int add_42(int i) {\n    return i + 42;\n  }\n=}\nreactor Add_42 {\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n}\nreactor Add_1 {\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\nmain reactor {\n  a = new Add_42()\n  b = new Add_1()\n}\n
    \n

    The compiler will issue a duplicate symbol error because the function definition gets repeated in the separate C files generated for the two reactor classes, Add_42 and Add_1. When the compiled C code gets linked, the linker will find two definitions for the function add_42.

    \n

    To correct this compile error, the file-level preamble should contain only a declaration, not a definition, as here:

    \n
    preamble {=\n  int add_42(int i);\n=}\nreactor Add_42 {\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n}\nreactor Add_1 {\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\nmain reactor {\n  preamble {=\n    int add_42(int i) {\n      return i + 42;\n    }\n  =}\n  a = new Add_42()\n  b = new Add_1()\n}\n
    \n

    The function definition here is put into the main reactor, but it can be put in any reactor defined in the file.

    \n

    Most header files contain only declarations, and hence can be safely included\nusing #include in a file-level $preamble$. If you wish to use a header file that includes both declarations and definitions, then you will need to include it within each reactor that uses it.

    \n

    If you wish to share variables across reactors, similar constraints apply.\nNote that sharing variables across reactors is strongly discouraged because it can undermine the determinacy of Lingua Franca, and you may have to implement mutual-exclusion locks to access such variables. But it is occassionaly justfiable, as in the following example:

    \n
    preamble {=\n  extern const char shared_string[];\n=}\nreactor A {\n  reaction(startup) {=\n    printf("Reactor A says %s.\\n", shared_string);\n  =}\n}\nreactor B {\n  reaction(startup) {=\n    printf("Reactor B says %s.\\n", shared_string);\n  =}\n}\nmain reactor {\n  preamble {=\n    const char shared_string[] = "Hello";\n  =}\n  a = new A()\n  b = new B()\n}\n
    \n

    Notice the use of the extern keyword in C, which is required because the definition of the shared_string variable will be in a separate (code-generated) C file, the one for main, not the ones for A and B.

    \n

    One subtlety is that if you define symbols that you will use in $input$, $output$, or $state$ declarations, then the symbols must be defined in a file-level $preamble$.\nSpecifically, the following code will fail to compile:

    \n
    main reactor {\n  preamble {=\n    typedef int foo;\n  =}\n  state x:foo = 0\n  reaction(startup) {=\n    lf_print("State is %d", self->x);\n  =}\n}\n
    \n

    The compiler will issue an unknown type name error. To correct this, just move the declaration to a file-level $preamble$:

    \n
    preamble {=\n  typedef int foo;\n=}\nmain reactor {\n  state x:foo = 0\n  reaction(startup) {=\n    lf_print("State is %d", self->x);\n  =}\n}\n
    \n
    \n
    \n

    For example, the following reactor uses the charconv header from the c++ standard library to convert a string to an integer:

    \n
    target Cpp;\n\nmain reactor {\n  private preamble {=\n    #include <charconv>\n    #include <string>\n  =}\n\n  timer t;\n  reaction(t) {=\n    std::string raw = "42";\n    std::size_t number;\n\n    auto result = std::from_chars(raw.data(), raw.data() + raw.size(), number);\n    if (result.ec == std::errc::invalid_argument) {\n      std::cerr << "Could not convert.";\n    } else {\n      std::cout << "Converted string: " << raw << " to integer: " << number << std::endl;\n    }\n  =}\n}\n
    \n

    This will print:

    \n
    [INFO]  Starting the execution\nConverted string: 42 to integer: 42\n[INFO]  Terminating the execution
    \n

    By putting the #include in the preamble, the library becomes available in all reactions of this reactor. Note the private qualifier before the preamble keyword.\nThis ensures that the preamble is only visible to the reactions defined in this reactor and not to any other reactors. In contrast,\nthe public qualifier ensures that the preamble is also visible to other reactors in files that import the reactor defining the public preamble.

    \n
    reactor Preamble {\n  public preamble {=\n    struct MyStruct {\n      int foo;\n      std::string bar;\n    };\n  =}\n\n  private preamble {=\n    auto add_42(int i) noexcept -> int {\n      return i + 42;\n    }\n  =}\n\n  logical action a:MyStruct;\n\n  reaction(startup) {=\n    a.schedule({add_42(42), "baz"});\n  =}\n\n  reaction(a) {=\n    auto value = *a.get();\n    std::cout << "Received " << value.foo << " and '" << value.bar << "'\\n";\n  =}\n}\n
    \n

    It defines both a public and a private preamble. The public preamble defines the type MyStruct. This type definition will be visible to all elements of the\nPreamble reactor as well as to all reactors defined in files that import Preamble. The private preamble defines the function add_42(int i).\nThis function will only be usable to reactions within the Preamble reactor.

    \n

    You can think of public and private preambles as the equivalent of header files and source files in C++. In fact, the public preamble will be translated to a\nheader file and the private preamble to a source file. As a rule of thumb, all types that are used in port or action definitions as well as in state variables\nor parameters should be defined in a public preamble. Also, declarations of functions to be shared across reactors should be placed in the public preamble.\nEverything else, like function definitions or types that are used only within reactions, should be placed in a private preamble.

    \n

    Note that preambles can also be specified on the file level. These file level preambles are visible to all reactors within the file.\nAn example of this can be found in PreambleFile.lf.

    \n

    Admittedly, the precise interactions of preambles and imports can become confusing. The preamble mechanism will likely be refined in future revisions.

    \n

    Note that functions defined in the preamble cannot access members such as state variables of the reactor unless they are explicitly passed as arguments.\nIf access to the inner state of a reactor is required, methods present a viable and easy to use alternative.

    \n
    \n
    \n

    For example, the following reactor uses the platform module to print the platform information and a defined method to add 42 to an integer:

    \n
    main reactor Preamble {\n\tpreamble {=\n\t\timport platform\n\t\tdef add_42(self, i):\n\t\t\treturn i + 42\n\t=}\n\ttimer t;\n\treaction(t) {=\n\t\ts = "42"\n\t\ti = int(s)\n\t\tprint("Converted string {:s} to int {:d}.".format(s, i))\n\t\tprint("42 plus 42 is ", self.add_42(42))\n\t\tprint("Your platform is ", self.platform.system())\n\t=}\n}\n
    \n

    On a Linux machine, this will print:

    \n
    Converted string 42 to int 42.\n42 plus 42 is 84\nYour platform is Linux
    \n

    By putting import in the $preamble$, the module becomes available in all reactions of this reactor using the self modifier.

    \n

    Note: Preambles will be put in the generated Python class for the given reactor, and thus is part of the instance of the reactor. This means that anything you put in the preamble will be specific to a particular reactor instance and cannot be used to share information between different instantiations of the reactor (this is a feature, not a bug, because it helps ensure determinacy). For more information about implementation details of the Python target, see Implementation Details.

    \n

    Alternatively, you can define a $preamble$ outside any reactor definition. Such a $preamble$ can be used for functions such as import or to define a global function. The following example shows importing the hello module:

    \n
    target Python {\n  files: include/hello.py\n};\npreamble {=\n  import hello\n=}\n
    \n

    Notice the usage of the files target property to move the hello.py module located in the include folder of the test directory into the working directory (located in src-gen/NAME).

    \n

    For another example, the following program uses the built-in Python input() function to get typed input from the user:

    \n
    target Python\nmain reactor {\n  preamble {=\n    import threading\n    def external(self, a):\n      while (True):\n        from_user = input() # Blocking\n        a.schedule(0, from_user)\n  =}\n  state thread\n  physical action a\n  timer t(2 secs, 2 secs)\n\n  reaction(startup) -> a {=\n    self.thread = self.threading.Thread(target=self.external, args=(a,))\n    self.thread.start()\n    print("Type something.")\n  =}\n\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"A time {elapsed_time} nsec after start, received: ", a.value)\n  =}\n\n  reaction(t) {=\n    print("Waiting ...")\n  =}\n}\n
    \n

    Within the $preamble$, we specify to import the threading Python module and define a function that will be started in a separate thread in the reaction to $startup$. The thread function named external blocks when input() is called until the user types something and hits the return or enter key. Usually, you do not want a Lingua Franca program to block waiting for input. In the above reactor, a $timer$ is used to repeatedly trigger a reaction that reminds the user that it is waiting for input.

    \n
    \n
    \n

    For example, the following reactor uses Node’s built-in path module to extract the base name from a path:

    \n
    target TypeScript;\nmain reactor Preamble {\n  preamble {=\n    import * as path from 'path';\n  =}\n  reaction (startup) {=\n    var filename = path.basename('/Users/Refsnes/demo_path.js');\n    console.log(filename);\n  =}\n}\n
    \n

    This will print:

    \n
    demo_path.js
    \n

    By putting the import in the preamble, the library becomes available in all reactions of this reactor. Oddly, it also becomes available in all subsequently defined reactors in the same file. It’s a bit more complicated to set up Node.js modules from npm that aren’t built-in, but the reaction code to import them is the same as what you see here.

    \n

    You can also use the preamble to define functions that are shared across reactions and reactors:

    \n
    main reactor Preamble {\n  preamble {=\n    function add42( i:number) {\n      return i + 42;\n    }\n  =}\n  timer t;\n  reaction(t) {=\n    let s = "42";\n    let radix = 10;\n    let i = parseInt(s, radix);\n    console.log("Converted string " + s + " to number " + i);\n    console.log("42 plus 42 is " + add42(42));\n  =}\n}\n
    \n

    Not surprisingly, this will print:

    \n
    Converted string 42 to number 42\n42 plus 42 is 84
    \n

    Using Node Modules

    \n

    Installing Node.js modules for TypeScript reactors with npm is essentially the same as installing modules for an ordinary Node.js program. First, write a Lingua Franca program (Foo.lf) and compile it. It may not type check if if you’re importing modules in the preamble and you haven’t installed the modules yet, but compiling your program will cause the TypeScript code generator to produce a project for your program. There should now be a package.json file in the same directory as your .lf file. Open a terminal and navigate to that directory. You can use the standard npm install command to install modules for your TypeScript reactors.

    \n

    The important takeaway here is with the package.json file and the compiled JavaScript in the Foo/dist/ directory, you have a standard Node.js program that executes as such. You can modify and debug it just as you would a Node.js program.

    \n
    \n
    \n

    FIXME: Add $preamble$ example.

    \n
    ","headings":[{"value":"Preamble","depth":2},{"value":"Using Node Modules","depth":3}],"frontmatter":{"permalink":"/docs/handbook/preambles","title":"Preambles","oneline":"Defining preambles in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","permalink":"/docs/handbook/generics"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","permalink":"/docs/handbook/distributed-execution"}}}},"pageContext":{"id":"1-preambles","slug":"/docs/handbook/preambles","repoPath":"/packages/documentation/copy/en/topics/Preambles.md","previousID":"2a0b9619-72b6-5ee7-8a38-83ff8d48005a","nextID":"79d9c9b2-eee4-5652-9541-c483de60119e","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/preambles","result":{"data":{"markdownRemark":{"id":"7f5f499e-179a-51f4-a401-ed9dbd103eb9","excerpt":"$page-showing-target$ Preamble Reactions may contain arbitrary target-language code, but often it is convenient for that code to invoke external libraries or to…","html":"

    $page-showing-target$

    \n

    Preamble

    \n

    Reactions may contain arbitrary target-language code, but often it is convenient for that code to invoke external libraries or to share procedure definitions. For either purpose, a reactor may include a $preamble$ section.

    \n
    \n

    For example, the following reactor uses the math C library for its trigonometric functions:

    \n
    main reactor {\n  preamble {=\n    #include <math.h>\n  =}\n  reaction(startup) {=\n    printf("The cosine of 1 is %f.\\n", cos(1));\n  =}\n}\n
    \n

    This will print:

    \n
    The cosine of 1 is 0.540302.
    \n

    By putting the #include in the $preamble$, the library becomes available in all reactions of this reactor.\nIf you wish to have the library available in all reactors in the same file, you can provide the $preamble$ outside the reactor, as shown here:

    \n
    preamble {=\n  #include <math.h>\n=}\nreactor Cos {\n  reaction(startup) {=\n    printf("The cosine of 1 is %f.\\n", cos(1));\n  =}\n}\nreactor Sin {\n  reaction(startup) {=\n    printf("The sine of 1 is %f.\\n", sin(1));\n  =}\n}\nmain reactor {\n  c = new Cos()\n  s = new Sin()\n}\n
    \n

    You can also use the $preamble$ to define functions that are shared across reactions within a reactor, as in this example:

    \n
    main reactor {\n  preamble {=\n    int add_42(int i) {\n      return i + 42;\n    }\n  =}\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\n
    \n

    Not surprisingly, this will print:

    \n
    42 plus 42 is 84.\n42 plus 1 is 43.
    \n

    (The order in which these are printed is arbitrary because the reactions can execute in parallel.)

    \n

    To share a function across reactors, however, is a bit trickers.\nA $preamble$ that is put outside the $reactor$ definition can only contain\ndeclarations not definitions of functions or variables.\nThe following code, for example will fail to compile:

    \n
    preamble {=\n  int add_42(int i) {\n    return i + 42;\n  }\n=}\nreactor Add_42 {\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n}\nreactor Add_1 {\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\nmain reactor {\n  a = new Add_42()\n  b = new Add_1()\n}\n
    \n

    The compiler will issue a duplicate symbol error because the function definition gets repeated in the separate C files generated for the two reactor classes, Add_42 and Add_1. When the compiled C code gets linked, the linker will find two definitions for the function add_42.

    \n

    To correct this compile error, the file-level preamble should contain only a declaration, not a definition, as here:

    \n
    preamble {=\n  int add_42(int i);\n=}\nreactor Add_42 {\n  reaction(startup) {=\n    printf("42 plus 42 is %d.\\n", add_42(42));\n  =}\n}\nreactor Add_1 {\n  reaction(startup) {=\n    printf("42 plus 1 is %d.\\n", add_42(1));\n  =}\n}\nmain reactor {\n  preamble {=\n    int add_42(int i) {\n      return i + 42;\n    }\n  =}\n  a = new Add_42()\n  b = new Add_1()\n}\n
    \n

    The function definition here is put into the main reactor, but it can be put in any reactor defined in the file.

    \n

    Most header files contain only declarations, and hence can be safely included\nusing #include in a file-level $preamble$. If you wish to use a header file that includes both declarations and definitions, then you will need to include it within each reactor that uses it.

    \n

    If you wish to share variables across reactors, similar constraints apply.\nNote that sharing variables across reactors is strongly discouraged because it can undermine the determinacy of Lingua Franca, and you may have to implement mutual-exclusion locks to access such variables. But it is occassionaly justfiable, as in the following example:

    \n
    preamble {=\n  extern const char shared_string[];\n=}\nreactor A {\n  reaction(startup) {=\n    printf("Reactor A says %s.\\n", shared_string);\n  =}\n}\nreactor B {\n  reaction(startup) {=\n    printf("Reactor B says %s.\\n", shared_string);\n  =}\n}\nmain reactor {\n  preamble {=\n    const char shared_string[] = "Hello";\n  =}\n  a = new A()\n  b = new B()\n}\n
    \n

    Notice the use of the extern keyword in C, which is required because the definition of the shared_string variable will be in a separate (code-generated) C file, the one for main, not the ones for A and B.

    \n

    One subtlety is that if you define symbols that you will use in $input$, $output$, or $state$ declarations, then the symbols must be defined in a file-level $preamble$.\nSpecifically, the following code will fail to compile:

    \n
    main reactor {\n  preamble {=\n    typedef int foo;\n  =}\n  state x:foo = 0\n  reaction(startup) {=\n    lf_print("State is %d", self->x);\n  =}\n}\n
    \n

    The compiler will issue an unknown type name error. To correct this, just move the declaration to a file-level $preamble$:

    \n
    preamble {=\n  typedef int foo;\n=}\nmain reactor {\n  state x:foo = 0\n  reaction(startup) {=\n    lf_print("State is %d", self->x);\n  =}\n}\n
    \n
    \n
    \n

    For example, the following reactor uses the charconv header from the c++ standard library to convert a string to an integer:

    \n
    target Cpp;\n\nmain reactor {\n  private preamble {=\n    #include <charconv>\n    #include <string>\n  =}\n\n  timer t;\n  reaction(t) {=\n    std::string raw = "42";\n    std::size_t number;\n\n    auto result = std::from_chars(raw.data(), raw.data() + raw.size(), number);\n    if (result.ec == std::errc::invalid_argument) {\n      std::cerr << "Could not convert.";\n    } else {\n      std::cout << "Converted string: " << raw << " to integer: " << number << std::endl;\n    }\n  =}\n}\n
    \n

    This will print:

    \n
    [INFO]  Starting the execution\nConverted string: 42 to integer: 42\n[INFO]  Terminating the execution
    \n

    By putting the #include in the preamble, the library becomes available in all reactions of this reactor. Note the private qualifier before the preamble keyword.\nThis ensures that the preamble is only visible to the reactions defined in this reactor and not to any other reactors. In contrast,\nthe public qualifier ensures that the preamble is also visible to other reactors in files that import the reactor defining the public preamble.

    \n
    reactor Preamble {\n  public preamble {=\n    struct MyStruct {\n      int foo;\n      std::string bar;\n    };\n  =}\n\n  private preamble {=\n    auto add_42(int i) noexcept -> int {\n      return i + 42;\n    }\n  =}\n\n  logical action a:MyStruct;\n\n  reaction(startup) {=\n    a.schedule({add_42(42), "baz"});\n  =}\n\n  reaction(a) {=\n    auto value = *a.get();\n    std::cout << "Received " << value.foo << " and '" << value.bar << "'\\n";\n  =}\n}\n
    \n

    It defines both a public and a private preamble. The public preamble defines the type MyStruct. This type definition will be visible to all elements of the\nPreamble reactor as well as to all reactors defined in files that import Preamble. The private preamble defines the function add_42(int i).\nThis function will only be usable to reactions within the Preamble reactor.

    \n

    You can think of public and private preambles as the equivalent of header files and source files in C++. In fact, the public preamble will be translated to a\nheader file and the private preamble to a source file. As a rule of thumb, all types that are used in port or action definitions as well as in state variables\nor parameters should be defined in a public preamble. Also, declarations of functions to be shared across reactors should be placed in the public preamble.\nEverything else, like function definitions or types that are used only within reactions, should be placed in a private preamble.

    \n

    Note that preambles can also be specified on the file level. These file level preambles are visible to all reactors within the file.\nAn example of this can be found in PreambleFile.lf.

    \n

    Admittedly, the precise interactions of preambles and imports can become confusing. The preamble mechanism will likely be refined in future revisions.

    \n

    Note that functions defined in the preamble cannot access members such as state variables of the reactor unless they are explicitly passed as arguments.\nIf access to the inner state of a reactor is required, methods present a viable and easy to use alternative.

    \n
    \n
    \n

    For example, the following reactor uses the platform module to print the platform information and a defined method to add 42 to an integer:

    \n
    main reactor Preamble {\n\tpreamble {=\n\t\timport platform\n\t\tdef add_42(self, i):\n\t\t\treturn i + 42\n\t=}\n\ttimer t;\n\treaction(t) {=\n\t\ts = "42"\n\t\ti = int(s)\n\t\tprint("Converted string {:s} to int {:d}.".format(s, i))\n\t\tprint("42 plus 42 is ", self.add_42(42))\n\t\tprint("Your platform is ", self.platform.system())\n\t=}\n}\n
    \n

    On a Linux machine, this will print:

    \n
    Converted string 42 to int 42.\n42 plus 42 is 84\nYour platform is Linux
    \n

    By putting import in the $preamble$, the module becomes available in all reactions of this reactor using the self modifier.

    \n

    Note: Preambles will be put in the generated Python class for the given reactor, and thus is part of the instance of the reactor. This means that anything you put in the preamble will be specific to a particular reactor instance and cannot be used to share information between different instantiations of the reactor (this is a feature, not a bug, because it helps ensure determinacy). For more information about implementation details of the Python target, see Implementation Details.

    \n

    Alternatively, you can define a $preamble$ outside any reactor definition. Such a $preamble$ can be used for functions such as import or to define a global function. The following example shows importing the hello module:

    \n
    target Python {\n  files: include/hello.py\n};\npreamble {=\n  import hello\n=}\n
    \n

    Notice the usage of the files target property to move the hello.py module located in the include folder of the test directory into the working directory (located in src-gen/NAME).

    \n

    For another example, the following program uses the built-in Python input() function to get typed input from the user:

    \n
    target Python\nmain reactor {\n  preamble {=\n    import threading\n    def external(self, a):\n      while (True):\n        from_user = input() # Blocking\n        a.schedule(0, from_user)\n  =}\n  state thread\n  physical action a\n  timer t(2 secs, 2 secs)\n\n  reaction(startup) -> a {=\n    self.thread = self.threading.Thread(target=self.external, args=(a,))\n    self.thread.start()\n    print("Type something.")\n  =}\n\n  reaction(a) {=\n    elapsed_time = lf.time.logical_elapsed()\n    print(f"A time {elapsed_time} nsec after start, received: ", a.value)\n  =}\n\n  reaction(t) {=\n    print("Waiting ...")\n  =}\n}\n
    \n

    Within the $preamble$, we specify to import the threading Python module and define a function that will be started in a separate thread in the reaction to $startup$. The thread function named external blocks when input() is called until the user types something and hits the return or enter key. Usually, you do not want a Lingua Franca program to block waiting for input. In the above reactor, a $timer$ is used to repeatedly trigger a reaction that reminds the user that it is waiting for input.

    \n
    \n
    \n

    For example, the following reactor uses Node’s built-in path module to extract the base name from a path:

    \n
    target TypeScript;\nmain reactor Preamble {\n  preamble {=\n    import * as path from 'path';\n  =}\n  reaction (startup) {=\n    var filename = path.basename('/Users/Refsnes/demo_path.js');\n    console.log(filename);\n  =}\n}\n
    \n

    This will print:

    \n
    demo_path.js
    \n

    By putting the import in the preamble, the library becomes available in all reactions of this reactor. Oddly, it also becomes available in all subsequently defined reactors in the same file. It’s a bit more complicated to set up Node.js modules from npm that aren’t built-in, but the reaction code to import them is the same as what you see here.

    \n

    You can also use the preamble to define functions that are shared across reactions and reactors:

    \n
    main reactor Preamble {\n  preamble {=\n    function add42( i:number) {\n      return i + 42;\n    }\n  =}\n  timer t;\n  reaction(t) {=\n    let s = "42";\n    let radix = 10;\n    let i = parseInt(s, radix);\n    console.log("Converted string " + s + " to number " + i);\n    console.log("42 plus 42 is " + add42(42));\n  =}\n}\n
    \n

    Not surprisingly, this will print:

    \n
    Converted string 42 to number 42\n42 plus 42 is 84
    \n

    Using Node Modules

    \n

    Installing Node.js modules for TypeScript reactors with npm is essentially the same as installing modules for an ordinary Node.js program. First, write a Lingua Franca program (Foo.lf) and compile it. It may not type check if if you’re importing modules in the preamble and you haven’t installed the modules yet, but compiling your program will cause the TypeScript code generator to produce a project for your program. There should now be a package.json file in the same directory as your .lf file. Open a terminal and navigate to that directory. You can use the standard npm install command to install modules for your TypeScript reactors.

    \n

    The important takeaway here is with the package.json file and the compiled JavaScript in the Foo/dist/ directory, you have a standard Node.js program that executes as such. You can modify and debug it just as you would a Node.js program.

    \n
    \n
    \n

    FIXME: Add $preamble$ example.

    \n
    ","headings":[{"value":"Preamble","depth":2},{"value":"Using Node Modules","depth":3}],"frontmatter":{"permalink":"/docs/handbook/preambles","title":"Preambles","oneline":"Defining preambles in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Generic Reactors","oneline":"Defining generic reactors in Lingua Franca.","permalink":"/docs/handbook/generics"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","permalink":"/docs/handbook/distributed-execution"}}}},"pageContext":{"id":"1-preambles","slug":"/docs/handbook/preambles","repoPath":"/packages/documentation/copy/en/topics/Preambles.md","previousID":"2a0b9619-72b6-5ee7-8a38-83ff8d48005a","nextID":"79d9c9b2-eee4-5652-9541-c483de60119e","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/proof-import/page-data.json b/page-data/docs/handbook/proof-import/page-data.json index e2813a44e..79e4b5000 100644 --- a/page-data/docs/handbook/proof-import/page-data.json +++ b/page-data/docs/handbook/proof-import/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/proof-import","result":{"data":{"markdownRemark":{"id":"4466937a-36f5-5fc8-a89d-98421824f135","excerpt":"This is a collection of thoughts on the design of a reliable package and import system that is ready for future applications. At this stage, this page mostly…","html":"

    This is a collection of thoughts on the design of a reliable package and import system that is ready for future applications. At this stage, this page mostly represents my personal view (Christian Menard). I will also focus on the C++ target here as this is the target I know best. The C target is not a good example for these considerations as there is a fundamental design issue with the C target. Since the code generator places all code in a single generated .c file and does things like #include reactor.c to avoid the need for Makefiles, it circumvents many of the issues that come with imports that I will outline here. It simply ignores file scopes and namespaces altogether.

    \n

    The status quo

    \n

    The current import system is lean and simple. Write import Bar.lf in Foo.lf and every reactor defined in Bar.lf will be visible in the file scope Foo.lf. Bar.lf is looked up simply by scanning the directory Foo.lf is placed in. This works well for the simple programs and tests we have right now, but does not scale. I identify the following problems:

    \n
      \n
    1. \n

      There is no notion of separate namespaces. Every reactor that Bar.lf defines becomes visible in Foo.lf. If both files define a Reactor Foo, there is a name clash and the import would be ill-formed. There should be a mechanism to distinguish the two definitions of Foo, such as using fully qualified names: Foo.Foo and Bar.Foo.

      \n
    2. \n
    3. \n

      There is no concept for importing files from a directory structure. It is unclear how Foo.lf could import my/lib/Bar.lf.

      \n
    4. \n
    5. \n

      There is no concept for packages or libraries that can be installed on the system. How could we import Reactors from a library that someone else provided?

      \n
    6. \n
    \n

    These are the more obvious issues that we have talked about. However, there are more subtle ones that we haven’t been discussed in depth (or at least not in the context of the import system design discussion). The open question is: What does importing a LF file actually mean? Obviously, an import should bring Reactors defined in another files into local scope. But what should happen with the other structures that are part of an LF file, namely target properties and preambles? That is not specified and our targets use a best practice approach. But this is far away from a good design that is scalable and future proof.

    \n

    A quick dive into the C++ code generator

    \n

    Before I discuss the problems with preambles and target properties, I would like to give you a quick overview of how the C++ code generator works. Consider the following LF program consisting of two files Foo.lf and Bar.lf:

    \n
    // Bar.lf\n\nreactor Bar {\n reaction(startup) {=\n   // do something bar like\n =}\n}
    \n
    // Foo.lf\n\nimport Bar.lf\n\nreactor Foo {\n bar = new Bar();\n reaction(startup) {=\n   // do something foo like\n =}\n}
    \n

    Now let us have a look on what the C++ code generator does. It will produce a file structure like this:

    \n
    CMakeLists.txt\nmain.cc\nBar/\n  Bar.cc\n  Bar.hh\nFoo/\n  Foo.cc\n  Foo.hh
    \n

    We can ignore CMakeLists.txt and main.cc for our discussion here. The former specifies how the whole program can be build and the latter contains the main() function and some code that is required to get the application up and running. For each processed <file>.lf file, the code generator creates a directory <file>. For each reactor <reactor> defined in <file>.lf, it will create <file>/<reactor>.cc and <file>/<reactor>.hh. The header file declares a class representing the reactor like this:

    \n
    // Bar/Bar.hh\n\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n\nclass Bar : public reactor::Reacor {\n private:\n  // default actions\n  reactor::StartupAction startup {"startup", this};\n  reactor::ShutdownAction shutdown {"shutdown", this};\n\n public:\n  /* ... */\n\n private:\n  // reaction bodies\n  r0_body();\n};
    \n

    The corresponding Bar/Bar.cc will look something like this:

    \n
    #include "Bar/Bar.hh"\n\n/* ... */\n\nBar::r0_body() {\n  // do something bar like\n}
    \n

    Similarly, Foo.hh and Foo.cc will be generated. However, since Foo.lf imports Bar.lf and instantiated the reactor Bar it must be made visible. This is done by an include directive in the generated code like so:

    \n
    // Foo/Foo.hh\n###\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n#include "Bar/Bar.hh"\n\nclass Foo : public reactor::Reacor {\n private:\n  // default actions\n  reactor::StartupAction startup;\n  reactor::ShutdownAction shutdown;\n\n  // reactor instances\n  Bar bar;\n public:\n  /* ... */\n\n private:\n  // reaction bodies\n  r0_body();\n};
    \n

    The problem with preambles

    \n

    The problems with preamble in the context of imports were already discussed in a related issue, but I would like to summarize the problem here. While the examples above worked nicely even with imports, things get messy as soon as we introduce a preamble. Let’s try this:

    \n
    // Bar.lf\n\nreactor Bar {\n preamble {=\n   struct bar_t {\n     int x;\n     std::string y;\n   };\n\n   bar_t bar_func {\n     return bar_t(42, "hello")\n   }\n =}\n output out:bar_t;\n reaction(startup) -> out {=\n   out.set(bar_fuc());\n =}\n}
    \n
    // Foo.lf\n\nimport Bar.lf\n\nreactor Foo\n bar = new Bar();\n reaction(bar.out) {=\n   auto& value = bar.out.get();\n   std::cout << "Received {" << value->x << ", " << value->y << "}\\n";\n =}\n}
    \n

    This would be expected to print Received {32, hello}. However, before we can even compile this program, we need to talk about what should happen with the preamble during code generation and how the import affects it. So where should the preamble go? The first thing that comes to mind, is to embed it in the header file Bar.hh something like this:

    \n
    // Bar/Bar.hh\n\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n\n// preamble\nstruct bar_t {\n  int x;\n  std::string y;\n};\n\nbar_t bar_func {\n  return bar_t(42, "hello")\n}\n\nclass Bar : public reactor::Reacor {\n /* ... */\n};
    \n

    If we embed the preamble like this and compile the program ,then the compiler is actually happy and processes all *.cc files without any complaints. But, there is a huge problem while linking the binary. The linker sees multiple definitions of bar_func and has no idea which one to use. Why is that? Well, the definition of bar_func is contained in a header file. This should never be done in C/C++! Since includes translate to a plain-text replacement by the preprocessor, Bar.cc will contain the full definition of bar_func. As Foo.cc imports Foo.hh which imports Bar.hh, also Foo.cc will contain the full definition. And since main.cc also has to include Foo.hh, main.cc will also contain the full definition of bar_func. So we have multiple definitions of the same function and the linker rightfully reports this as an error.

    \n

    So what should we do? We could place the preamble in Bar.cc instead. This ensures that only Bar.cc sees the definition of bar_func. But then the compiler complains. Neither Bar.hh nor Foo.hh see type declaration of bar_t. Note that there is a dependency of Foo.lf on the preamble in Bar.lf. The import system should somehow take care of this dependency! Also note that this has not appeared as a problem in C as the code generator places everything in the same compilation unit. Foo will see the preamble of Bar as long as Foo is generated before Bar.

    \n

    But how to solve it for C++ where the code is split in multiple compilation units (which really should be happening in C as well)? What we do at the moment is annotating the preamble with private and public keywords. This helps to split the preamble up and decide what to place in the header and what to place in the source file. For instance:

    \n
    // Bar.lf\n\nreactor Bar {\n public preamble {=\n   struct bar_t {\n     int x;\n     std::string y;\n   };\n =}\n private preamble {=\n   bar_t bar_func {\n     return bar_t(42, "hello")\n   }\n =}\n output out:bar_t;\n reaction(startup) -> out {=\n   out.set(bar_fuc());\n =}\n}
    \n

    This makes the type bar_t visible as part of the public interface of Bar. Both the code generated for Bar and the code generated for Foo will see the definition of bar_t. This is realized by placing the public preamble in Bar.hh The function bar_func is part of Bar’s private interface. It is only visible with the reactor definition of Bar and is not propagated by an import. This is realized by simply placing the private preamble in Bar.cc. This makes the compiler finally happy and when get an executable program private and public preambles provide a mechanism to define what is propagated on an import and what is not. I think this is an important distinction even in languages other than C/C++ that do not have this weird separation of source and header file.

    \n

    I am sorry for this lengthy diversion into things that happened in the past where we actually want to talk about how things should work in the future. However, understanding this issue is important and when talking about other solutions we should not forget that it exists.

    \n

    The problem with target properties

    \n

    It is also not well-defined what should happen with target properties when importing a .lf file. Apparently the common practice is simply ignoring the existence of other target declarations and only considering the target declaration of the .lf that contains the main reactor. I think this works reasonably well for our small programs. But it will cause problems when either programs become larger or we introduce new target properties where it is unclear what piece of code they reference. Let us have a look at the existing target properties for C++. How should those different properties be handled on an import? Which scope do they actually apply to? We haven’t really talked about this.

    \n

    fast, keepalive, threads and timeout are easy. They apply to the main reactor. Since we do not import main reactors from other files, it is clear that we really want to use the properties defined in the main compilation unit. So our current strategy works in this case. Although there are still some subtleties. For instance, if a library file defines keepalive=true and fast=false because it uses physical actions, should any file importing this library file be allowed to override these properties. Probably not, because it doesn’t make sense if physical actions are involved. But a careless user of the library might not be aware of that. So maybe it isn’t that clear after all.

    \n

    build-type, cmake-include, compile, logging and no-runtime-validation influence how the application is build. They are used for generating the CMakeLists.txt file. So their is quite clear: they apply to the whole compilation of the given application. Again it is a simple solution to only consider the target properties of the file containing the main reactor since this can be considered the file that ‘drives’ the compilation. But what if an imported .lf relies on an external library and uses the cmake-include property to tell CMake to look this library up, make the library header files visible and link our generated code to that library (fortunately this can be done with 2 lines in CMake). Should this target property really be ignored by our import? Probably not, because it will lead to compile errors if the author of the main .lf file does not configure cmake-include properly. So there should be some kind of merging mechanism for cmake-include. Should this be done for the other properties as well? I am not sure and I actually don’t know how the merging would work.

    \n

    So this raises a lot of questions that we currently have no answer to. I believe we need to find answers for these questions in order to create a well working import and package system. This gets only more complicated when we add more properties such as the proposed files directive. We should really consider what properties actually apply to and if they influence the way imports work.

    \n

    The work in progress

    \n

    To be continued… I want to describe here what is happening on the new_import and the (potential) problems this brings.

    \n

    Possible solutions

    \n

    To be continued… I would like to show a few possible solutions that have come to mind and that we discussed already.

    \n

    Concrete proposal

    \n

    With the risk of overlooking some of the issues discussed above, I’d like to outline a concrete proposal. To me, at least, it is easier to reason about these issues in a context with a few more constraints. Hopefully, this can serve as a starting point that we can tweak/adjust as needed. Note: this proposal borrows from the previous proposal written by Soroush. Based on my experience with Xtext, I have confidence that what is described below is feasible to implement.

    \n

    Import/export

    \n
      \n
    1. One LF file can contain multiple reactor definitions.
    2. \n
    3. There can be at most one main reactor per file.
    4. \n
    5. Any reactor class defined outside of the current file has to be imported explicitly.
    6. \n
    7. The visibility of a reactor class can be limited using a modifier in the class definition.
    8. \n
    \n
      \n
    • Should the default visibility be public or private? I have no strong preference either way.
    • \n
    \n
      \n
    1. An import statement must specify which reactor classes to import. This is necessary because if we populate a global scope using the uriImport mechanism, the local scope provider needs to know which definition to link to if there happen to exist multiple among the set of included files. We could potentially relax this constraint and only report the situation where we know for a fact that there is ambiguity and needs to be resolved by making the imports explicit. We could also deprecate the use of unqualified imports (the original syntax), therefore allow it but warn that it might not work as expected.
    2. \n
    3. An LF file in an import statement is specified by a path relative to the directory of the file in which the import statement occurs or relative to a series of directories in a wider search path.
    4. \n
    \n
      \n
    • Eclipse uses .project files to identify the root of a project; we can look for that.
    • \n
    • We can look for our own kind of manifest files as well. These can list additional locations to search. This is compatible with the idea of developing a package system. I personally like this approach better than using an environment variable.
    • \n
    \n
      \n
    1. I personally find fully qualified names excess generality and have never felt compelled to use them in Java. IMO, they lead to code that’s difficult to read and a pain to format. To keep things simple, I suggest we don’t support them. Instead, we should provide a mechanism for renaming imported reactor classes to avoid naming conflicts.
    2. \n
    3. Open question: do we want scope modifiers for imports? It seems that extra import statements could be used to increase visibility, so it might not be needed.
    4. \n
    \n

    Syntax

    \n
    Import := 'import' <ID> <Rename>? (',' <ID> <Rename>?)* 'from' <PATH>\n\nRename := 'as' <ID>
    \n

    Note: This syntax could be extended to support packages in addition to paths. But it doesn’t make much sense to have this until we have a package manager and package registry.

    \n

    Current state of the discussion: one unifying syntax vs. different syntax for references to files and packages.

    \n

    Preambles

    \n

    A preamble allows for the inclusion of verbatim target code that may be necessary for reactors to function. Currently, there are two scopes in which preambles can appear: (1) file scope and (2) reactor class scope. Moreover, there exist visibility modifiers to label preambles private or public. A public preamble is intended to contain code that is necessary for the use of a reactor that is in scope. A private preamble is intended to contain code that is necessary for the implementation of a reactor that is in scope. Only the C++ code generator can currently effectively separate these LF scope levels. It achieves this by putting each reactor class definition in its own file. LF file scope preambles are currently not supported by the C target, but this appears to be unintentional and would be easy to fix. Reactor class scope preambles are supported by the C target, but there is no isolation of scope; the preamble of one reactor is visible to the one defined after it. To fix this, I see two options: (1) follow the same approach as CppGenerator and output separate files, which also means that a Makefile has to be generated in order to compile the result, or (2) leverage block scope within a single file, but this will become complicated and make the generated C code even less humanly readable.

    \n

    We could put aside the problem of name clashes due to the absence of scope isolation in generated C code and fix this later. For the time being, the problem can be circumvented using .h files.

    \n

    Target Properties

    \n
      \n
    1. Each file declares a target.
    2. \n
    3. All code in all reactors in the same file must agree with the specified target.
    4. \n
    5. Additional target properties may be specified.
    6. \n
    7. Target properties are not inherited through imports.
    8. \n
    9. Any property that needs to be inherited through an import (such as the requirement to link against the pthread library) must be specified as a build dependency instead.
    10. \n
    \n

    Build Dependencies

    \n
      \n
    1. It must be possible to specify build dependencies, such as files, sources, and protobufs.
    2. \n
    3. We could either allow these definitions to go directly in the .lf file, or we could decide to specify them in a package description (i.e., separate file). We could potentially allow both.
    4. \n
    5. Build dependencies are inherited through imports (or from package descriptions), and they are never shadowed, always joined.
    6. \n
    \n

    Unique Reactor Names

    \n

    The new import system as described above ensures that reactor names within a single .lf file are unique. In case reactors with the same name are imported from different .lf files, the renaming mechanism needs to be used in order to resolve the name conflict. The same applies if the given .lf file defines some reactors and tries to import other reactors with the same name. For instance, consider the LF file in Example 1 below. In the scope of this file, three reactor declarations are visible: Foo, Bar and Baz, although the actual reactors have the same name Foo.

    \n

    Examples

    \n

    Throughout this section, I will be using two LF example programs. Since the markdown syntax does not provide a simple way to label and refer to code listings, I figure its easiest to place them here in a central place and refer to them later by the heading

    \n

    Example 1

    \n
    \\\\ Foo.lf\nimport Foo as Bar from "Bar.lf"\nimport Foo as Baz from "Baz.lf"\n\nreactor Foo {\n  \\\\ ...\n}
    \n

    Example 2

    \n
    \\\\ Baz.lf\nreactor Foo {\n  \\\\ ...\n}
    \n
    \\\\ Bar.lf\nimport Foo from "Baz.lf"\n\nreactor Foo {\n  foo = new Foo()\n  \\\\ ...\n}
    \n
    \\\\ Foo.lf\nimport Bar from "Bar.lf"\nmain reactor Foo {\n  bar = new Bar()\n  \\\\ ...\n}
    \n

    Unique Reactor Names in Target Code

    \n

    While the mechanism above effectively ensures uniqueness in a single LF file, this uniqueness is surprisingly hard to ensure in generated target code. C has an obvious problem here as it places all generated code in a single file. While the name conflict in the above code can be solved by generating code for three reactors named Bar, Baz and Foo, it breaks as soon as another file of the same LF program uses import Foo from \"Bar.lf\". Then there would be two definitions of the reactor Foo that cannot be resolved.

    \n

    Now you would probably expect that splitting the generated code into multiple files solves the issue, but unfortunately this is not true. If anything, it makes the problem more subtle. The C++ code generated from Example 1 would likely look something like this:

    \n
    // Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n#include "Baz.hh"\n\n// do the renaming\nusing Bar = Foo;\nusing Baz = Foo;\n\nclass Foo : public reactor::Reactor {\n};
    \n

    This will cause a compile error as there are multiple definitions of Foo. While renaming is possible in C++ with the using keyword (typedef works as well), the thing being renamed needs to be already visible in the scope. So there are multiple definitions of Foo as all the files Bar.hh, Baz.hh and Foo.hh define this class. We need a mechanism to distinguish the different definitions of Foo.

    \n

    There is even another issue that stems from the fact that the semantics of imports in LF are different from the include semantics of C++. Consider the code in Example 2, which is valid LF code. Although Bar.lf imports Foo and Foo.lf imports from Bar.lf, the definition of Foo in Baz.lf is not visible in Foo.lf. This ‘hiding’, however, does not easily propagate to the generated code. In C, there will be an error because both definitions of Foo are placed in the same file. In C++, the different definitions of Foo are placed in different files, but there will still be an error. The generated C++ code would look something like this:

    \n
    \\\\ Baz.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\nclass Foo : public reactor::Reactor {\n  // ...\n};
    \n
    \\\\ Bar.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Baz.hh"\n\nclass Bar : public reactor::Reactor {\n  Foo foo;\n  // ...\n};
    \n
    \\\\ Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n\nclass Foo : public reactor::Reactor {\n  Bar bar;\n  // ...\n};
    \n

    This will produce an error due to multiple definitions of Foo being visible in Foo.hh. The problem is that any include in Bar.hh becomes also visible in Foo.hh. So there is a name clash due to the way the C++ compiler processes included and that is hard to work around.

    \n

    Possible Solutions

    \n

    In conclusion from the above section, I can say that translating the file based scoping of reactor names that we have in LF to generated target code is not trivial. Any sensible solution will need to establish a mechanism to ensure that any two distinct reactors in LF are also distinct in target code.

    \n

    Namespaces

    \n

    We could introduce some form of a namespace mechanism that allows us to derive fully-qualified names of reactors. This is the preferred solution for me (Christian). Note that by ‘namespace’ I mean any logical organization of reactors in named groups and not the precise concept of C++ namespaces. In other languages those logical groups are also referred to as modules or packages. Also note that it is only important to be able to assign a fully-qualified name to a reactor, it does not necessarily require that we refer to reactors by their fully-qualified name in LF code.

    \n

    File based namespaces

    \n

    In my view, the easiest way to introduce namespaces in LF would be to leverage file system structure. Everything contained in Foo.lf would automatically be in the namespace Foo. So the FQN of a reactor Foo defined in Foo.lf would be Foo.Foo (or Foo::Foo, or some other delimiter). This would solve the name clashes in both of our examples. For Example 1, the generated code could look like this:

    \n
    // Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n#include "Baz.hh"\nnamespace Foo {\n\n// do the renaming\nusing Bar = Bar::Foo;\nusing Baz = Baz::Foo;\n\nclass Foo : public reactor::Reactor {\n};\n}
    \n

    For Example 2, the generated code could look like this:

    \n
    \\\\ Baz.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\nnamespace Baz {\nclass Foo : public reactor::Reactor {\n  // ...\n};\n}
    \n
    \\\\ Bar.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Baz.hh"\n\nnamespace Bar {\nusing Foo = Baz::Foo; // bring Foo in scope\n\nclass Bar : public reactor::Reactor {\n  Foo foo;\n  // ...\n};\n}
    \n
    \\\\ Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n\nnamespace Foo {\nusing Bar = Bar::Bar; // bring Bar in scope\n\nclass Foo : public reactor::Reactor {\n  Bar bar;\n  // ...\n};\n}
    \n

    While this appears to be a promising solution, it is not sufficient to only consider the name of an *.lf file to derive the namespace\nThere could be two files Foo.lf in different directories that both define the reactor Foo. Thus, we also need to consider the directory structure and introduce hierarchical namespaces. Consider this directory tree:

    \n
    foo/\n  bar/\n    foo.lf  # defines reactor Foo\n  baz/\n    foo.lf  # defines reactor Foo
    \n

    In this example, the two Foo reactors would have the fully qualified names foo.bar.foo.Foo and foo.baz.foo.Foo.\nIn order for this concept to work, we need the notion of a top-level namespace or directory. Naturally, this would be the package. Therefore, this namespace approach would also require a simple mechanism to define a package. For now this could be rudimentary. Simply placing an empty lf.yaml in the foo/ directory in the above example would be sufficient. In addition to the notion of packages, we would also need a simple mechanism to find packages. However, since packages are something we want to have anyway, it would not hurt to start and implement a rudimentary system now.

    \n

    This proposal is a bit at odds with the file based import mechanism described above. While it is clear what the namespace of an *.lf file relative to a package directory is, it is unclear what the namespace of an arbitrary file outside a package is. Marten suggested to resolve this by using a default namespace or the global namespace whenever a *lf that is not part of a package is imported and to make the user responsible for avoiding any conflicts.

    \n

    We would also need to restrict the naming of files and directories and ban the usage of the namespace delimiter (. or :: or some other) in file and directory names. In my opinion this is not much of a problem and common practice for many languages. If we decide to use this namespace mechanism, it would probably be better to drop the file based imports and switch to imports by FQN (e.g. import Foo from foo.bar.foo)

    \n

    A Namespace Directive

    \n

    As an alternative to the file based namespace mechanism described above, we could also introduce a namespace directive in the LF syntax or as part of the target properties. This would effectively allow the user to specify the namespace that any reactor defined in a file should be part of. This solution would allow to augment the file based import system that we have with a namespace mechanism. It is important to note, however, that this entirely shifts the responsibility for ensuring uniqueness within a namespace to the user. When we derive namespaces from the file path as described above, we can be sure that the resulting namespace only contains unique reactors because we ensure that any LF file only contains unique reactors. If we allow the user to specify the namespace, however, there could easily be two files with the same namespace directive that both define the reactor Foo. This approach might also cause problems for target languages where the namespaces relate to concrete file paths such as in Rust, Python or Java.

    \n

    Name Mangling

    \n

    There are other mechanisms to derive unique names apart from namespaces. One that is widely used by compilers is name mangling which replaces or decorates the original name. For instance, we could simply add a number to the name of generated reactors (Foo1, Foo2, …) to distinguish multiple LF reactor definitions named Foo. What separates our approach from traditional compiler though, is that we are not in control of the full build process and only generate source code to be processed by another compiler. Therefore, any renaming we do when compiling LF code to target code needs to be done with care as it could easily introduce new problems because we are not aware of all the identifiers defined in a target language. For instance if our LF program uses a library that defines the class Foo3, adding a third definition of the reactor Foo to the program would lead to an unexpected error that is also hard to debug.

    \n

    Soroush also proposed to use a hashing mechanism (for instance a hash of the file name) to decorate reactor names. This would be less likely\nto clash with any names defined in some library. However, we would need to make sure that any mechanism we use for generating unique decorated names follows strict rules and generates reproducible names. This reproducibility is crucial for several reasons.

    \n
      \n
    1. \n

      Since even a complex name mangling mechanism has still the chance to produce name clashes with identifiers defined outside of the LF program, those clashes should not occur randomly. There should be either an error or no error on each compilation run. Nondeterministic builds are no fun to deal with.

      \n
    2. \n
    3. \n

      In case of any errors, it is crucial to be able to reproduce and compare builds across machines and platforms. A platform dependent name mangling algorithm (for instance one that hashes file paths) would make it unnecessary hard to reproduce and debug compile errors.

      \n
    4. \n
    5. \n

      Somewhere in the future, we might want to be able to compile packages as libraries. Recompilation of the library should never change its API. Moreover, the name mangling algorithm should be robust in the sense that small changes in LF code do not lead to changed identifiers\nin the library API.

      \n
    6. \n
    \n

    All in all, I think it is hard to define an algorithm that generates reproducible and stable names, but maybe someone else has a good idea of how this could be achieved.

    \n

    Another obvious disadvantage of the name mangling approach would be that the generated code is less readable. Also any external target code that might want to reference reactors in a library compiled from LF code, would need to know and use the mangled name.

    \n

    Unique Reactor Names in our Tools

    \n

    In our last meeting (Tue 2020-08-04), I said that there are other places where we care about unique names: our tools such as the diagram view or the trace generator that I implemented for C++ and that we cannot ensure that names are unique at the moment. However, while thinking about it a bit more I realized that this is not much of an issue. Ambiguous names of reactor types are not a big problem for the diagram view. Since clicking on the nodes jumps directly to the reactor definition, the ambiguity in the names can easily be resolved.

    \n

    For the tracing, I realized that it is not the name of the reactor type that matters, but the name of the instance. These are unique fully-qualified names already. For instance main.foo.bar.r0, denotes the reaction with priority 0, of a reactor instance called bar that is contained by the reactor instance foo, which is in turn contained by the main reactor.

    \n

    Summary

    \n

    All in all, I think leveraging the file structure for determining the fully qualified names of reactors is the most promising solution.

    \n
      \n
    1. It works without any changes in our syntax. Only the code generators need to be updated to support the namespacing.
    2. \n
    3. In contrast to name mangling, it allows generation of readable code and also gives the programmer full control of how generated reactors are named.
    4. \n
    5. It fits naturally to languages that also support leveraging the file structure to create namespaces (e.g. python or rust).
    6. \n
    ","headings":[{"value":"The status quo","depth":1},{"value":"A quick dive into the C++ code generator","depth":2},{"value":"The problem with preambles","depth":2},{"value":"The problem with target properties","depth":2},{"value":"The work in progress","depth":3},{"value":"Possible solutions","depth":3},{"value":"Concrete proposal","depth":2},{"value":"Import/export","depth":3},{"value":"Syntax","depth":3},{"value":"Preambles","depth":2},{"value":"Target Properties","depth":2},{"value":"Build Dependencies","depth":2},{"value":"Unique Reactor Names","depth":1},{"value":"Examples","depth":2},{"value":"Example 1","depth":3},{"value":"Example 2","depth":3},{"value":"Unique Reactor Names in Target Code","depth":2},{"value":"Possible Solutions","depth":2},{"value":"Namespaces","depth":3},{"value":"File based namespaces","depth":4},{"value":"A Namespace Directive","depth":4},{"value":"Name Mangling","depth":3},{"value":"Unique Reactor Names in our Tools","depth":2},{"value":"Summary","depth":2}],"frontmatter":{"permalink":"/docs/handbook/proof-import","title":"Future Proof Package/Import System","oneline":"A future proof package and import system","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/proof-import","repoPath":"/packages/documentation/copy/en/less-developed/Future Proof Package and Import System.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/proof-import","result":{"data":{"markdownRemark":{"id":"4466937a-36f5-5fc8-a89d-98421824f135","excerpt":"This is a collection of thoughts on the design of a reliable package and import system that is ready for future applications. At this stage, this page mostly…","html":"

    This is a collection of thoughts on the design of a reliable package and import system that is ready for future applications. At this stage, this page mostly represents my personal view (Christian Menard). I will also focus on the C++ target here as this is the target I know best. The C target is not a good example for these considerations as there is a fundamental design issue with the C target. Since the code generator places all code in a single generated .c file and does things like #include reactor.c to avoid the need for Makefiles, it circumvents many of the issues that come with imports that I will outline here. It simply ignores file scopes and namespaces altogether.

    \n

    The status quo

    \n

    The current import system is lean and simple. Write import Bar.lf in Foo.lf and every reactor defined in Bar.lf will be visible in the file scope Foo.lf. Bar.lf is looked up simply by scanning the directory Foo.lf is placed in. This works well for the simple programs and tests we have right now, but does not scale. I identify the following problems:

    \n
      \n
    1. \n

      There is no notion of separate namespaces. Every reactor that Bar.lf defines becomes visible in Foo.lf. If both files define a Reactor Foo, there is a name clash and the import would be ill-formed. There should be a mechanism to distinguish the two definitions of Foo, such as using fully qualified names: Foo.Foo and Bar.Foo.

      \n
    2. \n
    3. \n

      There is no concept for importing files from a directory structure. It is unclear how Foo.lf could import my/lib/Bar.lf.

      \n
    4. \n
    5. \n

      There is no concept for packages or libraries that can be installed on the system. How could we import Reactors from a library that someone else provided?

      \n
    6. \n
    \n

    These are the more obvious issues that we have talked about. However, there are more subtle ones that we haven’t been discussed in depth (or at least not in the context of the import system design discussion). The open question is: What does importing a LF file actually mean? Obviously, an import should bring Reactors defined in another files into local scope. But what should happen with the other structures that are part of an LF file, namely target properties and preambles? That is not specified and our targets use a best practice approach. But this is far away from a good design that is scalable and future proof.

    \n

    A quick dive into the C++ code generator

    \n

    Before I discuss the problems with preambles and target properties, I would like to give you a quick overview of how the C++ code generator works. Consider the following LF program consisting of two files Foo.lf and Bar.lf:

    \n
    // Bar.lf\n\nreactor Bar {\n reaction(startup) {=\n   // do something bar like\n =}\n}
    \n
    // Foo.lf\n\nimport Bar.lf\n\nreactor Foo {\n bar = new Bar();\n reaction(startup) {=\n   // do something foo like\n =}\n}
    \n

    Now let us have a look on what the C++ code generator does. It will produce a file structure like this:

    \n
    CMakeLists.txt\nmain.cc\nBar/\n  Bar.cc\n  Bar.hh\nFoo/\n  Foo.cc\n  Foo.hh
    \n

    We can ignore CMakeLists.txt and main.cc for our discussion here. The former specifies how the whole program can be build and the latter contains the main() function and some code that is required to get the application up and running. For each processed <file>.lf file, the code generator creates a directory <file>. For each reactor <reactor> defined in <file>.lf, it will create <file>/<reactor>.cc and <file>/<reactor>.hh. The header file declares a class representing the reactor like this:

    \n
    // Bar/Bar.hh\n\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n\nclass Bar : public reactor::Reacor {\n private:\n  // default actions\n  reactor::StartupAction startup {"startup", this};\n  reactor::ShutdownAction shutdown {"shutdown", this};\n\n public:\n  /* ... */\n\n private:\n  // reaction bodies\n  r0_body();\n};
    \n

    The corresponding Bar/Bar.cc will look something like this:

    \n
    #include "Bar/Bar.hh"\n\n/* ... */\n\nBar::r0_body() {\n  // do something bar like\n}
    \n

    Similarly, Foo.hh and Foo.cc will be generated. However, since Foo.lf imports Bar.lf and instantiated the reactor Bar it must be made visible. This is done by an include directive in the generated code like so:

    \n
    // Foo/Foo.hh\n###\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n#include "Bar/Bar.hh"\n\nclass Foo : public reactor::Reacor {\n private:\n  // default actions\n  reactor::StartupAction startup;\n  reactor::ShutdownAction shutdown;\n\n  // reactor instances\n  Bar bar;\n public:\n  /* ... */\n\n private:\n  // reaction bodies\n  r0_body();\n};
    \n

    The problem with preambles

    \n

    The problems with preamble in the context of imports were already discussed in a related issue, but I would like to summarize the problem here. While the examples above worked nicely even with imports, things get messy as soon as we introduce a preamble. Let’s try this:

    \n
    // Bar.lf\n\nreactor Bar {\n preamble {=\n   struct bar_t {\n     int x;\n     std::string y;\n   };\n\n   bar_t bar_func {\n     return bar_t(42, "hello")\n   }\n =}\n output out:bar_t;\n reaction(startup) -> out {=\n   out.set(bar_fuc());\n =}\n}
    \n
    // Foo.lf\n\nimport Bar.lf\n\nreactor Foo\n bar = new Bar();\n reaction(bar.out) {=\n   auto& value = bar.out.get();\n   std::cout << "Received {" << value->x << ", " << value->y << "}\\n";\n =}\n}
    \n

    This would be expected to print Received {32, hello}. However, before we can even compile this program, we need to talk about what should happen with the preamble during code generation and how the import affects it. So where should the preamble go? The first thing that comes to mind, is to embed it in the header file Bar.hh something like this:

    \n
    // Bar/Bar.hh\n\n# pragma once\n\n#include "reactor-cpp/reactor-cpp.hh"\n\n// preamble\nstruct bar_t {\n  int x;\n  std::string y;\n};\n\nbar_t bar_func {\n  return bar_t(42, "hello")\n}\n\nclass Bar : public reactor::Reacor {\n /* ... */\n};
    \n

    If we embed the preamble like this and compile the program ,then the compiler is actually happy and processes all *.cc files without any complaints. But, there is a huge problem while linking the binary. The linker sees multiple definitions of bar_func and has no idea which one to use. Why is that? Well, the definition of bar_func is contained in a header file. This should never be done in C/C++! Since includes translate to a plain-text replacement by the preprocessor, Bar.cc will contain the full definition of bar_func. As Foo.cc imports Foo.hh which imports Bar.hh, also Foo.cc will contain the full definition. And since main.cc also has to include Foo.hh, main.cc will also contain the full definition of bar_func. So we have multiple definitions of the same function and the linker rightfully reports this as an error.

    \n

    So what should we do? We could place the preamble in Bar.cc instead. This ensures that only Bar.cc sees the definition of bar_func. But then the compiler complains. Neither Bar.hh nor Foo.hh see type declaration of bar_t. Note that there is a dependency of Foo.lf on the preamble in Bar.lf. The import system should somehow take care of this dependency! Also note that this has not appeared as a problem in C as the code generator places everything in the same compilation unit. Foo will see the preamble of Bar as long as Foo is generated before Bar.

    \n

    But how to solve it for C++ where the code is split in multiple compilation units (which really should be happening in C as well)? What we do at the moment is annotating the preamble with private and public keywords. This helps to split the preamble up and decide what to place in the header and what to place in the source file. For instance:

    \n
    // Bar.lf\n\nreactor Bar {\n public preamble {=\n   struct bar_t {\n     int x;\n     std::string y;\n   };\n =}\n private preamble {=\n   bar_t bar_func {\n     return bar_t(42, "hello")\n   }\n =}\n output out:bar_t;\n reaction(startup) -> out {=\n   out.set(bar_fuc());\n =}\n}
    \n

    This makes the type bar_t visible as part of the public interface of Bar. Both the code generated for Bar and the code generated for Foo will see the definition of bar_t. This is realized by placing the public preamble in Bar.hh The function bar_func is part of Bar’s private interface. It is only visible with the reactor definition of Bar and is not propagated by an import. This is realized by simply placing the private preamble in Bar.cc. This makes the compiler finally happy and when get an executable program private and public preambles provide a mechanism to define what is propagated on an import and what is not. I think this is an important distinction even in languages other than C/C++ that do not have this weird separation of source and header file.

    \n

    I am sorry for this lengthy diversion into things that happened in the past where we actually want to talk about how things should work in the future. However, understanding this issue is important and when talking about other solutions we should not forget that it exists.

    \n

    The problem with target properties

    \n

    It is also not well-defined what should happen with target properties when importing a .lf file. Apparently the common practice is simply ignoring the existence of other target declarations and only considering the target declaration of the .lf that contains the main reactor. I think this works reasonably well for our small programs. But it will cause problems when either programs become larger or we introduce new target properties where it is unclear what piece of code they reference. Let us have a look at the existing target properties for C++. How should those different properties be handled on an import? Which scope do they actually apply to? We haven’t really talked about this.

    \n

    fast, keepalive, threads and timeout are easy. They apply to the main reactor. Since we do not import main reactors from other files, it is clear that we really want to use the properties defined in the main compilation unit. So our current strategy works in this case. Although there are still some subtleties. For instance, if a library file defines keepalive=true and fast=false because it uses physical actions, should any file importing this library file be allowed to override these properties. Probably not, because it doesn’t make sense if physical actions are involved. But a careless user of the library might not be aware of that. So maybe it isn’t that clear after all.

    \n

    build-type, cmake-include, compile, logging and no-runtime-validation influence how the application is build. They are used for generating the CMakeLists.txt file. So their is quite clear: they apply to the whole compilation of the given application. Again it is a simple solution to only consider the target properties of the file containing the main reactor since this can be considered the file that ‘drives’ the compilation. But what if an imported .lf relies on an external library and uses the cmake-include property to tell CMake to look this library up, make the library header files visible and link our generated code to that library (fortunately this can be done with 2 lines in CMake). Should this target property really be ignored by our import? Probably not, because it will lead to compile errors if the author of the main .lf file does not configure cmake-include properly. So there should be some kind of merging mechanism for cmake-include. Should this be done for the other properties as well? I am not sure and I actually don’t know how the merging would work.

    \n

    So this raises a lot of questions that we currently have no answer to. I believe we need to find answers for these questions in order to create a well working import and package system. This gets only more complicated when we add more properties such as the proposed files directive. We should really consider what properties actually apply to and if they influence the way imports work.

    \n

    The work in progress

    \n

    To be continued… I want to describe here what is happening on the new_import and the (potential) problems this brings.

    \n

    Possible solutions

    \n

    To be continued… I would like to show a few possible solutions that have come to mind and that we discussed already.

    \n

    Concrete proposal

    \n

    With the risk of overlooking some of the issues discussed above, I’d like to outline a concrete proposal. To me, at least, it is easier to reason about these issues in a context with a few more constraints. Hopefully, this can serve as a starting point that we can tweak/adjust as needed. Note: this proposal borrows from the previous proposal written by Soroush. Based on my experience with Xtext, I have confidence that what is described below is feasible to implement.

    \n

    Import/export

    \n
      \n
    1. One LF file can contain multiple reactor definitions.
    2. \n
    3. There can be at most one main reactor per file.
    4. \n
    5. Any reactor class defined outside of the current file has to be imported explicitly.
    6. \n
    7. The visibility of a reactor class can be limited using a modifier in the class definition.
    8. \n
    \n
      \n
    • Should the default visibility be public or private? I have no strong preference either way.
    • \n
    \n
      \n
    1. An import statement must specify which reactor classes to import. This is necessary because if we populate a global scope using the uriImport mechanism, the local scope provider needs to know which definition to link to if there happen to exist multiple among the set of included files. We could potentially relax this constraint and only report the situation where we know for a fact that there is ambiguity and needs to be resolved by making the imports explicit. We could also deprecate the use of unqualified imports (the original syntax), therefore allow it but warn that it might not work as expected.
    2. \n
    3. An LF file in an import statement is specified by a path relative to the directory of the file in which the import statement occurs or relative to a series of directories in a wider search path.
    4. \n
    \n
      \n
    • Eclipse uses .project files to identify the root of a project; we can look for that.
    • \n
    • We can look for our own kind of manifest files as well. These can list additional locations to search. This is compatible with the idea of developing a package system. I personally like this approach better than using an environment variable.
    • \n
    \n
      \n
    1. I personally find fully qualified names excess generality and have never felt compelled to use them in Java. IMO, they lead to code that’s difficult to read and a pain to format. To keep things simple, I suggest we don’t support them. Instead, we should provide a mechanism for renaming imported reactor classes to avoid naming conflicts.
    2. \n
    3. Open question: do we want scope modifiers for imports? It seems that extra import statements could be used to increase visibility, so it might not be needed.
    4. \n
    \n

    Syntax

    \n
    Import := 'import' <ID> <Rename>? (',' <ID> <Rename>?)* 'from' <PATH>\n\nRename := 'as' <ID>
    \n

    Note: This syntax could be extended to support packages in addition to paths. But it doesn’t make much sense to have this until we have a package manager and package registry.

    \n

    Current state of the discussion: one unifying syntax vs. different syntax for references to files and packages.

    \n

    Preambles

    \n

    A preamble allows for the inclusion of verbatim target code that may be necessary for reactors to function. Currently, there are two scopes in which preambles can appear: (1) file scope and (2) reactor class scope. Moreover, there exist visibility modifiers to label preambles private or public. A public preamble is intended to contain code that is necessary for the use of a reactor that is in scope. A private preamble is intended to contain code that is necessary for the implementation of a reactor that is in scope. Only the C++ code generator can currently effectively separate these LF scope levels. It achieves this by putting each reactor class definition in its own file. LF file scope preambles are currently not supported by the C target, but this appears to be unintentional and would be easy to fix. Reactor class scope preambles are supported by the C target, but there is no isolation of scope; the preamble of one reactor is visible to the one defined after it. To fix this, I see two options: (1) follow the same approach as CppGenerator and output separate files, which also means that a Makefile has to be generated in order to compile the result, or (2) leverage block scope within a single file, but this will become complicated and make the generated C code even less humanly readable.

    \n

    We could put aside the problem of name clashes due to the absence of scope isolation in generated C code and fix this later. For the time being, the problem can be circumvented using .h files.

    \n

    Target Properties

    \n
      \n
    1. Each file declares a target.
    2. \n
    3. All code in all reactors in the same file must agree with the specified target.
    4. \n
    5. Additional target properties may be specified.
    6. \n
    7. Target properties are not inherited through imports.
    8. \n
    9. Any property that needs to be inherited through an import (such as the requirement to link against the pthread library) must be specified as a build dependency instead.
    10. \n
    \n

    Build Dependencies

    \n
      \n
    1. It must be possible to specify build dependencies, such as files, sources, and protobufs.
    2. \n
    3. We could either allow these definitions to go directly in the .lf file, or we could decide to specify them in a package description (i.e., separate file). We could potentially allow both.
    4. \n
    5. Build dependencies are inherited through imports (or from package descriptions), and they are never shadowed, always joined.
    6. \n
    \n

    Unique Reactor Names

    \n

    The new import system as described above ensures that reactor names within a single .lf file are unique. In case reactors with the same name are imported from different .lf files, the renaming mechanism needs to be used in order to resolve the name conflict. The same applies if the given .lf file defines some reactors and tries to import other reactors with the same name. For instance, consider the LF file in Example 1 below. In the scope of this file, three reactor declarations are visible: Foo, Bar and Baz, although the actual reactors have the same name Foo.

    \n

    Examples

    \n

    Throughout this section, I will be using two LF example programs. Since the markdown syntax does not provide a simple way to label and refer to code listings, I figure its easiest to place them here in a central place and refer to them later by the heading

    \n

    Example 1

    \n
    \\\\ Foo.lf\nimport Foo as Bar from "Bar.lf"\nimport Foo as Baz from "Baz.lf"\n\nreactor Foo {\n  \\\\ ...\n}
    \n

    Example 2

    \n
    \\\\ Baz.lf\nreactor Foo {\n  \\\\ ...\n}
    \n
    \\\\ Bar.lf\nimport Foo from "Baz.lf"\n\nreactor Foo {\n  foo = new Foo()\n  \\\\ ...\n}
    \n
    \\\\ Foo.lf\nimport Bar from "Bar.lf"\nmain reactor Foo {\n  bar = new Bar()\n  \\\\ ...\n}
    \n

    Unique Reactor Names in Target Code

    \n

    While the mechanism above effectively ensures uniqueness in a single LF file, this uniqueness is surprisingly hard to ensure in generated target code. C has an obvious problem here as it places all generated code in a single file. While the name conflict in the above code can be solved by generating code for three reactors named Bar, Baz and Foo, it breaks as soon as another file of the same LF program uses import Foo from \"Bar.lf\". Then there would be two definitions of the reactor Foo that cannot be resolved.

    \n

    Now you would probably expect that splitting the generated code into multiple files solves the issue, but unfortunately this is not true. If anything, it makes the problem more subtle. The C++ code generated from Example 1 would likely look something like this:

    \n
    // Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n#include "Baz.hh"\n\n// do the renaming\nusing Bar = Foo;\nusing Baz = Foo;\n\nclass Foo : public reactor::Reactor {\n};
    \n

    This will cause a compile error as there are multiple definitions of Foo. While renaming is possible in C++ with the using keyword (typedef works as well), the thing being renamed needs to be already visible in the scope. So there are multiple definitions of Foo as all the files Bar.hh, Baz.hh and Foo.hh define this class. We need a mechanism to distinguish the different definitions of Foo.

    \n

    There is even another issue that stems from the fact that the semantics of imports in LF are different from the include semantics of C++. Consider the code in Example 2, which is valid LF code. Although Bar.lf imports Foo and Foo.lf imports from Bar.lf, the definition of Foo in Baz.lf is not visible in Foo.lf. This ‘hiding’, however, does not easily propagate to the generated code. In C, there will be an error because both definitions of Foo are placed in the same file. In C++, the different definitions of Foo are placed in different files, but there will still be an error. The generated C++ code would look something like this:

    \n
    \\\\ Baz.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\nclass Foo : public reactor::Reactor {\n  // ...\n};
    \n
    \\\\ Bar.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Baz.hh"\n\nclass Bar : public reactor::Reactor {\n  Foo foo;\n  // ...\n};
    \n
    \\\\ Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n\nclass Foo : public reactor::Reactor {\n  Bar bar;\n  // ...\n};
    \n

    This will produce an error due to multiple definitions of Foo being visible in Foo.hh. The problem is that any include in Bar.hh becomes also visible in Foo.hh. So there is a name clash due to the way the C++ compiler processes included and that is hard to work around.

    \n

    Possible Solutions

    \n

    In conclusion from the above section, I can say that translating the file based scoping of reactor names that we have in LF to generated target code is not trivial. Any sensible solution will need to establish a mechanism to ensure that any two distinct reactors in LF are also distinct in target code.

    \n

    Namespaces

    \n

    We could introduce some form of a namespace mechanism that allows us to derive fully-qualified names of reactors. This is the preferred solution for me (Christian). Note that by ‘namespace’ I mean any logical organization of reactors in named groups and not the precise concept of C++ namespaces. In other languages those logical groups are also referred to as modules or packages. Also note that it is only important to be able to assign a fully-qualified name to a reactor, it does not necessarily require that we refer to reactors by their fully-qualified name in LF code.

    \n

    File based namespaces

    \n

    In my view, the easiest way to introduce namespaces in LF would be to leverage file system structure. Everything contained in Foo.lf would automatically be in the namespace Foo. So the FQN of a reactor Foo defined in Foo.lf would be Foo.Foo (or Foo::Foo, or some other delimiter). This would solve the name clashes in both of our examples. For Example 1, the generated code could look like this:

    \n
    // Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n#include "Baz.hh"\nnamespace Foo {\n\n// do the renaming\nusing Bar = Bar::Foo;\nusing Baz = Baz::Foo;\n\nclass Foo : public reactor::Reactor {\n};\n}
    \n

    For Example 2, the generated code could look like this:

    \n
    \\\\ Baz.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\nnamespace Baz {\nclass Foo : public reactor::Reactor {\n  // ...\n};\n}
    \n
    \\\\ Bar.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Baz.hh"\n\nnamespace Bar {\nusing Foo = Baz::Foo; // bring Foo in scope\n\nclass Bar : public reactor::Reactor {\n  Foo foo;\n  // ...\n};\n}
    \n
    \\\\ Foo.hh\n#include <reactor-cpp/reactor-cpp.hh>\n\n#include "Bar.hh"\n\nnamespace Foo {\nusing Bar = Bar::Bar; // bring Bar in scope\n\nclass Foo : public reactor::Reactor {\n  Bar bar;\n  // ...\n};\n}
    \n

    While this appears to be a promising solution, it is not sufficient to only consider the name of an *.lf file to derive the namespace\nThere could be two files Foo.lf in different directories that both define the reactor Foo. Thus, we also need to consider the directory structure and introduce hierarchical namespaces. Consider this directory tree:

    \n
    foo/\n  bar/\n    foo.lf  # defines reactor Foo\n  baz/\n    foo.lf  # defines reactor Foo
    \n

    In this example, the two Foo reactors would have the fully qualified names foo.bar.foo.Foo and foo.baz.foo.Foo.\nIn order for this concept to work, we need the notion of a top-level namespace or directory. Naturally, this would be the package. Therefore, this namespace approach would also require a simple mechanism to define a package. For now this could be rudimentary. Simply placing an empty lf.yaml in the foo/ directory in the above example would be sufficient. In addition to the notion of packages, we would also need a simple mechanism to find packages. However, since packages are something we want to have anyway, it would not hurt to start and implement a rudimentary system now.

    \n

    This proposal is a bit at odds with the file based import mechanism described above. While it is clear what the namespace of an *.lf file relative to a package directory is, it is unclear what the namespace of an arbitrary file outside a package is. Marten suggested to resolve this by using a default namespace or the global namespace whenever a *lf that is not part of a package is imported and to make the user responsible for avoiding any conflicts.

    \n

    We would also need to restrict the naming of files and directories and ban the usage of the namespace delimiter (. or :: or some other) in file and directory names. In my opinion this is not much of a problem and common practice for many languages. If we decide to use this namespace mechanism, it would probably be better to drop the file based imports and switch to imports by FQN (e.g. import Foo from foo.bar.foo)

    \n

    A Namespace Directive

    \n

    As an alternative to the file based namespace mechanism described above, we could also introduce a namespace directive in the LF syntax or as part of the target properties. This would effectively allow the user to specify the namespace that any reactor defined in a file should be part of. This solution would allow to augment the file based import system that we have with a namespace mechanism. It is important to note, however, that this entirely shifts the responsibility for ensuring uniqueness within a namespace to the user. When we derive namespaces from the file path as described above, we can be sure that the resulting namespace only contains unique reactors because we ensure that any LF file only contains unique reactors. If we allow the user to specify the namespace, however, there could easily be two files with the same namespace directive that both define the reactor Foo. This approach might also cause problems for target languages where the namespaces relate to concrete file paths such as in Rust, Python or Java.

    \n

    Name Mangling

    \n

    There are other mechanisms to derive unique names apart from namespaces. One that is widely used by compilers is name mangling which replaces or decorates the original name. For instance, we could simply add a number to the name of generated reactors (Foo1, Foo2, …) to distinguish multiple LF reactor definitions named Foo. What separates our approach from traditional compiler though, is that we are not in control of the full build process and only generate source code to be processed by another compiler. Therefore, any renaming we do when compiling LF code to target code needs to be done with care as it could easily introduce new problems because we are not aware of all the identifiers defined in a target language. For instance if our LF program uses a library that defines the class Foo3, adding a third definition of the reactor Foo to the program would lead to an unexpected error that is also hard to debug.

    \n

    Soroush also proposed to use a hashing mechanism (for instance a hash of the file name) to decorate reactor names. This would be less likely\nto clash with any names defined in some library. However, we would need to make sure that any mechanism we use for generating unique decorated names follows strict rules and generates reproducible names. This reproducibility is crucial for several reasons.

    \n
      \n
    1. \n

      Since even a complex name mangling mechanism has still the chance to produce name clashes with identifiers defined outside of the LF program, those clashes should not occur randomly. There should be either an error or no error on each compilation run. Nondeterministic builds are no fun to deal with.

      \n
    2. \n
    3. \n

      In case of any errors, it is crucial to be able to reproduce and compare builds across machines and platforms. A platform dependent name mangling algorithm (for instance one that hashes file paths) would make it unnecessary hard to reproduce and debug compile errors.

      \n
    4. \n
    5. \n

      Somewhere in the future, we might want to be able to compile packages as libraries. Recompilation of the library should never change its API. Moreover, the name mangling algorithm should be robust in the sense that small changes in LF code do not lead to changed identifiers\nin the library API.

      \n
    6. \n
    \n

    All in all, I think it is hard to define an algorithm that generates reproducible and stable names, but maybe someone else has a good idea of how this could be achieved.

    \n

    Another obvious disadvantage of the name mangling approach would be that the generated code is less readable. Also any external target code that might want to reference reactors in a library compiled from LF code, would need to know and use the mangled name.

    \n

    Unique Reactor Names in our Tools

    \n

    In our last meeting (Tue 2020-08-04), I said that there are other places where we care about unique names: our tools such as the diagram view or the trace generator that I implemented for C++ and that we cannot ensure that names are unique at the moment. However, while thinking about it a bit more I realized that this is not much of an issue. Ambiguous names of reactor types are not a big problem for the diagram view. Since clicking on the nodes jumps directly to the reactor definition, the ambiguity in the names can easily be resolved.

    \n

    For the tracing, I realized that it is not the name of the reactor type that matters, but the name of the instance. These are unique fully-qualified names already. For instance main.foo.bar.r0, denotes the reaction with priority 0, of a reactor instance called bar that is contained by the reactor instance foo, which is in turn contained by the main reactor.

    \n

    Summary

    \n

    All in all, I think leveraging the file structure for determining the fully qualified names of reactors is the most promising solution.

    \n
      \n
    1. It works without any changes in our syntax. Only the code generators need to be updated to support the namespacing.
    2. \n
    3. In contrast to name mangling, it allows generation of readable code and also gives the programmer full control of how generated reactors are named.
    4. \n
    5. It fits naturally to languages that also support leveraging the file structure to create namespaces (e.g. python or rust).
    6. \n
    ","headings":[{"value":"The status quo","depth":1},{"value":"A quick dive into the C++ code generator","depth":2},{"value":"The problem with preambles","depth":2},{"value":"The problem with target properties","depth":2},{"value":"The work in progress","depth":3},{"value":"Possible solutions","depth":3},{"value":"Concrete proposal","depth":2},{"value":"Import/export","depth":3},{"value":"Syntax","depth":3},{"value":"Preambles","depth":2},{"value":"Target Properties","depth":2},{"value":"Build Dependencies","depth":2},{"value":"Unique Reactor Names","depth":1},{"value":"Examples","depth":2},{"value":"Example 1","depth":3},{"value":"Example 2","depth":3},{"value":"Unique Reactor Names in Target Code","depth":2},{"value":"Possible Solutions","depth":2},{"value":"Namespaces","depth":3},{"value":"File based namespaces","depth":4},{"value":"A Namespace Directive","depth":4},{"value":"Name Mangling","depth":3},{"value":"Unique Reactor Names in our Tools","depth":2},{"value":"Summary","depth":2}],"frontmatter":{"permalink":"/docs/handbook/proof-import","title":"Future Proof Package/Import System","oneline":"A future proof package and import system","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/proof-import","repoPath":"/packages/documentation/copy/en/less-developed/Future Proof Package and Import System.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/reaction-declarations/page-data.json b/page-data/docs/handbook/reaction-declarations/page-data.json index 46dd561a6..b8a534d43 100644 --- a/page-data/docs/handbook/reaction-declarations/page-data.json +++ b/page-data/docs/handbook/reaction-declarations/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reaction-declarations","result":{"data":{"markdownRemark":{"id":"9562e9fd-8d70-5f52-b5b1-0a79a3e238c2","excerpt":"Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also…","html":"

    Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also possible to just declare them and provide implementations in a separate file. The syntax of reaction declarations is the same as for reaction definitions, except they have no implementation. Reaction declarations can be thought of as function prototypes.

    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target C {\n  cmake-include: ["hello.cmake"],\n  files: ["hello.c"]\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The cmake-include target property is used to make the build system aware of an externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.c)\n
    \n

    The files target property is used to make the file that has the implementation in hello.c accessible,\nwhich could look something like this:

    \n
    #include <stdio.h>\n#include "../include/HelloDecl/HelloDecl.h"\n\nvoid hello(hellodecl_self_t* self) {\n    printf("Hello declaration!\\n");\n}\n
    \n

    File Structure

    \n

    In the above example, the C file used #include to import a file called HelloDecl.h. This file\nwas generated from the Lingua Franca source file when the LF program was compiled. The file\nHelloDecl.h is named after the main reactor, which is called HelloDecl, and its parent\ndirectory, include/HelloDecl, is named after the file, HelloDecl.lf.

    \n

    In general, compiling a Lingua Franca program that uses reaction declarations will always generate a\ndirectory in the include directory for each file in the program. This directory will contain a\nheader file for each reactor that is defined in the file.

    \n

    As another example, if an LF program consists of files F1 and F2, where F1 defines reactors\nA and B and F2 defines the reactor C and the main reactor F2, then the directory structure\nwill look something like this:

    \n
    include/\n├ F1/\n│ ├ A.h\n│ └ B.h\n└ F2/\n  ├ C.h\n  └ F2.h\nsrc/\n├ F1.lf  // defines A and B\n└ F2.lf  // defines C and F2\nsrc-gen/
    \n

    There is no particular location where you are required to place your C files or your CMake files.\nFor example, you may choose to place them in a directory called c that is a sibling of the src\ndirectory.

    \n

    The Generated Header Files

    \n

    The generated header files are necessary in order to separate your C code from your LF code because\nthe describe the signatures of the reaction functions that you must implement.

    \n

    In addition, they define structs that will be referenced by the reaction bodies. This includes the\nself struct of the reactor to which the header file corresponds, as well as structs for its ports,\nits actions, and the ports of its child reactors.

    \n

    As with preambles, programmer discipline is required to avoid breaking the deterministic semantics\nof Lingua Franca. In particular, although the information exposed in these header files allows\nregular C code to operate on ports and self structs, such information must not be saved in global or\nstatic variables.

    \n

    Linking Your C Code

    \n

    As with any Lingua Franca project that uses external C files, projects involving external reactions\nmust use the cmake-include target property to link those files into the main target.

    \n

    This is done using the syntax

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE <files>)\n
    \n

    where <files> is a list of the C files you need to link, with paths given relative to the project\nroot (the parent of the src directory).

    \n
    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target Cpp {\n  cmake-include: ["hello.cmake"],\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The behavior of the hello reaction is provided using a method definition in an external C++ file hello.cc.

    \n
    #include "HelloDecl/HelloDecl.hh" // include the code generated reactor class\n\n// define the reaction implementation\nvoid HelloDecl::Inner::hello([[maybe_unused]] const reactor::StartupTrigger& startup) {\n  std::cout << "Hello World." << std::endl;\n}\n
    \n

    Using the cmake-include target property, we can make the build system aware of this externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.cc)\n
    \n

    Note that this mechanism can be used to add arbitrary additional resources such as additional headers and implementation files or 3rd party libraries to the compilation.

    \n

    Header Files and Method Signatures

    \n

    In order to provide an implementation of a reaction method, it is important to know the header file that declares the reactor class as well as the precise signature of the method implementing the reaction body.

    \n

    The LF compiler generates a header file for each reactor that gets defined in LF code. The header file is named after the corresponding reactor and prefixed by the path to the LF file that defines the reactor. Consider the following example project structure:

    \n
    src/\n├ A.lf   // defines Foo\n└ sub/\n  └ B.lf // defines Bar
    \n

    In this case, the compiler will generate two header files A/Foo.hh and sub/B/Bar.hh, which would need to be included by an external implementation file.

    \n

    The precise method signature depends on the name of the reactor, the name of the reactions, and the precise triggers, sources, and effects that are defined in the LF code.\nThe return type is always void. A reaction foo in a reactor Bar will be named Bar::Inner::foo. Note that each reactor class in the C++ target defines an Inner class which contains all reactions as well as the parameters and state variables. This is done to deliberately restrict the scope of reaction bodies in order to avoid accidental violations of reactor semantics.\nAny declared triggers, sources or effects are given to the reaction method via method arguments. The precise arguments and their types depend on the LF code. If in doubt, please check the signature used in the generated header file under src-gen/<lf-file>, where <lf-file> corresponds to the LF file that you are compiling.

    \n
    \n
    \n

    The $target-language$ target does not currently support reaction declarations.

    \n
    ","headings":[{"value":"Example","depth":2},{"value":"File Structure","depth":2},{"value":"The Generated Header Files","depth":2},{"value":"Linking Your C Code","depth":2},{"value":"Example","depth":2},{"value":"Header Files and Method Signatures","depth":2}],"frontmatter":{"permalink":"/docs/handbook/reaction-declarations","title":"Reaction Declarations","oneline":"Reaction declarations in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/reaction-declarations","repoPath":"/packages/documentation/copy/en/topics/Reaction Declarations.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reaction-declarations","result":{"data":{"markdownRemark":{"id":"9562e9fd-8d70-5f52-b5b1-0a79a3e238c2","excerpt":"Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also…","html":"

    Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also possible to just declare them and provide implementations in a separate file. The syntax of reaction declarations is the same as for reaction definitions, except they have no implementation. Reaction declarations can be thought of as function prototypes.

    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target C {\n  cmake-include: ["hello.cmake"],\n  files: ["hello.c"]\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The cmake-include target property is used to make the build system aware of an externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.c)\n
    \n

    The files target property is used to make the file that has the implementation in hello.c accessible,\nwhich could look something like this:

    \n
    #include <stdio.h>\n#include "../include/HelloDecl/HelloDecl.h"\n\nvoid hello(hellodecl_self_t* self) {\n    printf("Hello declaration!\\n");\n}\n
    \n

    File Structure

    \n

    In the above example, the C file used #include to import a file called HelloDecl.h. This file\nwas generated from the Lingua Franca source file when the LF program was compiled. The file\nHelloDecl.h is named after the main reactor, which is called HelloDecl, and its parent\ndirectory, include/HelloDecl, is named after the file, HelloDecl.lf.

    \n

    In general, compiling a Lingua Franca program that uses reaction declarations will always generate a\ndirectory in the include directory for each file in the program. This directory will contain a\nheader file for each reactor that is defined in the file.

    \n

    As another example, if an LF program consists of files F1 and F2, where F1 defines reactors\nA and B and F2 defines the reactor C and the main reactor F2, then the directory structure\nwill look something like this:

    \n
    include/\n├ F1/\n│ ├ A.h\n│ └ B.h\n└ F2/\n  ├ C.h\n  └ F2.h\nsrc/\n├ F1.lf  // defines A and B\n└ F2.lf  // defines C and F2\nsrc-gen/
    \n

    There is no particular location where you are required to place your C files or your CMake files.\nFor example, you may choose to place them in a directory called c that is a sibling of the src\ndirectory.

    \n

    The Generated Header Files

    \n

    The generated header files are necessary in order to separate your C code from your LF code because\nthe describe the signatures of the reaction functions that you must implement.

    \n

    In addition, they define structs that will be referenced by the reaction bodies. This includes the\nself struct of the reactor to which the header file corresponds, as well as structs for its ports,\nits actions, and the ports of its child reactors.

    \n

    As with preambles, programmer discipline is required to avoid breaking the deterministic semantics\nof Lingua Franca. In particular, although the information exposed in these header files allows\nregular C code to operate on ports and self structs, such information must not be saved in global or\nstatic variables.

    \n

    Linking Your C Code

    \n

    As with any Lingua Franca project that uses external C files, projects involving external reactions\nmust use the cmake-include target property to link those files into the main target.

    \n

    This is done using the syntax

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE <files>)\n
    \n

    where <files> is a list of the C files you need to link, with paths given relative to the project\nroot (the parent of the src directory).

    \n
    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target Cpp {\n  cmake-include: ["hello.cmake"],\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The behavior of the hello reaction is provided using a method definition in an external C++ file hello.cc.

    \n
    #include "HelloDecl/HelloDecl.hh" // include the code generated reactor class\n\n// define the reaction implementation\nvoid HelloDecl::Inner::hello([[maybe_unused]] const reactor::StartupTrigger& startup) {\n  std::cout << "Hello World." << std::endl;\n}\n
    \n

    Using the cmake-include target property, we can make the build system aware of this externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.cc)\n
    \n

    Note that this mechanism can be used to add arbitrary additional resources such as additional headers and implementation files or 3rd party libraries to the compilation.

    \n

    Header Files and Method Signatures

    \n

    In order to provide an implementation of a reaction method, it is important to know the header file that declares the reactor class as well as the precise signature of the method implementing the reaction body.

    \n

    The LF compiler generates a header file for each reactor that gets defined in LF code. The header file is named after the corresponding reactor and prefixed by the path to the LF file that defines the reactor. Consider the following example project structure:

    \n
    src/\n├ A.lf   // defines Foo\n└ sub/\n  └ B.lf // defines Bar
    \n

    In this case, the compiler will generate two header files A/Foo.hh and sub/B/Bar.hh, which would need to be included by an external implementation file.

    \n

    The precise method signature depends on the name of the reactor, the name of the reactions, and the precise triggers, sources, and effects that are defined in the LF code.\nThe return type is always void. A reaction foo in a reactor Bar will be named Bar::Inner::foo. Note that each reactor class in the C++ target defines an Inner class which contains all reactions as well as the parameters and state variables. This is done to deliberately restrict the scope of reaction bodies in order to avoid accidental violations of reactor semantics.\nAny declared triggers, sources or effects are given to the reaction method via method arguments. The precise arguments and their types depend on the LF code. If in doubt, please check the signature used in the generated header file under src-gen/<lf-file>, where <lf-file> corresponds to the LF file that you are compiling.

    \n
    \n
    \n

    The $target-language$ target does not currently support reaction declarations.

    \n
    ","headings":[{"value":"Example","depth":2},{"value":"File Structure","depth":2},{"value":"The Generated Header Files","depth":2},{"value":"Linking Your C Code","depth":2},{"value":"Example","depth":2},{"value":"Header Files and Method Signatures","depth":2}],"frontmatter":{"permalink":"/docs/handbook/reaction-declarations","title":"Reaction Declarations","oneline":"Reaction declarations in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/reaction-declarations","repoPath":"/packages/documentation/copy/en/topics/Reaction Declarations.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/reactions/page-data.json b/page-data/docs/handbook/reactions/page-data.json index 0c3394bf2..94b3ccfb9 100644 --- a/page-data/docs/handbook/reactions/page-data.json +++ b/page-data/docs/handbook/reactions/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reactions","result":{"data":{"markdownRemark":{"id":"1432f795-b6dc-5a03-84b2-2f09c3c01d21","excerpt":"Reaction Declaration A reaction declaration has the following form: Each reaction declares its triggers, uses, and effects: The triggers field can be a comma…","html":"

    Reaction Declaration

    \n

    A reaction declaration has the following form:

    \n
      reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n
    \n

    Each reaction declares its triggers, uses, and effects:

    \n
      \n
    • The triggers field can be a comma-separated list of input ports, output ports of contained reactors, timers, actions, or the special events $startup$, $shutdown$, and $reset$ (explained here). There must be at least one trigger for each reaction.
    • \n
    • The uses field, which is optional, specifies input ports (or output ports of contained reactors) that do not trigger execution of the reaction but may be read by the reaction.
    • \n
    • The effects field, which is also optional, is a comma-separated lists of output ports ports, input ports of contained reactors, or actions.
    • \n
    \n

    Reactions may optionally be named. The name is cosmetic and may serve as additional documentation. Note that reactions cannot be called like functions, even if they are named.

    \n

    The reaction’s behavior is defined by its body, which should be given in the target programming language. Note that the reaction body may only read from actions and ports that it has declared as triggers or uses, and it may only write to actions and ports that is has declared as an effect. The target code generators implement a scoping mechanism, such that only variables that are declared in the reaction signature are accessible in the reaction body.

    \n

    In some targets, the reaction body may be omitted and the body can be defined natively in the target language in an external file. See the section on Bodyless Reactions for details.

    \n

    Reaction Order

    \n

    A reactor may have multiple reactions, and more than one reaction may be enabled at any given tag. In Lingua Franca semantics, if two or more reactions of the same reactor are simultaneously enabled, then they will be invoked sequentially in the order in which they are declared. More strongly, the reactions of a reactor are mutually exclusive and are invoked in tag order primarily and declaration order secondarily. Consider the following example:

    \n

    $start(Alignment)$

    \n
    target C {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s: int = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self->s += 1;\n  =}\n  reaction(t2) {=\n    self->s -= 2;\n  =}\n  reaction(t4) {=\n    printf("s = %d\\n", self->s);\n  =}\n}\n
    \n
    target Cpp {\n  timeout: 3 s\n}\nmain reactor Alignment {\n  state s: int(0)\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  timer t4(400 ms, 400 ms)\n  reaction(t1) {=\n    s += 1;\n  =}\n  reaction(t2) {=\n    s -= 2;\n  =}\n  reaction(t4) {=\n    std::cout << "s = " << std::to_string(s) << std::endl;\n  =}\n}\n
    \n
    target Python {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self.s += 1\n  =}\n  reaction(t2) {=\n    self.s -= 2\n  =}\n  reaction(t4) {=\n    print(f"s = {self.s}")\n  =}\n}\n
    \n
    target TypeScript {\n  timeout: 3 s\n}\nmain reactor Alignment {\n  state s: number = 0\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  timer t4(400 ms, 400 ms)\n  reaction(t1) {=\n    s += 1\n  =}\n  reaction(t2) {=\n    s -= 2\n  =}\n  reaction(t4) {=\n    console.log(`s = ${s}`)\n  =}\n}\n
    \n
    target Rust {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s: u32 = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self.s += 1;\n  =}\n  reaction(t2) {=\n    self.s -= 2;\n  =}\n  reaction(t4) {=\n    println!("s = {}", self.s);\n  =}\n}\n
    \n

    $end(Alignment)$

    \n

    Every 100 ms, this increments the state variable s by 1, every 200 ms, it decrements s by 2, and every 400 ms, it prints the value of s. When these reactions align, they are invoked in declaration order, and, as a result, the printed value of s is always 0.

    \n

    Overwriting Outputs

    \n

    Just as the reactions of the Alignment reactor overwrite the state variable s, logically simultaneous reactions can overwrite outputs. Consider the following example:

    \n

    $start(Overwriting)$

    \n
    target C\nreactor Overwriting {\n  output y: int\n  state s: int = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self->s += 1;\n    lf_set(y, self->s);\n  =}\n  reaction(t2) -> y {=\n    self->s -= 2;\n    lf_set(y, self->s);\n  =}\n}\n
    \n
    target Cpp\nreactor Overwriting {\n  output y: int\n  state s: int(0)\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  reaction(t1) -> y {=\n    s += 1;\n    y.set(s);\n  =}\n  reaction(t2) -> y {=\n    s -= 2;\n    y.set(s);\n  =}\n}\n
    \n
    target Python\nreactor Overwriting {\n  output y\n  state s = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self.s += 1\n    y.set(self.s)\n  =}\n  reaction(t2) -> y {=\n    self.s -= 2\n    y.set(self.s)\n  =}\n}\n
    \n
    target TypeScript\nreactor Overwriting {\n  output y: number\n  state s: number = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    s += 1\n    y = s\n  =}\n  reaction(t2) -> y {=\n    s -= 2\n    y = s\n  =}\n}\n
    \n
    target Rust\nreactor Overwriting {\n  output y: u32\n  state s: u32 = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self.s += 1;\n    ctx.set(y, self.s);\n  =}\n  reaction(t2) -> y {=\n    self.s -= 2;\n    ctx.set(y, self.s);\n  =}\n}\n
    \n

    $end(Overwriting)$

    \n

    Here, the reaction to t1 will set the output to 1 or 2, but every time it sets it to 2, the second reaction (to t2) will overwrite the output with the value 0. As a consequence, the outputs will be 1, 0, 1, 0, … deterministically.

    \n

    Reacting to Outputs of Contained Reactors

    \n

    A reaction may be triggered by the an input to the reactor, but also by an output of a contained reactor, as illustrated in the following example:

    \n

    $start(Contained)$

    \n
    target C\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if (s.y->value != 0 && s.y->value != 1) {\n      lf_print_error_and_exit("Outputs should only be 0 or 1!");\n    }\n  =}\n}\n
    \n
    target Cpp\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    auto is_correct = [](auto value){\n      return value == 0 || value == 1;\n    };\n    if (s.y.is_present() && !is_correct(*s.y.get())) {\n      std::cout << "Output shoudl only be 0 or 1!" << std::endl;\n    }\n  =}\n}\n
    \n
    target Python\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if s.y.value != 0 and s.y.value != 1:\n      sys.stderr.write("ERROR: Outputs should only be 0 or 1!\\n")\n      exit(1)\n  =}\n}\n
    \n
    target TypeScript\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if (s.y != 0 && s.y != 1) {\n      util.requestErrorStop("Outputs should only be 0 or 1!")\n    }\n  =}\n}\n
    \n
    target Rust\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    let value = ctx.get(s__y).unwrap();\n    if value != 0 && value != 1 {\n      eprintln!("Output schould only be 0 or 1!");\n      ctx.request_stop(Asap);\n    }\n  =}\n}\n
    \n

    $end(Contained)$

    \n\"Lingua\n

    This instantiates the above Overwriting reactor and monitors its outputs.

    \n

    Triggering Contained Reactors

    \n

    A reaction can set the input of a contained reactor, thereby triggering its reactions, as illustrated in the following example:

    \n

    $start(Triggering)$

    \n
    target C\nreactor Inside {\n  input x: int\n  reaction(x) {=\n    printf("Received %d\\n", x->value);=\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    lf_set(i.x, 42);\n  =}\n}\n
    \n
    target Cpp\nreactor Inside {\n  input x: int\n  reaction(x) {=\n    std::cout << "Received " << std::to_string(*x.get()) << std::endl;\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x.set(42);\n  =}\n}\n
    \n
    target Python\nreactor Inside {\n  input x\n  reaction(x) {=\n    print(f"Received {x.value}")\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x.set(42);\n  =}\n}\n
    \n
    target TypeScript\nreactor Inside {\n  input x: number\n  reaction(x) {=\n    console.log("Received ${x}");\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x = 42\n  =}\n}\n
    \n
    target Rust\nreactor Inside {\n  input x: u32\n  reaction(x) {=\n    println!("Received {}", ctx.get(x).unwrap());\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    ctx.set(i__x, 42);\n  =}\n}\n
    \n

    $end(Triggering)$

    \n

    The reaction to $startup$ declares the input port of the inside reactor as an effect and then sets it with value 42.\nThis will cause the inside reactor’s reaction to execute and print Received 42.

    \n

    Startup, Shutdown, and Reset Reactions

    \n

    Reactions may be triggered by the special events $startup$, $shutdown$, or $reset$.\nFor example,

    \n
      reaction(startup) {=\n    // ... Do something\n  =}\n
    \n

    A reaction to $startup$ is triggered at the very first tag when the program begins (or, if within a mode of a modal reactor, when the mode is first entered).\nThis reaction will be logically simultaneous with reactions to timers that have a zero offset.\nAs usual, for logically simultaneous reactions declared within the same reactor, the order in which they are invoked will be governed by the order in which they are declared.

    \n

    A reaction to $shutdown$ is invoked at program termination.\nSee the Termination section for details.

    \n
    \n

    Reactions to the $reset$ event are not supported in $target-language$ because modal reactors are not supported.

    \n
    \n
    \n

    A reaction to the $reset$ event is invoked if the reaction or reactor is within a mode of a modal reactor and the mode is entered via a reset transition.\nFor details, see the Modal Reactors section.

    \n
    \n

    Bodyless Reactions

    \n

    Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also possible to just declare them and provide implementations in a separate file. The syntax of reaction declarations is the same as for reaction definitions, except they have no implementation. Reaction declarations can be thought of as function prototypes.

    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target C {\n  cmake-include: ["hello.cmake"],\n  files: ["hello.c"]\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The cmake-include target property is used to make the build system aware of an externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.c)\n
    \n

    The files target property is used to make accessible the file that has the implementation in `hello.c,\nwhich could look something like this:

    \n
    #include <stdio.h>\n#include "../include/HelloDecl/HelloDecl.h"\n\nvoid hello(hellodecl_self_t* self) {\n    printf("Hello declaration!\\n");\n}\n
    \n

    File Structure

    \n

    In the above example, the C file uses #include to import a file called HelloDecl.h. The HelloDecl.h file\nis generated from the Lingua Franca source file when the LF program is compiled. The file\nHelloDecl.h is named after the main reactor, which is called HelloDecl, and its parent\ndirectory, include/HelloDecl, is named after the file, HelloDecl.lf.

    \n

    In general, compiling a Lingua Franca program that uses reaction declarations will always generate a\ndirectory in the include directory for each file in the program. This directory will contain a\nheader file for each reactor that is defined in the file.

    \n

    As another example, if an LF program consists of files F1.lf and F2.lf, where F1.lf defines reactors\nA and B and F2.lf defines the reactor C and the main reactor F2, then the directory structure\nwill look something like this:

    \n
    include/\n├ F1/\n│ ├ A.h\n│ └ B.h\n└ F2/\n  ├ C.h\n  └ F2.h\nsrc/\n├ F1.lf  // defines A and B\n└ F2.lf  // defines C and F2\nsrc-gen/
    \n

    There is no particular location where you are required to place your C files or your CMake files.\nFor example, you may choose to place them in a directory called c that is a sibling of the src\ndirectory.

    \n

    The Generated Header Files

    \n

    The generated header files are necessary in order to separate your C code from your LF code because\nthey describe the signatures of the reaction functions that you must implement.

    \n

    In addition, they define structs that will be referenced by the reaction bodies. This includes the\nself struct of the reactor to which the header file corresponds, as well as structs for its ports,\nits actions, and the ports of its child reactors.

    \n

    As with preambles, programmer discipline is required to avoid breaking the deterministic semantics\nof Lingua Franca. In particular, although the information exposed in these header files allows\nregular C code to operate on ports and self structs, such information must not be saved in global or\nstatic variables.

    \n

    Linking Your C Code

    \n

    As with any Lingua Franca project that uses external C files, projects involving external reactions\nmust use the cmake-include target property to link those files into the main target.

    \n

    The file referenced by the cmake-include target property has the following syntax:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE <files>)\n
    \n

    where <files> is a list of the C files you need to link, with paths given relative to the project\nroot (the parent of the src directory).

    \n
    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target Cpp {\n  cmake-include: ["hello.cmake"],\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The behavior of the hello reaction is provided using a method definition in an external C++ file hello.cc.

    \n
    #include "HelloDecl/HelloDecl.hh" // include the code generated reactor class\n\n// define the reaction implementation\nvoid HelloDecl::Inner::hello([[maybe_unused]] const reactor::StartupTrigger& startup) {\n  std::cout << "Hello World." << std::endl;\n}\n
    \n

    Using the cmake-include target property, we can make the build system aware of this externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.cc)\n
    \n

    Note that this mechanism can be used to add arbitrary additional resources such as additional headers and implementation files or 3rd party libraries to the compilation.

    \n

    Header Files and Method Signatures

    \n

    In order to provide an implementation of a reaction method, it is important to know the header file that declares the reactor class as well as the precise signature of the method implementing the reaction body.

    \n

    The LF compiler generates a header file for each reactor that gets defined in LF code. The header file is named after the corresponding reactor and prefixed by the path to the LF file that defines the reactor. Consider the following example project structure:

    \n
    src/\n├ A.lf   // defines Foo\n└ sub/\n  └ B.lf // defines Bar
    \n

    In this case, the compiler will generate two header files A/Foo.hh and sub/B/Bar.hh, which would need to be included by an external implementation file.

    \n

    The precise method signature depends on the name of the reactor, the name of the reactions, and the precise triggers, sources, and effects that are defined in the LF code.\nThe return type is always void. A reaction foo in a reactor Bar will be named Bar::Inner::foo. Note that each reactor class in the C++ target defines an Inner class which contains all reactions as well as the parameters and state variables. This is done to deliberately restrict the scope of reaction bodies in order to avoid accidental violations of reactor semantics.\nAny declared triggers, sources or effects are given to the reaction method via method arguments. The precise arguments and their types depend on the LF code. If in doubt, please check the signature used in the generated header file under src-gen/<lf-file>, where <lf-file> corresponds to the LF file that you are compiling.

    \n
    \n
    \n

    The $target-language$ target does not currently support reaction declarations.

    \n
    ","headings":[{"value":"Reaction Declaration","depth":2},{"value":"Reaction Order","depth":2},{"value":"Overwriting Outputs","depth":2},{"value":"Reacting to Outputs of Contained Reactors","depth":2},{"value":"Triggering Contained Reactors","depth":2},{"value":"Startup, Shutdown, and Reset Reactions","depth":2},{"value":"Bodyless Reactions","depth":2},{"value":"Example","depth":3},{"value":"File Structure","depth":3},{"value":"The Generated Header Files","depth":3},{"value":"Linking Your C Code","depth":3},{"value":"Example","depth":3},{"value":"Header Files and Method Signatures","depth":3}],"frontmatter":{"permalink":"/docs/handbook/reactions","title":"Reactions","oneline":"Reactions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","permalink":"/docs/handbook/composing-reactors"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Methods","oneline":"Methods in Lingua Franca.","permalink":"/docs/handbook/methods"}}}},"pageContext":{"id":"1-reactions","slug":"/docs/handbook/reactions","repoPath":"/packages/documentation/copy/en/topics/Reactions.md","previousID":"834f9d0d-f7c6-5732-8c60-bad1954701f7","nextID":"c2fc2a0b-a389-5c48-9cb0-c079bf2e6034","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reactions","result":{"data":{"markdownRemark":{"id":"1432f795-b6dc-5a03-84b2-2f09c3c01d21","excerpt":"Reaction Declaration A reaction declaration has the following form: Each reaction declares its triggers, uses, and effects: The triggers field can be a comma…","html":"

    Reaction Declaration

    \n

    A reaction declaration has the following form:

    \n
      reaction [<name>] (<triggers>) [<uses>] [-> <effects>] [{= ... body ...=}]\n
    \n

    Each reaction declares its triggers, uses, and effects:

    \n
      \n
    • The triggers field can be a comma-separated list of input ports, output ports of contained reactors, timers, actions, or the special events $startup$, $shutdown$, and $reset$ (explained here). There must be at least one trigger for each reaction.
    • \n
    • The uses field, which is optional, specifies input ports (or output ports of contained reactors) that do not trigger execution of the reaction but may be read by the reaction.
    • \n
    • The effects field, which is also optional, is a comma-separated lists of output ports ports, input ports of contained reactors, or actions.
    • \n
    \n

    Reactions may optionally be named. The name is cosmetic and may serve as additional documentation. Note that reactions cannot be called like functions, even if they are named.

    \n

    The reaction’s behavior is defined by its body, which should be given in the target programming language. Note that the reaction body may only read from actions and ports that it has declared as triggers or uses, and it may only write to actions and ports that is has declared as an effect. The target code generators implement a scoping mechanism, such that only variables that are declared in the reaction signature are accessible in the reaction body.

    \n

    In some targets, the reaction body may be omitted and the body can be defined natively in the target language in an external file. See the section on Bodyless Reactions for details.

    \n

    Reaction Order

    \n

    A reactor may have multiple reactions, and more than one reaction may be enabled at any given tag. In Lingua Franca semantics, if two or more reactions of the same reactor are simultaneously enabled, then they will be invoked sequentially in the order in which they are declared. More strongly, the reactions of a reactor are mutually exclusive and are invoked in tag order primarily and declaration order secondarily. Consider the following example:

    \n

    $start(Alignment)$

    \n
    target C {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s: int = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self->s += 1;\n  =}\n  reaction(t2) {=\n    self->s -= 2;\n  =}\n  reaction(t4) {=\n    printf("s = %d\\n", self->s);\n  =}\n}\n
    \n
    target Cpp {\n  timeout: 3 s\n}\nmain reactor Alignment {\n  state s: int(0)\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  timer t4(400 ms, 400 ms)\n  reaction(t1) {=\n    s += 1;\n  =}\n  reaction(t2) {=\n    s -= 2;\n  =}\n  reaction(t4) {=\n    std::cout << "s = " << std::to_string(s) << std::endl;\n  =}\n}\n
    \n
    target Python {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self.s += 1\n  =}\n  reaction(t2) {=\n    self.s -= 2\n  =}\n  reaction(t4) {=\n    print(f"s = {self.s}")\n  =}\n}\n
    \n
    target TypeScript {\n  timeout: 3 s\n}\nmain reactor Alignment {\n  state s: number = 0\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  timer t4(400 ms, 400 ms)\n  reaction(t1) {=\n    s += 1\n  =}\n  reaction(t2) {=\n    s -= 2\n  =}\n  reaction(t4) {=\n    console.log(`s = ${s}`)\n  =}\n}\n
    \n
    target Rust {\n  timeout: 3 secs\n}\nmain reactor Alignment {\n  state s: u32 = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  timer t4(400 msec, 400 msec)\n  reaction(t1) {=\n    self.s += 1;\n  =}\n  reaction(t2) {=\n    self.s -= 2;\n  =}\n  reaction(t4) {=\n    println!("s = {}", self.s);\n  =}\n}\n
    \n

    $end(Alignment)$

    \n

    Every 100 ms, this increments the state variable s by 1, every 200 ms, it decrements s by 2, and every 400 ms, it prints the value of s. When these reactions align, they are invoked in declaration order, and, as a result, the printed value of s is always 0.

    \n

    Overwriting Outputs

    \n

    Just as the reactions of the Alignment reactor overwrite the state variable s, logically simultaneous reactions can overwrite outputs. Consider the following example:

    \n

    $start(Overwriting)$

    \n
    target C\nreactor Overwriting {\n  output y: int\n  state s: int = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self->s += 1;\n    lf_set(y, self->s);\n  =}\n  reaction(t2) -> y {=\n    self->s -= 2;\n    lf_set(y, self->s);\n  =}\n}\n
    \n
    target Cpp\nreactor Overwriting {\n  output y: int\n  state s: int(0)\n  timer t1(100 ms, 100 ms)\n  timer t2(200 ms, 200 ms)\n  reaction(t1) -> y {=\n    s += 1;\n    y.set(s);\n  =}\n  reaction(t2) -> y {=\n    s -= 2;\n    y.set(s);\n  =}\n}\n
    \n
    target Python\nreactor Overwriting {\n  output y\n  state s = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self.s += 1\n    y.set(self.s)\n  =}\n  reaction(t2) -> y {=\n    self.s -= 2\n    y.set(self.s)\n  =}\n}\n
    \n
    target TypeScript\nreactor Overwriting {\n  output y: number\n  state s: number = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    s += 1\n    y = s\n  =}\n  reaction(t2) -> y {=\n    s -= 2\n    y = s\n  =}\n}\n
    \n
    target Rust\nreactor Overwriting {\n  output y: u32\n  state s: u32 = 0\n  timer t1(100 msec, 100 msec)\n  timer t2(200 msec, 200 msec)\n  reaction(t1) -> y {=\n    self.s += 1;\n    ctx.set(y, self.s);\n  =}\n  reaction(t2) -> y {=\n    self.s -= 2;\n    ctx.set(y, self.s);\n  =}\n}\n
    \n

    $end(Overwriting)$

    \n

    Here, the reaction to t1 will set the output to 1 or 2, but every time it sets it to 2, the second reaction (to t2) will overwrite the output with the value 0. As a consequence, the outputs will be 1, 0, 1, 0, … deterministically.

    \n

    Reacting to Outputs of Contained Reactors

    \n

    A reaction may be triggered by the an input to the reactor, but also by an output of a contained reactor, as illustrated in the following example:

    \n

    $start(Contained)$

    \n
    target C\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if (s.y->value != 0 && s.y->value != 1) {\n      lf_print_error_and_exit("Outputs should only be 0 or 1!");\n    }\n  =}\n}\n
    \n
    target Cpp\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    auto is_correct = [](auto value){\n      return value == 0 || value == 1;\n    };\n    if (s.y.is_present() && !is_correct(*s.y.get())) {\n      std::cout << "Output shoudl only be 0 or 1!" << std::endl;\n    }\n  =}\n}\n
    \n
    target Python\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if s.y.value != 0 and s.y.value != 1:\n      sys.stderr.write("ERROR: Outputs should only be 0 or 1!\\n")\n      exit(1)\n  =}\n}\n
    \n
    target TypeScript\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    if (s.y != 0 && s.y != 1) {\n      util.requestErrorStop("Outputs should only be 0 or 1!")\n    }\n  =}\n}\n
    \n
    target Rust\nimport Overwriting from "Overwriting.lf"\nmain reactor {\n  s = new Overwriting()\n  reaction(s.y) {=\n    let value = ctx.get(s__y).unwrap();\n    if value != 0 && value != 1 {\n      eprintln!("Output schould only be 0 or 1!");\n      ctx.request_stop(Asap);\n    }\n  =}\n}\n
    \n

    $end(Contained)$

    \n\"Lingua\n

    This instantiates the above Overwriting reactor and monitors its outputs.

    \n

    Triggering Contained Reactors

    \n

    A reaction can set the input of a contained reactor, thereby triggering its reactions, as illustrated in the following example:

    \n

    $start(Triggering)$

    \n
    target C\nreactor Inside {\n  input x: int\n  reaction(x) {=\n    printf("Received %d\\n", x->value);=\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    lf_set(i.x, 42);\n  =}\n}\n
    \n
    target Cpp\nreactor Inside {\n  input x: int\n  reaction(x) {=\n    std::cout << "Received " << std::to_string(*x.get()) << std::endl;\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x.set(42);\n  =}\n}\n
    \n
    target Python\nreactor Inside {\n  input x\n  reaction(x) {=\n    print(f"Received {x.value}")\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x.set(42);\n  =}\n}\n
    \n
    target TypeScript\nreactor Inside {\n  input x: number\n  reaction(x) {=\n    console.log("Received ${x}");\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    i.x = 42\n  =}\n}\n
    \n
    target Rust\nreactor Inside {\n  input x: u32\n  reaction(x) {=\n    println!("Received {}", ctx.get(x).unwrap());\n  =}\n}\nmain reactor {\n  i = new Inside()\n  reaction(startup) -> i.x {=\n    ctx.set(i__x, 42);\n  =}\n}\n
    \n

    $end(Triggering)$

    \n

    The reaction to $startup$ declares the input port of the inside reactor as an effect and then sets it with value 42.\nThis will cause the inside reactor’s reaction to execute and print Received 42.

    \n

    Startup, Shutdown, and Reset Reactions

    \n

    Reactions may be triggered by the special events $startup$, $shutdown$, or $reset$.\nFor example,

    \n
      reaction(startup) {=\n    // ... Do something\n  =}\n
    \n

    A reaction to $startup$ is triggered at the very first tag when the program begins (or, if within a mode of a modal reactor, when the mode is first entered).\nThis reaction will be logically simultaneous with reactions to timers that have a zero offset.\nAs usual, for logically simultaneous reactions declared within the same reactor, the order in which they are invoked will be governed by the order in which they are declared.

    \n

    A reaction to $shutdown$ is invoked at program termination.\nSee the Termination section for details.

    \n
    \n

    Reactions to the $reset$ event are not supported in $target-language$ because modal reactors are not supported.

    \n
    \n
    \n

    A reaction to the $reset$ event is invoked if the reaction or reactor is within a mode of a modal reactor and the mode is entered via a reset transition.\nFor details, see the Modal Reactors section.

    \n
    \n

    Bodyless Reactions

    \n

    Sometimes, it is inconvenient to mix Lingua Franca code with target code. Rather than defining reactions (i.e., complete with inlined target code), it is also possible to just declare them and provide implementations in a separate file. The syntax of reaction declarations is the same as for reaction definitions, except they have no implementation. Reaction declarations can be thought of as function prototypes.

    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target C {\n  cmake-include: ["hello.cmake"],\n  files: ["hello.c"]\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The cmake-include target property is used to make the build system aware of an externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.c)\n
    \n

    The files target property is used to make accessible the file that has the implementation in `hello.c,\nwhich could look something like this:

    \n
    #include <stdio.h>\n#include "../include/HelloDecl/HelloDecl.h"\n\nvoid hello(hellodecl_self_t* self) {\n    printf("Hello declaration!\\n");\n}\n
    \n

    File Structure

    \n

    In the above example, the C file uses #include to import a file called HelloDecl.h. The HelloDecl.h file\nis generated from the Lingua Franca source file when the LF program is compiled. The file\nHelloDecl.h is named after the main reactor, which is called HelloDecl, and its parent\ndirectory, include/HelloDecl, is named after the file, HelloDecl.lf.

    \n

    In general, compiling a Lingua Franca program that uses reaction declarations will always generate a\ndirectory in the include directory for each file in the program. This directory will contain a\nheader file for each reactor that is defined in the file.

    \n

    As another example, if an LF program consists of files F1.lf and F2.lf, where F1.lf defines reactors\nA and B and F2.lf defines the reactor C and the main reactor F2, then the directory structure\nwill look something like this:

    \n
    include/\n├ F1/\n│ ├ A.h\n│ └ B.h\n└ F2/\n  ├ C.h\n  └ F2.h\nsrc/\n├ F1.lf  // defines A and B\n└ F2.lf  // defines C and F2\nsrc-gen/
    \n

    There is no particular location where you are required to place your C files or your CMake files.\nFor example, you may choose to place them in a directory called c that is a sibling of the src\ndirectory.

    \n

    The Generated Header Files

    \n

    The generated header files are necessary in order to separate your C code from your LF code because\nthey describe the signatures of the reaction functions that you must implement.

    \n

    In addition, they define structs that will be referenced by the reaction bodies. This includes the\nself struct of the reactor to which the header file corresponds, as well as structs for its ports,\nits actions, and the ports of its child reactors.

    \n

    As with preambles, programmer discipline is required to avoid breaking the deterministic semantics\nof Lingua Franca. In particular, although the information exposed in these header files allows\nregular C code to operate on ports and self structs, such information must not be saved in global or\nstatic variables.

    \n

    Linking Your C Code

    \n

    As with any Lingua Franca project that uses external C files, projects involving external reactions\nmust use the cmake-include target property to link those files into the main target.

    \n

    The file referenced by the cmake-include target property has the following syntax:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE <files>)\n
    \n

    where <files> is a list of the C files you need to link, with paths given relative to the project\nroot (the parent of the src directory).

    \n
    \n
    \n

    Example

    \n

    Consider the following program that has a single reaction named hello and is triggered at startup.\nIt has no implementation.

    \n
    target Cpp {\n  cmake-include: ["hello.cmake"],\n}\n\nmain reactor HelloDecl {\n\n  reaction hello(startup)\n\n}\n
    \n

    The behavior of the hello reaction is provided using a method definition in an external C++ file hello.cc.

    \n
    #include "HelloDecl/HelloDecl.hh" // include the code generated reactor class\n\n// define the reaction implementation\nvoid HelloDecl::Inner::hello([[maybe_unused]] const reactor::StartupTrigger& startup) {\n  std::cout << "Hello World." << std::endl;\n}\n
    \n

    Using the cmake-include target property, we can make the build system aware of this externally supplied implementation. The contents of hello.cmake is as follows:

    \n
    target_sources(${LF_MAIN_TARGET} PRIVATE hello.cc)\n
    \n

    Note that this mechanism can be used to add arbitrary additional resources such as additional headers and implementation files or 3rd party libraries to the compilation.

    \n

    Header Files and Method Signatures

    \n

    In order to provide an implementation of a reaction method, it is important to know the header file that declares the reactor class as well as the precise signature of the method implementing the reaction body.

    \n

    The LF compiler generates a header file for each reactor that gets defined in LF code. The header file is named after the corresponding reactor and prefixed by the path to the LF file that defines the reactor. Consider the following example project structure:

    \n
    src/\n├ A.lf   // defines Foo\n└ sub/\n  └ B.lf // defines Bar
    \n

    In this case, the compiler will generate two header files A/Foo.hh and sub/B/Bar.hh, which would need to be included by an external implementation file.

    \n

    The precise method signature depends on the name of the reactor, the name of the reactions, and the precise triggers, sources, and effects that are defined in the LF code.\nThe return type is always void. A reaction foo in a reactor Bar will be named Bar::Inner::foo. Note that each reactor class in the C++ target defines an Inner class which contains all reactions as well as the parameters and state variables. This is done to deliberately restrict the scope of reaction bodies in order to avoid accidental violations of reactor semantics.\nAny declared triggers, sources or effects are given to the reaction method via method arguments. The precise arguments and their types depend on the LF code. If in doubt, please check the signature used in the generated header file under src-gen/<lf-file>, where <lf-file> corresponds to the LF file that you are compiling.

    \n
    \n
    \n

    The $target-language$ target does not currently support reaction declarations.

    \n
    ","headings":[{"value":"Reaction Declaration","depth":2},{"value":"Reaction Order","depth":2},{"value":"Overwriting Outputs","depth":2},{"value":"Reacting to Outputs of Contained Reactors","depth":2},{"value":"Triggering Contained Reactors","depth":2},{"value":"Startup, Shutdown, and Reset Reactions","depth":2},{"value":"Bodyless Reactions","depth":2},{"value":"Example","depth":3},{"value":"File Structure","depth":3},{"value":"The Generated Header Files","depth":3},{"value":"Linking Your C Code","depth":3},{"value":"Example","depth":3},{"value":"Header Files and Method Signatures","depth":3}],"frontmatter":{"permalink":"/docs/handbook/reactions","title":"Reactions","oneline":"Reactions in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","permalink":"/docs/handbook/composing-reactors"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Methods","oneline":"Methods in Lingua Franca.","permalink":"/docs/handbook/methods"}}}},"pageContext":{"id":"1-reactions","slug":"/docs/handbook/reactions","repoPath":"/packages/documentation/copy/en/topics/Reactions.md","previousID":"834f9d0d-f7c6-5732-8c60-bad1954701f7","nextID":"c2fc2a0b-a389-5c48-9cb0-c079bf2e6034","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/reactors-on-patmos/page-data.json b/page-data/docs/handbook/reactors-on-patmos/page-data.json index 5c81f4254..a525d74aa 100644 --- a/page-data/docs/handbook/reactors-on-patmos/page-data.json +++ b/page-data/docs/handbook/reactors-on-patmos/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reactors-on-patmos","result":{"data":{"markdownRemark":{"id":"1b9eb6de-b3ca-54c1-bc79-972ea7a0fd22","excerpt":"Reactors on Patmos Reactors can be executed on Patmos, a bare-metal execution platform\nthat is optimized for time-predictable execution. Well written C programs…","html":"

    Reactors on Patmos

    \n

    Reactors can be executed on Patmos, a bare-metal execution platform\nthat is optimized for time-predictable execution. Well written C programs can be analyzed for their\nworst-case execution time (WCET).

    \n

    Compiling and Running Reactors

    \n

    Patmos can run in an FPGA, but there are also two\nsimulators available:

    \n
      \n
    1. pasim a software ISA simulator that is written in C++.
    2. \n
    3. patemu a cycle-accurate hardware emulator generated from the hardware description.
    4. \n
    \n

    To execute reactions on Patmos, the Patmos toolchain needs\nto be installed. The web page contains a quick start, detailed information including how to\nperform WCET analysis is available in the\nPatmos Reference Handbook.

    \n

    To execute the “hello world” reactor on Patmos use the LF compiler to generate the C code.\nCompile the reactor with the Patmos compiler (in src-gen):

    \n
    patmos-clang Minimal.c -o Minimal.elf
    \n

    The reactor can be executed on the SW simulator with:

    \n
    pasim Minimal.elf
    \n

    As Patmos is a bare metal runtime that has no notion of calendar time, its start time\nis considered the epoch and the following output will be observed:

    \n
    Start execution at time Thu Jan  1 00:00:00 1970\nplus 640000 nanoseconds.\nHello World.\nElapsed logical time (in nsec): 0\nElapsed physical time (in nsec): 3970000
    \n

    The reactor can also be executed on the hardware emulator of Patmos:

    \n
    patemu Minimal.elf
    \n

    This execution is considerably slower than the SW simulator, as the concrete hardware\nof Patmos is simulated cycle-accurate.

    \n

    Worst-Case Execution Time Analysis

    \n

    Following example is a code fragment from\nWcet.lf.

    \n
    reactor Work {\n    input in1: int;\n    input in2: int;\n    output out:int;\n    reaction(in1, in2) -> out {=\n    \tint ret;\n    \tif (in1 > 10) {\n    \t\tret = in2 * in1;\n    \t} else {\n    \t\tret = in2 + in1;\n    \t}\n        lf_set(out, ret);\n    =}\n}\n
    \n

    We want to perform WCET analysis of the single reaction of the Work reactor.\nThis reaction, depending on the input data, will either perform a multiplication,\nwhich is more expensive in Patmos, or an addition. The WCET analysis shall consider\nthe multiplication path as the worst-case path. To generate the information for\nWCET analysis by the compiler we have to compile the application as follows:

    \n
    patmos-clang -O2 -mserialize=wcet.pml Wcet.c
    \n

    We investigate the C source code Wcet.c and find that the reaction we\nare interested is named reaction_function1. Therefore, we invoke WCET analysis\nas follows:

    \n
    platin wcet -i wcet.pml -b a.out -e reaction_function1 --report
    \n

    This results in following report:

    \n
    ...\n[platin] INFO: Finished run WCET analysis (platin)          in 62 ms\n[platin] INFO: best WCET bound: 242 cycles\n---\n- analysis-entry: reaction_function1\n  source: platin\n  cycles: 242\n...
    \n

    The analysis gives the WCET of 242 clock cycles for the reaction,\nwhich includes clock cycles for data cache misses.\nFurther details on the WCET analysis\ntool platin and e.g., how to annotate loop bounds can be found in the\nPatmos Reference Handbook.

    \n

    Note, that the WCET analysis of a reaction does only include the code of the\nreaction function, not the cache miss cost of calling the function from\nthe scheduler or the cache miss cost when returning to the scheduler.

    ","headings":[{"value":"Reactors on Patmos","depth":2},{"value":"Compiling and Running Reactors","depth":3},{"value":"Worst-Case Execution Time Analysis","depth":3}],"frontmatter":{"permalink":"/docs/handbook/reactors-on-patmos","title":"Reactors on Patmos","oneline":"Reactors on Patmos (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/reactors-on-patmos","repoPath":"/packages/documentation/copy/en/preliminary/Reactors on Patmos.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/reactors-on-patmos","result":{"data":{"markdownRemark":{"id":"1b9eb6de-b3ca-54c1-bc79-972ea7a0fd22","excerpt":"Reactors on Patmos Reactors can be executed on Patmos, a bare-metal execution platform\nthat is optimized for time-predictable execution. Well written C programs…","html":"

    Reactors on Patmos

    \n

    Reactors can be executed on Patmos, a bare-metal execution platform\nthat is optimized for time-predictable execution. Well written C programs can be analyzed for their\nworst-case execution time (WCET).

    \n

    Compiling and Running Reactors

    \n

    Patmos can run in an FPGA, but there are also two\nsimulators available:

    \n
      \n
    1. pasim a software ISA simulator that is written in C++.
    2. \n
    3. patemu a cycle-accurate hardware emulator generated from the hardware description.
    4. \n
    \n

    To execute reactions on Patmos, the Patmos toolchain needs\nto be installed. The web page contains a quick start, detailed information including how to\nperform WCET analysis is available in the\nPatmos Reference Handbook.

    \n

    To execute the “hello world” reactor on Patmos use the LF compiler to generate the C code.\nCompile the reactor with the Patmos compiler (in src-gen):

    \n
    patmos-clang Minimal.c -o Minimal.elf
    \n

    The reactor can be executed on the SW simulator with:

    \n
    pasim Minimal.elf
    \n

    As Patmos is a bare metal runtime that has no notion of calendar time, its start time\nis considered the epoch and the following output will be observed:

    \n
    Start execution at time Thu Jan  1 00:00:00 1970\nplus 640000 nanoseconds.\nHello World.\nElapsed logical time (in nsec): 0\nElapsed physical time (in nsec): 3970000
    \n

    The reactor can also be executed on the hardware emulator of Patmos:

    \n
    patemu Minimal.elf
    \n

    This execution is considerably slower than the SW simulator, as the concrete hardware\nof Patmos is simulated cycle-accurate.

    \n

    Worst-Case Execution Time Analysis

    \n

    Following example is a code fragment from\nWcet.lf.

    \n
    reactor Work {\n    input in1: int;\n    input in2: int;\n    output out:int;\n    reaction(in1, in2) -> out {=\n    \tint ret;\n    \tif (in1 > 10) {\n    \t\tret = in2 * in1;\n    \t} else {\n    \t\tret = in2 + in1;\n    \t}\n        lf_set(out, ret);\n    =}\n}\n
    \n

    We want to perform WCET analysis of the single reaction of the Work reactor.\nThis reaction, depending on the input data, will either perform a multiplication,\nwhich is more expensive in Patmos, or an addition. The WCET analysis shall consider\nthe multiplication path as the worst-case path. To generate the information for\nWCET analysis by the compiler we have to compile the application as follows:

    \n
    patmos-clang -O2 -mserialize=wcet.pml Wcet.c
    \n

    We investigate the C source code Wcet.c and find that the reaction we\nare interested is named reaction_function1. Therefore, we invoke WCET analysis\nas follows:

    \n
    platin wcet -i wcet.pml -b a.out -e reaction_function1 --report
    \n

    This results in following report:

    \n
    ...\n[platin] INFO: Finished run WCET analysis (platin)          in 62 ms\n[platin] INFO: best WCET bound: 242 cycles\n---\n- analysis-entry: reaction_function1\n  source: platin\n  cycles: 242\n...
    \n

    The analysis gives the WCET of 242 clock cycles for the reaction,\nwhich includes clock cycles for data cache misses.\nFurther details on the WCET analysis\ntool platin and e.g., how to annotate loop bounds can be found in the\nPatmos Reference Handbook.

    \n

    Note, that the WCET analysis of a reaction does only include the code of the\nreaction function, not the cache miss cost of calling the function from\nthe scheduler or the cache miss cost when returning to the scheduler.

    ","headings":[{"value":"Reactors on Patmos","depth":2},{"value":"Compiling and Running Reactors","depth":3},{"value":"Worst-Case Execution Time Analysis","depth":3}],"frontmatter":{"permalink":"/docs/handbook/reactors-on-patmos","title":"Reactors on Patmos","oneline":"Reactors on Patmos (preliminary)","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/reactors-on-patmos","repoPath":"/packages/documentation/copy/en/preliminary/Reactors on Patmos.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/regression-tests/page-data.json b/page-data/docs/handbook/regression-tests/page-data.json index 1c7964de6..b518beb2d 100644 --- a/page-data/docs/handbook/regression-tests/page-data.json +++ b/page-data/docs/handbook/regression-tests/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/regression-tests","result":{"data":{"markdownRemark":{"id":"96f63950-6ca6-530f-9c7a-ed0605046575","excerpt":"Lingua Franca comes with an extensive set of regression tests that are executed on various platforms automatically whenever an update is pushed to the LF…","html":"

    Lingua Franca comes with an extensive set of regression tests that are executed on various platforms automatically whenever an update is pushed to the LF repository. There are two categories of tests:

    \n
      \n
    • Unit tests are Java or Kotlin methods in our code base that are labeled with the @Test directive. These tests check individual functions of the code generation infrastructure. These are located in the src/test directory of each subroject within the repository.
    • \n
    • Integration tests are complete Lingua Franca programs that are compiled and executed automatically. A test passes if it successfully compiles and runs to completion with normal termination (return code 0). These tests are located in the test directory at the root of the LF repo, with one subdirectory per target language.
    • \n
    \n

    Their implementation can be found in the core/src/integrationTest directory.\nThe integration tests are also executed through JUnit using methods with @Test directives, but they are executed separately.

    \n

    Running the Tests From the Command Line

    \n

    To run all unit tests, simply run ./gradlew test. Note that also the normal build tasks ./gradlew build runs all the unit tests.

    \n

    The integration tests can be run using the integrationTest task. However, typically it is not desired to run all tests for all targets locally as it will need the right target tooling and will take a long time.

    \n

    To run only the integration tests for one target, we provide the targetTest gradle task. For instance, you can use the following command to run all Rust tests:

    \n
    ./gradlew targetTest -Ptarget=Rust
    \n

    You can specify any valid target. If you run the task without specifying the target property ./gradlew tagetTest it will produce an error message and list all available targets.

    \n

    The targetTest task is essentially a convenient shortcut for the following:

    \n
    ./gradew core:integrationTest --test org.lflang.tests.runtime.<target>Test.*
    \n

    If you prefer have more control over which tests are executed, you can also use this more verbose version.

    \n

    It is also possible to run a subset of the tests. For example, the C tests are organized into the following categories:

    \n
      \n
    • generic tests are .lf files located in $LF/test/C/src.
    • \n
    • concurrent tests are .lf files located in $LF/test/C/src/concurrent.
    • \n
    • federated tests are .lf files located in $LF/test/C/src/federated.
    • \n
    • multiport tests are .lf files located in $LF/test/C/src/multiport.
    • \n
    \n

    To invoke only the C tests in the concurrent category, for example, run this:

    \n
    ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CTest.runConcurrentTests
    \n

    Sometimes it is convenient to only run a single specific test case. This can be done with the singleTest task. For instance:

    \n
    ./gradlew singleTest -DsingleTest=test/C/src/Minimal.lf
    \n

    Reporting Bugs

    \n

    If you encounter a bug or add some enhancement to Lingua Franca, then you should create a regression test either as a system test or a unit test and issue a pull request. System tests are particularly easy to create since they are simply Lingua Franca programs that either compile and execute successfully (the test passes) or fail either to compile or execute.

    \n

    Testing Architecture

    \n

    System tests can be put in any subdirectory of $LF/test or $LF/example.\nAny .lf file within these directories will be treated as a system test unless they are within a directory named failing, in which case they will be ignored.\nThe tests are automatically indexed by our JUnit-based test infrastructure, which is located in the package core/src/integrationTest. Each target has its own class in the runtime package, with a number of test methods that correspond to particular test categories, such as generic, concurrent, federated, etc. A test can be associated with a particular category by placing it in a directory that matches its name. For instance, we can create a test (e.g., Foo.lf) in test/C/src/concurrent, which will then get indexed under the target C in the category concurrent. Files placed directly in test/C/src will be considered generic C tests, and a file in a directory concurrent/federated will be indexed as federated (corresponding to the nearest containing directory).

    \n

    Caution: adding a new category requires updating an enum in TestRegistry.java and adding a @Test-labeled method to TestBase.

    \n

    Known Failures

    \n

    Sometimes it is useful to retain tests that have a known failure that should be addressed at a later point. Such tests can simply be put in a directory called failing, which will tell our test indexing code to exclude it.

    \n

    Test Output

    \n

    Tests are grouped by target and category. It is also reported when, for a given category, there are other targets that feature tests that are missing for the target under test. Tests that either do not have a main reactor or are marked as known failures are reported as “ignored.” For all the tests that were successfully indexed, it is reported how many passed. For each failing test, diagnostics are reported that should help explain the failure. Here is some sample output for Ctest.runConcurrentTests, which runs tests categorized as concurrent for the C target:

    \n
    CTest > runConcurrentTests() STANDARD_OUT\n    ==============================================================================\n    Target: C\n    Description: Run concurrent tests.\n    ==============================================================================\n\n    ==============================================================================\n    Category: CONCURRENT\n    ==============================================================================\n    ------------------------------------------------------------------------------\n    Ignored: 0\n    ------------------------------------------------------------------------------\n\n    ------------------------------------------------------------------------------\n    Covered: 29/33\n    ------------------------------------------------------------------------------\n    Missing: src/concurrent/BankToBank.lf\n    Missing: src/concurrent/BankToBankMultiport.lf\n    Missing: src/concurrent/BankToBankMultiportAfter.lf\n    Missing: src/concurrent/BankToMultiport.lf\n\n    ------------------------------------------------------------------------------\n    Passing: 29/29\n    ------------------------------------------------------------------------------\n
    \n

    Code Coverage

    \n

    Code coverage is automatically recorded when running tests.\nA combined report for each subproject can be created by running ./gradlew jacocoTestReport.\nFor the core subproject, the html report will be located in build/reports/html/index.html.\nNote that this report will only reflect the coverage of the test that have actually executed.

    \n

    Continuous Integration

    \n

    Each push or pull request will trigger all tests to be run on GitHub Actions. It’s configuration can be found here.

    ","headings":[{"value":"Running the Tests From the Command Line","depth":2},{"value":"Reporting Bugs","depth":2},{"value":"Testing Architecture","depth":2},{"value":"Known Failures","depth":3},{"value":"Test Output","depth":3},{"value":"Code Coverage","depth":2},{"value":"Continuous Integration","depth":2}],"frontmatter":{"permalink":"/docs/handbook/regression-tests","title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","permalink":"/docs/handbook/intellij"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Running Benchmarks","oneline":"Running Benchmarks.","permalink":"/docs/handbook/running-benchmarks"}}}},"pageContext":{"id":"5-regression-tests","slug":"/docs/handbook/regression-tests","repoPath":"/packages/documentation/copy/en/developer/Regression Tests.md","previousID":"1d9f0442-2300-5615-9c04-6ee5f2c33793","nextID":"8d78ab4e-cebd-5116-bbe9-871de58f9aeb","lang":"en","modifiedTime":"2023-10-19T14:14:27.297Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/regression-tests","result":{"data":{"markdownRemark":{"id":"96f63950-6ca6-530f-9c7a-ed0605046575","excerpt":"Lingua Franca comes with an extensive set of regression tests that are executed on various platforms automatically whenever an update is pushed to the LF…","html":"

    Lingua Franca comes with an extensive set of regression tests that are executed on various platforms automatically whenever an update is pushed to the LF repository. There are two categories of tests:

    \n
      \n
    • Unit tests are Java or Kotlin methods in our code base that are labeled with the @Test directive. These tests check individual functions of the code generation infrastructure. These are located in the src/test directory of each subroject within the repository.
    • \n
    • Integration tests are complete Lingua Franca programs that are compiled and executed automatically. A test passes if it successfully compiles and runs to completion with normal termination (return code 0). These tests are located in the test directory at the root of the LF repo, with one subdirectory per target language.
    • \n
    \n

    Their implementation can be found in the core/src/integrationTest directory.\nThe integration tests are also executed through JUnit using methods with @Test directives, but they are executed separately.

    \n

    Running the Tests From the Command Line

    \n

    To run all unit tests, simply run ./gradlew test. Note that also the normal build tasks ./gradlew build runs all the unit tests.

    \n

    The integration tests can be run using the integrationTest task. However, typically it is not desired to run all tests for all targets locally as it will need the right target tooling and will take a long time.

    \n

    To run only the integration tests for one target, we provide the targetTest gradle task. For instance, you can use the following command to run all Rust tests:

    \n
    ./gradlew targetTest -Ptarget=Rust
    \n

    You can specify any valid target. If you run the task without specifying the target property ./gradlew tagetTest it will produce an error message and list all available targets.

    \n

    The targetTest task is essentially a convenient shortcut for the following:

    \n
    ./gradew core:integrationTest --test org.lflang.tests.runtime.<target>Test.*
    \n

    If you prefer have more control over which tests are executed, you can also use this more verbose version.

    \n

    It is also possible to run a subset of the tests. For example, the C tests are organized into the following categories:

    \n
      \n
    • generic tests are .lf files located in $LF/test/C/src.
    • \n
    • concurrent tests are .lf files located in $LF/test/C/src/concurrent.
    • \n
    • federated tests are .lf files located in $LF/test/C/src/federated.
    • \n
    • multiport tests are .lf files located in $LF/test/C/src/multiport.
    • \n
    \n

    To invoke only the C tests in the concurrent category, for example, run this:

    \n
    ./gradlew core:integrationTest --tests org.lflang.tests.runtime.CTest.runConcurrentTests
    \n

    Sometimes it is convenient to only run a single specific test case. This can be done with the singleTest task. For instance:

    \n
    ./gradlew singleTest -DsingleTest=test/C/src/Minimal.lf
    \n

    Reporting Bugs

    \n

    If you encounter a bug or add some enhancement to Lingua Franca, then you should create a regression test either as a system test or a unit test and issue a pull request. System tests are particularly easy to create since they are simply Lingua Franca programs that either compile and execute successfully (the test passes) or fail either to compile or execute.

    \n

    Testing Architecture

    \n

    System tests can be put in any subdirectory of $LF/test or $LF/example.\nAny .lf file within these directories will be treated as a system test unless they are within a directory named failing, in which case they will be ignored.\nThe tests are automatically indexed by our JUnit-based test infrastructure, which is located in the package core/src/integrationTest. Each target has its own class in the runtime package, with a number of test methods that correspond to particular test categories, such as generic, concurrent, federated, etc. A test can be associated with a particular category by placing it in a directory that matches its name. For instance, we can create a test (e.g., Foo.lf) in test/C/src/concurrent, which will then get indexed under the target C in the category concurrent. Files placed directly in test/C/src will be considered generic C tests, and a file in a directory concurrent/federated will be indexed as federated (corresponding to the nearest containing directory).

    \n

    Caution: adding a new category requires updating an enum in TestRegistry.java and adding a @Test-labeled method to TestBase.

    \n

    Known Failures

    \n

    Sometimes it is useful to retain tests that have a known failure that should be addressed at a later point. Such tests can simply be put in a directory called failing, which will tell our test indexing code to exclude it.

    \n

    Test Output

    \n

    Tests are grouped by target and category. It is also reported when, for a given category, there are other targets that feature tests that are missing for the target under test. Tests that either do not have a main reactor or are marked as known failures are reported as “ignored.” For all the tests that were successfully indexed, it is reported how many passed. For each failing test, diagnostics are reported that should help explain the failure. Here is some sample output for Ctest.runConcurrentTests, which runs tests categorized as concurrent for the C target:

    \n
    CTest > runConcurrentTests() STANDARD_OUT\n    ==============================================================================\n    Target: C\n    Description: Run concurrent tests.\n    ==============================================================================\n\n    ==============================================================================\n    Category: CONCURRENT\n    ==============================================================================\n    ------------------------------------------------------------------------------\n    Ignored: 0\n    ------------------------------------------------------------------------------\n\n    ------------------------------------------------------------------------------\n    Covered: 29/33\n    ------------------------------------------------------------------------------\n    Missing: src/concurrent/BankToBank.lf\n    Missing: src/concurrent/BankToBankMultiport.lf\n    Missing: src/concurrent/BankToBankMultiportAfter.lf\n    Missing: src/concurrent/BankToMultiport.lf\n\n    ------------------------------------------------------------------------------\n    Passing: 29/29\n    ------------------------------------------------------------------------------\n
    \n

    Code Coverage

    \n

    Code coverage is automatically recorded when running tests.\nA combined report for each subproject can be created by running ./gradlew jacocoTestReport.\nFor the core subproject, the html report will be located in build/reports/html/index.html.\nNote that this report will only reflect the coverage of the test that have actually executed.

    \n

    Continuous Integration

    \n

    Each push or pull request will trigger all tests to be run on GitHub Actions. It’s configuration can be found here.

    ","headings":[{"value":"Running the Tests From the Command Line","depth":2},{"value":"Reporting Bugs","depth":2},{"value":"Testing Architecture","depth":2},{"value":"Known Failures","depth":3},{"value":"Test Output","depth":3},{"value":"Code Coverage","depth":2},{"value":"Continuous Integration","depth":2}],"frontmatter":{"permalink":"/docs/handbook/regression-tests","title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Developer IntelliJ Setup","oneline":"Developer IntelliJ Setup.","permalink":"/docs/handbook/intellij"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Running Benchmarks","oneline":"Running Benchmarks.","permalink":"/docs/handbook/running-benchmarks"}}}},"pageContext":{"id":"5-regression-tests","slug":"/docs/handbook/regression-tests","repoPath":"/packages/documentation/copy/en/developer/Regression Tests.md","previousID":"1d9f0442-2300-5615-9c04-6ee5f2c33793","nextID":"8d78ab4e-cebd-5116-bbe9-871de58f9aeb","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/related-work/page-data.json b/page-data/docs/handbook/related-work/page-data.json index 0afd5e439..d6e96c3e4 100644 --- a/page-data/docs/handbook/related-work/page-data.json +++ b/page-data/docs/handbook/related-work/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/related-work","result":{"data":{"markdownRemark":{"id":"9b70f0f3-50a7-5c9f-a0fd-7a858bff6a59","excerpt":"Lingua Franca is focused more on using the best ideas than on being innovative.\nHere, we list most closely related work first, then other work with which it may…","html":"

    Lingua Franca is focused more on using the best ideas than on being innovative.\nHere, we list most closely related work first, then other work with which it may be useful to contrast.

    \n

    Software Frameworks

    \n
      \n
    • \n

      Rubus.

      \n
    • \n
    • \n

      Akka framework for distributed Fog computing.

      \n
    • \n
    • \n

      Accessors, from Berkeley, a JavaScript-based framework for IoT: This framework is the most direct inspiration for Lingua Franca. The idea behind accessors is to componentize IoT resources by encapsulating them in actors. As such, their interactions can be coordinated under a discrete event semantics paper.

      \n
    • \n
    • \n

      Rebecca.

      \n
    • \n
    • \n

      The Kiel Integrated Environment for Layout Eclipse Rich Client (KIELER) is a graphical environment for programming using SCCharts (see the 2014 PLDI paper).

      \n
    • \n
    • \n

      RTMAPS: From Intempora. It has a graphical syntax in a UI and advertises “data is acquired asynchronously and each data sample is captured along with its time stamp at its own pace.” You can build your own blocks in C++ or Python. It does, however, look like its not deterministic.

      \n
    • \n
    \n

    Usage of the Term Reactor

    \n\n

    Other Pointers

    \n
      \n
    • Reactive Manifesto: Version 2.0, Published in 2014, this position paper defines Reactive Systems as those that are Responsive, Resilient, Elastic and Message Driven.
    • \n
    \n

    Academic Projects

    \n
      \n
    • \n

      I/O Automata, from MIT, is a formalism that could be used to model the semantics of Lingua Franca. Timed I/O Automata FIXME: link extend I/O Automata with temporal semantics. They share with LF the notion of reactions to input messages and internal events that change the state of an actor and produce outputs. The behavior of a component is given as a state machine.

      \n
    • \n
    • \n

      FIXME Hewitt actors.

      \n
    • \n
    • \n

      SyncCharts: By Charles André. See the 1996 technical report.

      \n
    • \n
    • \n

      ReactiveML: Website

      \n
    • \n
    \n

    Contrasting Work

    \n
      \n
    • \n

      CAPH (a recursive acronym for CAPH Ain’t plain HDL), a hardware description language from CNRS, is a fine-grained dataflow language for compiling into FPGAs. The language has no temporal semantics, and although it has a notion of firing rules, it is not clear which of the many variants of dataflow is realized nor whether the MoC is deterministic. The paper does not cite any of the prior work on dataflow MoCs.

      \n
    • \n
    • \n

      Robot Operating System (ROS), an open-source project originally from Willow Garage: ROS provides a publish-and-subscribe server for interaction between components. Version 1 has no timing properties at all. Version 2 has some timing properties such as priorities, but it makes no effort to be deterministic.

      \n
    • \n
    • \n

      RADLER framework from SRI, which is based on a publish-and-subscribe architecture similar to ROS. It introduces some timing constructs such as periodic execution and scheduling constraints, but it makes no effort to be deterministic.

      \n
    • \n
    ","headings":[{"value":"Software Frameworks","depth":2},{"value":"Usage of the Term Reactor","depth":3},{"value":"Other Pointers","depth":3},{"value":"Academic Projects","depth":2},{"value":"Contrasting Work","depth":2}],"frontmatter":{"permalink":"/docs/handbook/related-work","title":"Related Work","oneline":"Related Work","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/related-work","repoPath":"/packages/documentation/copy/en/less-developed/Related Work.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/related-work","result":{"data":{"markdownRemark":{"id":"9b70f0f3-50a7-5c9f-a0fd-7a858bff6a59","excerpt":"Lingua Franca is focused more on using the best ideas than on being innovative.\nHere, we list most closely related work first, then other work with which it may…","html":"

    Lingua Franca is focused more on using the best ideas than on being innovative.\nHere, we list most closely related work first, then other work with which it may be useful to contrast.

    \n

    Software Frameworks

    \n
      \n
    • \n

      Rubus.

      \n
    • \n
    • \n

      Akka framework for distributed Fog computing.

      \n
    • \n
    • \n

      Accessors, from Berkeley, a JavaScript-based framework for IoT: This framework is the most direct inspiration for Lingua Franca. The idea behind accessors is to componentize IoT resources by encapsulating them in actors. As such, their interactions can be coordinated under a discrete event semantics paper.

      \n
    • \n
    • \n

      Rebecca.

      \n
    • \n
    • \n

      The Kiel Integrated Environment for Layout Eclipse Rich Client (KIELER) is a graphical environment for programming using SCCharts (see the 2014 PLDI paper).

      \n
    • \n
    • \n

      RTMAPS: From Intempora. It has a graphical syntax in a UI and advertises “data is acquired asynchronously and each data sample is captured along with its time stamp at its own pace.” You can build your own blocks in C++ or Python. It does, however, look like its not deterministic.

      \n
    • \n
    \n

    Usage of the Term Reactor

    \n\n

    Other Pointers

    \n
      \n
    • Reactive Manifesto: Version 2.0, Published in 2014, this position paper defines Reactive Systems as those that are Responsive, Resilient, Elastic and Message Driven.
    • \n
    \n

    Academic Projects

    \n
      \n
    • \n

      I/O Automata, from MIT, is a formalism that could be used to model the semantics of Lingua Franca. Timed I/O Automata FIXME: link extend I/O Automata with temporal semantics. They share with LF the notion of reactions to input messages and internal events that change the state of an actor and produce outputs. The behavior of a component is given as a state machine.

      \n
    • \n
    • \n

      FIXME Hewitt actors.

      \n
    • \n
    • \n

      SyncCharts: By Charles André. See the 1996 technical report.

      \n
    • \n
    • \n

      ReactiveML: Website

      \n
    • \n
    \n

    Contrasting Work

    \n
      \n
    • \n

      CAPH (a recursive acronym for CAPH Ain’t plain HDL), a hardware description language from CNRS, is a fine-grained dataflow language for compiling into FPGAs. The language has no temporal semantics, and although it has a notion of firing rules, it is not clear which of the many variants of dataflow is realized nor whether the MoC is deterministic. The paper does not cite any of the prior work on dataflow MoCs.

      \n
    • \n
    • \n

      Robot Operating System (ROS), an open-source project originally from Willow Garage: ROS provides a publish-and-subscribe server for interaction between components. Version 1 has no timing properties at all. Version 2 has some timing properties such as priorities, but it makes no effort to be deterministic.

      \n
    • \n
    • \n

      RADLER framework from SRI, which is based on a publish-and-subscribe architecture similar to ROS. It introduces some timing constructs such as periodic execution and scheduling constraints, but it makes no effort to be deterministic.

      \n
    • \n
    ","headings":[{"value":"Software Frameworks","depth":2},{"value":"Usage of the Term Reactor","depth":3},{"value":"Other Pointers","depth":3},{"value":"Academic Projects","depth":2},{"value":"Contrasting Work","depth":2}],"frontmatter":{"permalink":"/docs/handbook/related-work","title":"Related Work","oneline":"Related Work","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/related-work","repoPath":"/packages/documentation/copy/en/less-developed/Related Work.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/running-benchmarks/page-data.json b/page-data/docs/handbook/running-benchmarks/page-data.json index 941f6a105..5b9290a5a 100644 --- a/page-data/docs/handbook/running-benchmarks/page-data.json +++ b/page-data/docs/handbook/running-benchmarks/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/running-benchmarks","result":{"data":{"markdownRemark":{"id":"ac793a6f-ced3-5dd9-972e-a6891620a738","excerpt":"Running Benchmarks The LF repository contains a series of benchmarks in the benchmark directory. There is also a flexible benchmark runner that automates the…","html":"

    Running Benchmarks

    \n

    The LF repository contains a series of benchmarks in the benchmark directory. There is also a flexible benchmark runner that automates the process of running benchmarks for various settings and collecting results from those benchmarks. It is located in benchmark/runner.\nThe runner is written in python and is based on hydra, a tool for dynamically creating hierarchical configurations by composition

    \n

    Prerequisites

    \n

    Install Python dependencies

    \n

    The benchmark runner is written in Python and requires a working Python3 installation. It also requires a few python packages to be installed. Namely, hydra-core, cogapp and pandas.

    \n

    It is recommended to install the dependencies and execute the benchmark runner in a virtual environment. For instance, this can be done with virtualenv:

    \n
    virtualenv ~/virtualenvs/lfrunner -p python3\nsource ~/virtualenvs/lfrunner/bin/activate\n
    \n

    Then the dependencies can be installed by running:

    \n
    pip install -r benchmark/runner/requirements.txt\n
    \n

    Compile lfc

    \n

    For running LF benchmarks, the command-line compiler lfc needs to be built. Simply run

    \n
    bin/build-lfc\n
    \n

    in the root directory of the LF repository.

    \n

    Also, the environment variable LF_PATH needs to be set and point to the location of the LF repository. This needs to be an absolute path.

    \n
    export LF_PATH=/path/to/lf\n
    \n

    Setup Savina

    \n

    Currently all of our benchmarks are ported from the Savina actor benchmark suite. In order to compare our LF implementations with actor based implementation, the Savina benchmark suite needs to be downloaded and compiled. Note that we require a modified version of the Savina suite, that adds support for specifying the number of worker threads and that includes CAF implementations of most benchmarks.

    \n

    To download and build Savina, run the following commands:

    \n
    git clone https://github.com/lf-lang/savina.git\ncd savina\nmvn install\n
    \n

    Building Savina requires a Java 8 JDK. Depending on the local setup, JAVA_HOME might need to be adjusted before running mvn in order to point to the correct JDK.

    \n
    export JAVA_HOME=/path/to/jdk8\n
    \n

    Before invoking the benchmark runner, the environment variable SAVINA_PATH needs to be set and point to the location of the Savina repository using an absolute path.

    \n
    export SAVINA_PATH=/path/to/savina\n
    \n

    CAF

    \n

    To further build the CAF benchmarks, CAF 0.16.5 needs to be downloaded, compiled and installed first:

    \n
    git clone --branch "0.16.5" git@github.com:actor-framework/actor-framework.git\nmkdir actor-framework/build && cd actor-framework/build\ncmake -DCMAKE_INSTALL_PREFIX=<preferred/install/location> ..\nmake install\n
    \n

    Then, from within the Savina directory, the CAF benchmarks can be build:

    \n
    cmake -DCAF_ROOT_DIR=<path/to/caf/install/location> ..\nmake\n
    \n

    The CAF benchmarks are used in these two publications:

    \n\n

    Running a benchmark

    \n

    A benchmark can simply be run by specifying a benchmark and a target. For instance

    \n
    cd benchmark/runner\n./run_benchmark.py benchmark=savina_micro_pingpong target=lf-c\n
    \n

    runs the Ping Pong benchmark from the Savina suite using the C-target of LF. Currently, supported targets are lf-c, lf-cpp, akka, and caf where akka corresponds to the Akka implementation in the original Savina suite and caf corresponds to a implementation using the C++ Actor Framework .

    \n

    The benchmarks can also be configured. The threads and iterations parameters apply to every benchmark and specify the number of worker threads as well as how many times the benchmark should be run. Most benchmarks allow additional parameters. For instance, the Ping Pong benchmark sends a configurable number of pings that be set via the benchmark.params.messages configuration key. Running the Akka version of the Ping Pong benchmark for 1000 messages, 1 thread and 12 iterations could be done like this:

    \n
    ./run_benchmark.py benchmark=savina_micro_pingpong target=akka threads=1 iterations=12 benchmark.params.messages=1000\n
    \n

    Each benchmark run produces an output directory in the scheme outputs/<date>/<time>/ (e.g. outputs/2020-12-17/16-46-16/). This directory contains a files results.csv which contains the measured execution time for each iteration and all the parameters used for running this particular benchmark. The CSV file contains precisely one row per iteration.

    \n

    Running a series of benchmarks (multirun)

    \n

    The runner also allows to automatically run a single benchmark or a series of benchmarks with a range of settings. The multirun feature is simply used by the -m switch. For instance:

    \n
    ./run_benchmark.py -m benchmark=savina_micro_pingpong target="glob(*)" threads=1,2,4 iterations=12 benchmark.params.messages="range(1000000,10000000,1000000)"\n
    \n

    runs the Ping Pong benchmark for all targets using 1, 2 and 4 threads and for a number of messages ranging from 1M to 10M (in 1M steps).

    \n

    This mechanism can also be used to run multiple benchmarks. For instance,

    \n
    ./run_benchmark.py -m benchmark="glob(*)" target="glob(*)" threads=4 iterations=12\n
    \n

    runs all benchmarks for all targets using 4 threads and 12 iterations.

    \n

    The results for a multirun are written to a directory in the scheme multirun/<date>/<time>/<n> (e.g. multirun/2020-12-17/17-11-03/0/) where <n> denotes the particular run. Each of the <n> subdirectories contains a results.csv for this particular run.

    \n

    Collecting results from multirun

    \n

    A second script called collect_results.py provides a convenient way for collecting results from a multirun and merging them into a single CSV file. Simply running

    \n
    ./collect_results.py multirun/<date>/<time>/ out.csv\n
    \n

    collects all results from the particular multirun and stores the merged data structure in out.csv. collect_results.py not only merges the results, but it also calculates minimum, maximum and median execution time for each individual run. The resulting CSV does not contain the measured values of individual iterations anymore and only contains a single row per run. This behavior can be disabled with the --raw command line flag. With the flag set, the results from all runs are merged as say are and the resulting file contains rows for all individual runs, but no minimum, maximum and median values.

    \n

    How it works

    \n

    The benchmark runner itself is actually relatively simple. Most of the complexity is dealt with by hydra. Hydra is a complex and convenient tool for handling configurations. These configurations can be merged from different sources and be overridden via command line arguments as you have seen above. The actual benchmark runner receives the configuration represented as nested dictionaries from hydra. It then executes the benchmarks precisely as instructed by the configuration.

    \n

    The configuration is split into two big parts: the benchmark configuration and the target configuration. The benchmark configuration describes a particular benchmark instance. This is described in more detail in the next section. The target configuration specifies how to run a benchmark for a specific target (e.g. akka, lf-c, lf-cpp). This is not intended to be changed by the user and therefore isn’t explained in detail here. Essentially a benchmark run is split into 5 steps as is outlined in the following. The target configuration precisely specifies what needs to be done in each step

    \n
      \n
    1. copy The command used to copy relevant source files to a temporary directory.
    2. \n
    3. gen The command used to generate a configured LF file. This is intended to apply a code generation tool like cog to the source code in order to make benchmarks parameterized.
    4. \n
    5. compile The command used to compile the benchmark.
    6. \n
    7. run The command used to generate the benchmark.
    8. \n
    9. parser A parser (a python method) that is used to process the output of the benchmark run and that returns the execution times of individual benchmark runs in a list.
    10. \n
    \n

    Adding new benchmarks

    \n

    In order to add new benchmarks, a new configuration file needs to be created in the conf/benchmark subdirectory. Benchmarks may be grouped by the underscore-delimited segments in their file name. For instance, the PingPong benchmark is part of the micro-benchmarks of the Savina suite, and consequently its configuration file is named in conf/benchmark/savina_micro_pingpong.yaml. This allows to later specify benchmark=savina/micro/pingpong on the command line. Below you can see the contents of savina_micro_pingpong.yaml which we will break down in the following.

    \n
    # @package benchmark\nname: "Ping Pong"\nparams:\n  pings: 1000000\n\n# target specific configuration\ntargets:\n  akka:\n    jar: "${savina_path}/target/savina-0.0.1-SNAPSHOT-jar-with-dependencies.jar"\n    class: "edu.rice.habanero.benchmarks.pingpong.PingPongAkkaActorBenchmark"\n    run_args:\n      pings: ["-n", "<value>"]\n  caf:\n    bin: "caf_01_pingpong"\n    run_args:\n      pings: ["-n", "<value>"]\n  lf-cpp:\n    copy_sources:\n      - "${lf_path}/benchmark/Cpp/Savina/src/BenchmarkRunner.lf"\n      - "${lf_path}/benchmark/Cpp/Savina/src/micro"\n    lf_file: "micro/PingPong.lf"\n    binary: "PingPong"\n    gen_args: null\n    run_args:\n      pings: ["--count", "<value>"]\n  lf-c:\n    copy_sources:\n      - "${lf_path}/benchmark/C/Savina/src/micro/PingPong.lf"\n    lf_file: "PingPong.lf"\n    binary: "PingPong"\n    gen_args:\n      pings: ["-D", "count=<value>"]\n
    \n

    The first line # @package benchmark is hydra specific. It specifies that this configuration is part of the benchmark package. Essentially this enables the configuration to be assigned to benchmark on the command line.

    \n
    name: "Ping Pong"\nparams:\n  pings: 1000000\n
    \n

    This part sets the benchmark name to “Ping Pong” and declares that there is one benchmark specific parameter: pings. This configuration also set the default value for pings to 1000000. Note that the params dictionary may specify an arbitrary number of parameters.

    \n

    The remainder of the configuration file contains target specific configurations that provide instructions on how the particular benchmark can be run for the various targets. This block

    \n
    # target specific configuration\ntargets:\n  akka:\n    jar: "${savina_path}/target/savina-0.0.1-SNAPSHOT-jar-with-dependencies.jar"\n    class: "edu.rice.habanero.benchmarks.pingpong.PingPongAkkaActorBenchmark"\n    run_args:\n      pings: ["-n", "<value>"]\n
    \n

    specifies how the benchmark is executed using Akka. The jar and class configuration keys simply instruct the benchmark runner which class in which jar to run. Note that hydra automatically resolves ${savina_path} to the value you set in the SAVINA_PATH environment variable.

    \n

    The run_args configuration key allows specification of further arguments that are added to the command to be executed when running the benchmark. It expects a dictionary, where the keys are names of parameters as specified above in the params configuration key, and the values are a list of arguments to be added to the executed command. In the case of the pings parameter, the Akka implementation of the benchmark expects the -n flag followed by the parameter value. Note that the special string <value> is automatically resolved by the runner to the actual parameter value when executing the command.

    \n

    Instructions for the C++ target are specified as follows.

    \n
    lf-cpp:\n  copy_sources:\n    - "${lf_path}/benchmark/Cpp/Savina/src/BenchmarkRunner.lf"\n    - "${lf_path}/benchmark/Cpp/Savina/src/micro"\n  lf_file: "micro/PingPong.lf"\n  binary: "PingPong"\n  gen_args: null\n  run_args:\n    pings: ["--count", "<value>"]\n
    \n

    For C and C++ programs, we cannot run a precompiled program as it is the case for Akka, but we need to compile the benchmark first. The benchmark handler automatically performs the build in a temporary directory, so that it doesn’t interfere with the source tree. First, it copies all files listed under copy_sources to the temporary directory. If the specified source path is a directory, the whole directory is copied recursively. The lf_file configuration file specifies the file to be compiled with lfc. binary indicates the name of the binary file resulting from the compilation process.

    \n

    For some benchmarks, not all parameters can be applied at runtime. In such cases, the gen_args configuration key can be used to provide additional arguments that should be passed to cog. cog then applies the parameters to the source file (assuming that the source LF file uses cog directives to generate code according to the configuration). Similarly run_args specifies any additional arguments that should be passed to the binary when running the benchmark. In the case of the C++ configuration for the Ping Pong benchmark, the number of pings is a runtime parameter and specified with --count. Since this particular benchmark does not have any parameter that need to be set during generation, gen_args is set to null.

    \n

    Finally, we have the C part of the target configuration.

    \n
    lf-c:\n  copy_sources:\n    - "${lf_path}/benchmark/C/Savina/src/micro/PingPong.lf"\n  lf_file: "PingPong.lf"\n  binary: "PingPong"\n  gen_args:\n    pings: ["-D", "count=<value>"]\n
    \n

    This is very similar to the C++ configuration. However, the C target of LF currently does not support overriding of parameter values at runtime. Therefore, all parameters need to be provided as arguments to the code generator and the benchmark needs to provide corresponding cog directives.

    \n

    New benchmarks can be simply added by replicating this example and adjusting the precise configuration values and parameters to the specific benchmark.

    ","headings":[{"value":"Running Benchmarks","depth":1},{"value":"Prerequisites","depth":2},{"value":"Install Python dependencies","depth":3},{"value":"Compile lfc","depth":3},{"value":"Setup Savina","depth":3},{"value":"CAF","depth":4},{"value":"Running a benchmark","depth":2},{"value":"Running a series of benchmarks (multirun)","depth":2},{"value":"Collecting results from multirun","depth":2},{"value":"How it works","depth":2},{"value":"Adding new benchmarks","depth":2}],"frontmatter":{"permalink":"/docs/handbook/running-benchmarks","title":"Running Benchmarks","oneline":"Running Benchmarks.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","permalink":"/docs/handbook/regression-tests"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Website Development","oneline":"Development of the Lingua Franca website.","permalink":"/docs/handbook/website-development"}}}},"pageContext":{"id":"5-running-benchmarks","slug":"/docs/handbook/running-benchmarks","repoPath":"/packages/documentation/copy/en/developer/Running Benchmarks.md","previousID":"b004db16-2d0f-54e3-a2a8-d9d6f510eea1","nextID":"6fe7623e-c5ce-509d-9532-9d282ae790cc","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/running-benchmarks","result":{"data":{"markdownRemark":{"id":"ac793a6f-ced3-5dd9-972e-a6891620a738","excerpt":"Running Benchmarks The LF repository contains a series of benchmarks in the benchmark directory. There is also a flexible benchmark runner that automates the…","html":"

    Running Benchmarks

    \n

    The LF repository contains a series of benchmarks in the benchmark directory. There is also a flexible benchmark runner that automates the process of running benchmarks for various settings and collecting results from those benchmarks. It is located in benchmark/runner.\nThe runner is written in python and is based on hydra, a tool for dynamically creating hierarchical configurations by composition

    \n

    Prerequisites

    \n

    Install Python dependencies

    \n

    The benchmark runner is written in Python and requires a working Python3 installation. It also requires a few python packages to be installed. Namely, hydra-core, cogapp and pandas.

    \n

    It is recommended to install the dependencies and execute the benchmark runner in a virtual environment. For instance, this can be done with virtualenv:

    \n
    virtualenv ~/virtualenvs/lfrunner -p python3\nsource ~/virtualenvs/lfrunner/bin/activate\n
    \n

    Then the dependencies can be installed by running:

    \n
    pip install -r benchmark/runner/requirements.txt\n
    \n

    Compile lfc

    \n

    For running LF benchmarks, the command-line compiler lfc needs to be built. Simply run

    \n
    bin/build-lfc\n
    \n

    in the root directory of the LF repository.

    \n

    Also, the environment variable LF_PATH needs to be set and point to the location of the LF repository. This needs to be an absolute path.

    \n
    export LF_PATH=/path/to/lf\n
    \n

    Setup Savina

    \n

    Currently all of our benchmarks are ported from the Savina actor benchmark suite. In order to compare our LF implementations with actor based implementation, the Savina benchmark suite needs to be downloaded and compiled. Note that we require a modified version of the Savina suite, that adds support for specifying the number of worker threads and that includes CAF implementations of most benchmarks.

    \n

    To download and build Savina, run the following commands:

    \n
    git clone https://github.com/lf-lang/savina.git\ncd savina\nmvn install\n
    \n

    Building Savina requires a Java 8 JDK. Depending on the local setup, JAVA_HOME might need to be adjusted before running mvn in order to point to the correct JDK.

    \n
    export JAVA_HOME=/path/to/jdk8\n
    \n

    Before invoking the benchmark runner, the environment variable SAVINA_PATH needs to be set and point to the location of the Savina repository using an absolute path.

    \n
    export SAVINA_PATH=/path/to/savina\n
    \n

    CAF

    \n

    To further build the CAF benchmarks, CAF 0.16.5 needs to be downloaded, compiled and installed first:

    \n
    git clone --branch "0.16.5" git@github.com:actor-framework/actor-framework.git\nmkdir actor-framework/build && cd actor-framework/build\ncmake -DCMAKE_INSTALL_PREFIX=<preferred/install/location> ..\nmake install\n
    \n

    Then, from within the Savina directory, the CAF benchmarks can be build:

    \n
    cmake -DCAF_ROOT_DIR=<path/to/caf/install/location> ..\nmake\n
    \n

    The CAF benchmarks are used in these two publications:

    \n\n

    Running a benchmark

    \n

    A benchmark can simply be run by specifying a benchmark and a target. For instance

    \n
    cd benchmark/runner\n./run_benchmark.py benchmark=savina_micro_pingpong target=lf-c\n
    \n

    runs the Ping Pong benchmark from the Savina suite using the C-target of LF. Currently, supported targets are lf-c, lf-cpp, akka, and caf where akka corresponds to the Akka implementation in the original Savina suite and caf corresponds to a implementation using the C++ Actor Framework .

    \n

    The benchmarks can also be configured. The threads and iterations parameters apply to every benchmark and specify the number of worker threads as well as how many times the benchmark should be run. Most benchmarks allow additional parameters. For instance, the Ping Pong benchmark sends a configurable number of pings that be set via the benchmark.params.messages configuration key. Running the Akka version of the Ping Pong benchmark for 1000 messages, 1 thread and 12 iterations could be done like this:

    \n
    ./run_benchmark.py benchmark=savina_micro_pingpong target=akka threads=1 iterations=12 benchmark.params.messages=1000\n
    \n

    Each benchmark run produces an output directory in the scheme outputs/<date>/<time>/ (e.g. outputs/2020-12-17/16-46-16/). This directory contains a files results.csv which contains the measured execution time for each iteration and all the parameters used for running this particular benchmark. The CSV file contains precisely one row per iteration.

    \n

    Running a series of benchmarks (multirun)

    \n

    The runner also allows to automatically run a single benchmark or a series of benchmarks with a range of settings. The multirun feature is simply used by the -m switch. For instance:

    \n
    ./run_benchmark.py -m benchmark=savina_micro_pingpong target="glob(*)" threads=1,2,4 iterations=12 benchmark.params.messages="range(1000000,10000000,1000000)"\n
    \n

    runs the Ping Pong benchmark for all targets using 1, 2 and 4 threads and for a number of messages ranging from 1M to 10M (in 1M steps).

    \n

    This mechanism can also be used to run multiple benchmarks. For instance,

    \n
    ./run_benchmark.py -m benchmark="glob(*)" target="glob(*)" threads=4 iterations=12\n
    \n

    runs all benchmarks for all targets using 4 threads and 12 iterations.

    \n

    The results for a multirun are written to a directory in the scheme multirun/<date>/<time>/<n> (e.g. multirun/2020-12-17/17-11-03/0/) where <n> denotes the particular run. Each of the <n> subdirectories contains a results.csv for this particular run.

    \n

    Collecting results from multirun

    \n

    A second script called collect_results.py provides a convenient way for collecting results from a multirun and merging them into a single CSV file. Simply running

    \n
    ./collect_results.py multirun/<date>/<time>/ out.csv\n
    \n

    collects all results from the particular multirun and stores the merged data structure in out.csv. collect_results.py not only merges the results, but it also calculates minimum, maximum and median execution time for each individual run. The resulting CSV does not contain the measured values of individual iterations anymore and only contains a single row per run. This behavior can be disabled with the --raw command line flag. With the flag set, the results from all runs are merged as say are and the resulting file contains rows for all individual runs, but no minimum, maximum and median values.

    \n

    How it works

    \n

    The benchmark runner itself is actually relatively simple. Most of the complexity is dealt with by hydra. Hydra is a complex and convenient tool for handling configurations. These configurations can be merged from different sources and be overridden via command line arguments as you have seen above. The actual benchmark runner receives the configuration represented as nested dictionaries from hydra. It then executes the benchmarks precisely as instructed by the configuration.

    \n

    The configuration is split into two big parts: the benchmark configuration and the target configuration. The benchmark configuration describes a particular benchmark instance. This is described in more detail in the next section. The target configuration specifies how to run a benchmark for a specific target (e.g. akka, lf-c, lf-cpp). This is not intended to be changed by the user and therefore isn’t explained in detail here. Essentially a benchmark run is split into 5 steps as is outlined in the following. The target configuration precisely specifies what needs to be done in each step

    \n
      \n
    1. copy The command used to copy relevant source files to a temporary directory.
    2. \n
    3. gen The command used to generate a configured LF file. This is intended to apply a code generation tool like cog to the source code in order to make benchmarks parameterized.
    4. \n
    5. compile The command used to compile the benchmark.
    6. \n
    7. run The command used to generate the benchmark.
    8. \n
    9. parser A parser (a python method) that is used to process the output of the benchmark run and that returns the execution times of individual benchmark runs in a list.
    10. \n
    \n

    Adding new benchmarks

    \n

    In order to add new benchmarks, a new configuration file needs to be created in the conf/benchmark subdirectory. Benchmarks may be grouped by the underscore-delimited segments in their file name. For instance, the PingPong benchmark is part of the micro-benchmarks of the Savina suite, and consequently its configuration file is named in conf/benchmark/savina_micro_pingpong.yaml. This allows to later specify benchmark=savina/micro/pingpong on the command line. Below you can see the contents of savina_micro_pingpong.yaml which we will break down in the following.

    \n
    # @package benchmark\nname: "Ping Pong"\nparams:\n  pings: 1000000\n\n# target specific configuration\ntargets:\n  akka:\n    jar: "${savina_path}/target/savina-0.0.1-SNAPSHOT-jar-with-dependencies.jar"\n    class: "edu.rice.habanero.benchmarks.pingpong.PingPongAkkaActorBenchmark"\n    run_args:\n      pings: ["-n", "<value>"]\n  caf:\n    bin: "caf_01_pingpong"\n    run_args:\n      pings: ["-n", "<value>"]\n  lf-cpp:\n    copy_sources:\n      - "${lf_path}/benchmark/Cpp/Savina/src/BenchmarkRunner.lf"\n      - "${lf_path}/benchmark/Cpp/Savina/src/micro"\n    lf_file: "micro/PingPong.lf"\n    binary: "PingPong"\n    gen_args: null\n    run_args:\n      pings: ["--count", "<value>"]\n  lf-c:\n    copy_sources:\n      - "${lf_path}/benchmark/C/Savina/src/micro/PingPong.lf"\n    lf_file: "PingPong.lf"\n    binary: "PingPong"\n    gen_args:\n      pings: ["-D", "count=<value>"]\n
    \n

    The first line # @package benchmark is hydra specific. It specifies that this configuration is part of the benchmark package. Essentially this enables the configuration to be assigned to benchmark on the command line.

    \n
    name: "Ping Pong"\nparams:\n  pings: 1000000\n
    \n

    This part sets the benchmark name to “Ping Pong” and declares that there is one benchmark specific parameter: pings. This configuration also set the default value for pings to 1000000. Note that the params dictionary may specify an arbitrary number of parameters.

    \n

    The remainder of the configuration file contains target specific configurations that provide instructions on how the particular benchmark can be run for the various targets. This block

    \n
    # target specific configuration\ntargets:\n  akka:\n    jar: "${savina_path}/target/savina-0.0.1-SNAPSHOT-jar-with-dependencies.jar"\n    class: "edu.rice.habanero.benchmarks.pingpong.PingPongAkkaActorBenchmark"\n    run_args:\n      pings: ["-n", "<value>"]\n
    \n

    specifies how the benchmark is executed using Akka. The jar and class configuration keys simply instruct the benchmark runner which class in which jar to run. Note that hydra automatically resolves ${savina_path} to the value you set in the SAVINA_PATH environment variable.

    \n

    The run_args configuration key allows specification of further arguments that are added to the command to be executed when running the benchmark. It expects a dictionary, where the keys are names of parameters as specified above in the params configuration key, and the values are a list of arguments to be added to the executed command. In the case of the pings parameter, the Akka implementation of the benchmark expects the -n flag followed by the parameter value. Note that the special string <value> is automatically resolved by the runner to the actual parameter value when executing the command.

    \n

    Instructions for the C++ target are specified as follows.

    \n
    lf-cpp:\n  copy_sources:\n    - "${lf_path}/benchmark/Cpp/Savina/src/BenchmarkRunner.lf"\n    - "${lf_path}/benchmark/Cpp/Savina/src/micro"\n  lf_file: "micro/PingPong.lf"\n  binary: "PingPong"\n  gen_args: null\n  run_args:\n    pings: ["--count", "<value>"]\n
    \n

    For C and C++ programs, we cannot run a precompiled program as it is the case for Akka, but we need to compile the benchmark first. The benchmark handler automatically performs the build in a temporary directory, so that it doesn’t interfere with the source tree. First, it copies all files listed under copy_sources to the temporary directory. If the specified source path is a directory, the whole directory is copied recursively. The lf_file configuration file specifies the file to be compiled with lfc. binary indicates the name of the binary file resulting from the compilation process.

    \n

    For some benchmarks, not all parameters can be applied at runtime. In such cases, the gen_args configuration key can be used to provide additional arguments that should be passed to cog. cog then applies the parameters to the source file (assuming that the source LF file uses cog directives to generate code according to the configuration). Similarly run_args specifies any additional arguments that should be passed to the binary when running the benchmark. In the case of the C++ configuration for the Ping Pong benchmark, the number of pings is a runtime parameter and specified with --count. Since this particular benchmark does not have any parameter that need to be set during generation, gen_args is set to null.

    \n

    Finally, we have the C part of the target configuration.

    \n
    lf-c:\n  copy_sources:\n    - "${lf_path}/benchmark/C/Savina/src/micro/PingPong.lf"\n  lf_file: "PingPong.lf"\n  binary: "PingPong"\n  gen_args:\n    pings: ["-D", "count=<value>"]\n
    \n

    This is very similar to the C++ configuration. However, the C target of LF currently does not support overriding of parameter values at runtime. Therefore, all parameters need to be provided as arguments to the code generator and the benchmark needs to provide corresponding cog directives.

    \n

    New benchmarks can be simply added by replicating this example and adjusting the precise configuration values and parameters to the specific benchmark.

    ","headings":[{"value":"Running Benchmarks","depth":1},{"value":"Prerequisites","depth":2},{"value":"Install Python dependencies","depth":3},{"value":"Compile lfc","depth":3},{"value":"Setup Savina","depth":3},{"value":"CAF","depth":4},{"value":"Running a benchmark","depth":2},{"value":"Running a series of benchmarks (multirun)","depth":2},{"value":"Collecting results from multirun","depth":2},{"value":"How it works","depth":2},{"value":"Adding new benchmarks","depth":2}],"frontmatter":{"permalink":"/docs/handbook/running-benchmarks","title":"Running Benchmarks","oneline":"Running Benchmarks.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Regression Tests","oneline":"Regression Tests for Lingua Franca.","permalink":"/docs/handbook/regression-tests"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Website Development","oneline":"Development of the Lingua Franca website.","permalink":"/docs/handbook/website-development"}}}},"pageContext":{"id":"5-running-benchmarks","slug":"/docs/handbook/running-benchmarks","repoPath":"/packages/documentation/copy/en/developer/Running Benchmarks.md","previousID":"b004db16-2d0f-54e3-a2a8-d9d6f510eea1","nextID":"6fe7623e-c5ce-509d-9532-9d282ae790cc","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/security/page-data.json b/page-data/docs/handbook/security/page-data.json index 59f1d7468..1bc8772b9 100644 --- a/page-data/docs/handbook/security/page-data.json +++ b/page-data/docs/handbook/security/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/security","result":{"data":{"markdownRemark":{"id":"21be3a62-9007-5e8f-a8ab-0e0e5a549b99","excerpt":"Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations…","html":"

    Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations will be secure if the auth target property is enabled.

    \n

    By default, there is no secure authentication happening when a federate joins a federation, and data exchanged by federates is not encrypted. For targets that support it, the auth target property can be used to enable authentication between federates. Messages exchanged between federates after authentication are not encrypted, but this capability is planned for the future.

    \n
    \n

    The $target-language$ target does not currently support the auth target option.

    \n
    \n
    \n

    Authentication

    \n

    For the C target, federated execution is able to apply security with authentication by using HMAC authentication between RTI and federates. To enable this, include the auth property in your target specification, as follows:

    \n
    target C {\n    auth: true\n};\n
    \n

    The RTI build must include CMake options to enable simple HMAC-based authentication of federates. Add -DAUTH=ON option to the CMake command as shown below:

    \n
    mkdir build && cd build\ncmake -DAUTH=ON ../\nmake\nsudo make install\n
    \n

    If you would like to go back to non-AUTH mode, you would have to remove all contents of the build folder.

    \n
    ","headings":[{"value":"Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations will be secure if the auth target property is enabled.","depth":3},{"value":"Authentication","depth":2}],"frontmatter":{"permalink":"/docs/handbook/security","title":"Security","oneline":"Secure Federated Execution","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Containerized Execution","oneline":"Containerized Execution using Docker","permalink":"/docs/handbook/containerized-execution"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"id":"3-security","slug":"/docs/handbook/security","repoPath":"/packages/documentation/copy/en/reference/Security.md","previousID":"0d63a4b3-f226-54ba-a299-23b69782aba6","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/security","result":{"data":{"markdownRemark":{"id":"21be3a62-9007-5e8f-a8ab-0e0e5a549b99","excerpt":"Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations…","html":"

    Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations will be secure if the auth target property is enabled.

    \n

    By default, there is no secure authentication happening when a federate joins a federation, and data exchanged by federates is not encrypted. For targets that support it, the auth target property can be used to enable authentication between federates. Messages exchanged between federates after authentication are not encrypted, but this capability is planned for the future.

    \n
    \n

    The $target-language$ target does not currently support the auth target option.

    \n
    \n
    \n

    Authentication

    \n

    For the C target, federated execution is able to apply security with authentication by using HMAC authentication between RTI and federates. To enable this, include the auth property in your target specification, as follows:

    \n
    target C {\n    auth: true\n};\n
    \n

    The RTI build must include CMake options to enable simple HMAC-based authentication of federates. Add -DAUTH=ON option to the CMake command as shown below:

    \n
    mkdir build && cd build\ncmake -DAUTH=ON ../\nmake\nsudo make install\n
    \n

    If you would like to go back to non-AUTH mode, you would have to remove all contents of the build folder.

    \n
    ","headings":[{"value":"Warning: the experimental security features described on this page are under development and not production ready. Users should not expect their federations will be secure if the auth target property is enabled.","depth":3},{"value":"Authentication","depth":2}],"frontmatter":{"permalink":"/docs/handbook/security","title":"Security","oneline":"Secure Federated Execution","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Containerized Execution","oneline":"Containerized Execution using Docker","permalink":"/docs/handbook/containerized-execution"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"id":"3-security","slug":"/docs/handbook/security","repoPath":"/packages/documentation/copy/en/reference/Security.md","previousID":"0d63a4b3-f226-54ba-a299-23b69782aba6","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/superdense-time/page-data.json b/page-data/docs/handbook/superdense-time/page-data.json index 065510ad0..5549c0944 100644 --- a/page-data/docs/handbook/superdense-time/page-data.json +++ b/page-data/docs/handbook/superdense-time/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/superdense-time","result":{"data":{"markdownRemark":{"id":"20889c15-4d9e-53ef-b371-5b83af6a6440","excerpt":"$page-showing-target$ Tag vs. Time The model of time in Lingua Franca is a bit more sophisticated than we have hinted at. Specifically, a superdense model of…","html":"

    $page-showing-target$

    \n

    Tag vs. Time

    \n

    The model of time in Lingua Franca is a bit more sophisticated than we have hinted at. Specifically, a superdense model of time is used. In particular, instead of a timestamp, LF uses a tag, which consists of a logical time t and a microstep m.

    \n

    A $logical$ $action$ may have a <min_delay> of zero, and the <offset> argument to the schedule() function may be zero. In this case, the call to schedule() appears to be requesting that the action trigger at the current logical time. Here is where superdense time comes in. The action will indeed trigger at the current logical time, but one microstep later. Consider the following example:

    \n

    $start(Microsteps)$

    \n
    target C\nmain reactor {\n  state count: int = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    printf("%d. Logical time is %lld. Microstep is %d.\\n",\n        self->count, lf_tag().time, lf_tag().microstep\n    );\n    if (self->count++ < 5) {\n      lf_schedule(a, 0);\n    }\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  state count: int(1)\n  logical action a\n  reaction(startup, a) -> a {=\n    std::cout << count << " Logical time is " << get_logical_time() << " Microstep: " << get_microstep() <<std::endl;\n    if (count++ < 5) {\n      a.schedule();\n    }\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  state count = 1\n  logical action a\n  reaction(startup, a) {=\n    print(\n        f"{self.count}. Logical time is {lf.tag().time}. "\n        f"Microstep is {lf.tag().microstep}."\n    )\n    if self.count < 5:\n      a.schedule(0)\n    self.count += 1\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  state count: number = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    console.log(`${count}. Logical time is ${util.getCurrentLogicalTime()}. Microstep is ${util.getCurrentTag().microstep}.`)\n    if (count++ < 5) {\n      actions.a.schedule(TimeValue.zero(), null)\n    }\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  state count: u32 = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    let tag = ctx.get_tag();\n    println!(\n        "{}. Logical time is {}. Microstep is {}.",\n        self.count,\n        tag.offset_from_t0.as_nanos(),\n        tag.microstep(),\n    );\n    if self.count < 5 {\n      self.count += 1;\n      ctx.schedule(a, Asap);\n    }\n  =}\n}\n
    \n

    $end(Microsteps)$

    \n\"Lingua\n

    Executing this program will yield something like this:

    \n
    1. Logical time is 1649607749415269000. Microstep is 0.\n2. Logical time is 1649607749415269000. Microstep is 1.\n3. Logical time is 1649607749415269000. Microstep is 2.\n4. Logical time is 1649607749415269000. Microstep is 3.\n5. Logical time is 1649607749415269000. Microstep is 4.
    \n

    Notice that the logical time is not advancing, but the microstep is (the logical time, in this case, gives the number of nanoseconds that have elapsed since January 1, 1970). The general rule is that every call to schedule() advances the tag by at least one microstep.

    \n

    Logical Simultaneity

    \n

    Two events are logically simultaneous only if both the logical time and the microstep are equal. The following example illustrates this:

    \n

    $start(Simultaneous)$

    \n
    target C\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    printf("Time since start: %lld, microstep: %d\\n",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n    if (x->is_present) {\n      printf("  x is present.\\n");\n    }\n    if (y->is_present) {\n      printf("  y is present.\\n");\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    lf_set(d.x, 1);\n    lf_schedule(repeat, 0);\n  =}\n  reaction(repeat) -> d.y {= lf_set(d.y, 1); =}\n}\n
    \n
    target Cpp\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    std::cout << "Time since start: " << get_elapsed_logical_time() << " Current Microstep: " << get_microstep() << std::endl;\n    if (x.is_present()) {\n      std::cout << "x is present" << std::endl;\n    }\n    if (y.is_present()) {\n      std::cout << "y is present" << std::endl;\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x.set(1);\n    repeat.schedule(0ms);\n  =}\n  reaction(repeat) -> d.y {= d.y.set(1); =}\n}\n
    \n
    target Python\nreactor Destination {\n  input x\n  input y\n  reaction(x, y) {=\n    print(\n        f"Time since start: {lf.time.logical_elapsed()}, "\n        f"microstep: {lf.tag().microstep}"\n    )\n    if x.is_present:\n      print("  x is present.")\n    if y.is_present:\n      print("  y is present.")\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x.set(1)\n    repeat.schedule(0)\n  =}\n  reaction(repeat) -> d.y {= d.y.set(1) =}\n}\n
    \n
    target TypeScript\nreactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    console.log(`Time since start: ${util.getElapsedLogicalTime()}, microstep: ${util.getCurrentTag().microstep}`)\n    if (x !== undefined) {\n      console.log("  x is present.")\n    }\n    if (y !== undefined) {\n      console.log("  y is present.")\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x = 1\n    actions.repeat.schedule(0, null)\n  =}\n  reaction(repeat) -> d.y {= d.y = 1 =}\n}\n
    \n
    target Rust\nreactor Destination {\n  input x: u32\n  input y: u32\n  reaction(x, y) {=\n    let tag = ctx.get_tag();\n    println!(\n        "Time since start: {}, microstep: {}",\n        tag.offset_from_t0.as_nanos(),\n        tag.microstep,\n    );\n    if ctx.is_present(x) {\n      println!("  x is present.");\n    }\n    if ctx.is_present(y) {\n      println!("  y is present.");\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    ctx.set(d__x, 1);\n    ctx.schedule(repeat, Asap);\n  =}\n  reaction(repeat) -> d.y {= ctx.set(d__y, 1); =}\n}\n
    \n

    $end(Simultaneous)$

    \n\"Lingua\n

    The Destination reactor has two inputs, x and y, and it reports in a reaction to either input what is the logical time, the microstep, and which input is present. The main reactor reacts to $startup$ by sending data to the x input of Destination. It then schedules a repeat action with an <offset> of zero. The repeat reaction is invoked strictly later, one microstep later. The output printed, therefore, will look like this:

    \n
    Time since start: 0, microstep: 0\n  x is present.\nTime since start: 0, microstep: 1\n  y is present.
    \n

    The reported elapsed logical time has not advanced in the second reaction, but the fact that x is not present in the second reaction proves that the first reaction and the second are not logically simultaneous. The second occurs one microstep later.

    \n

    Alignment of Logical and Physical Times

    \n

    Recall that in Lingua Franca, logical time “chases” physical time, invoking reactions at a physical time close to their logical time. For that purpose, the microstep is ignored.

    ","headings":[{"value":"Tag vs. Time","depth":2},{"value":"Logical Simultaneity","depth":2},{"value":"Alignment of Logical and Physical Times","depth":2}],"frontmatter":{"permalink":"/docs/handbook/superdense-time","title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Actions","oneline":"Actions in Lingua Franca.","permalink":"/docs/handbook/actions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Modal Reactors","oneline":"Modal Reactors","permalink":"/docs/handbook/modal-models"}}}},"pageContext":{"id":"1-superdense-time","slug":"/docs/handbook/superdense-time","repoPath":"/packages/documentation/copy/en/topics/Superdense Time.md","previousID":"ab880406-6c38-59c6-9a2c-a8f736013224","nextID":"ddeb2577-9554-5362-9ed2-abba8f412fc1","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/superdense-time","result":{"data":{"markdownRemark":{"id":"20889c15-4d9e-53ef-b371-5b83af6a6440","excerpt":"$page-showing-target$ Tag vs. Time The model of time in Lingua Franca is a bit more sophisticated than we have hinted at. Specifically, a superdense model of…","html":"

    $page-showing-target$

    \n

    Tag vs. Time

    \n

    The model of time in Lingua Franca is a bit more sophisticated than we have hinted at. Specifically, a superdense model of time is used. In particular, instead of a timestamp, LF uses a tag, which consists of a logical time t and a microstep m.

    \n

    A $logical$ $action$ may have a <min_delay> of zero, and the <offset> argument to the schedule() function may be zero. In this case, the call to schedule() appears to be requesting that the action trigger at the current logical time. Here is where superdense time comes in. The action will indeed trigger at the current logical time, but one microstep later. Consider the following example:

    \n

    $start(Microsteps)$

    \n
    target C\nmain reactor {\n  state count: int = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    printf("%d. Logical time is %lld. Microstep is %d.\\n",\n        self->count, lf_tag().time, lf_tag().microstep\n    );\n    if (self->count++ < 5) {\n      lf_schedule(a, 0);\n    }\n  =}\n}\n
    \n
    target Cpp\nmain reactor {\n  state count: int(1)\n  logical action a\n  reaction(startup, a) -> a {=\n    std::cout << count << " Logical time is " << get_logical_time() << " Microstep: " << get_microstep() <<std::endl;\n    if (count++ < 5) {\n      a.schedule();\n    }\n  =}\n}\n
    \n
    target Python\nmain reactor {\n  state count = 1\n  logical action a\n  reaction(startup, a) {=\n    print(\n        f"{self.count}. Logical time is {lf.tag().time}. "\n        f"Microstep is {lf.tag().microstep}."\n    )\n    if self.count < 5:\n      a.schedule(0)\n    self.count += 1\n  =}\n}\n
    \n
    target TypeScript\nmain reactor {\n  state count: number = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    console.log(`${count}. Logical time is ${util.getCurrentLogicalTime()}. Microstep is ${util.getCurrentTag().microstep}.`)\n    if (count++ < 5) {\n      actions.a.schedule(TimeValue.zero(), null)\n    }\n  =}\n}\n
    \n
    target Rust\nmain reactor {\n  state count: u32 = 1\n  logical action a\n  reaction(startup, a) -> a {=\n    let tag = ctx.get_tag();\n    println!(\n        "{}. Logical time is {}. Microstep is {}.",\n        self.count,\n        tag.offset_from_t0.as_nanos(),\n        tag.microstep(),\n    );\n    if self.count < 5 {\n      self.count += 1;\n      ctx.schedule(a, Asap);\n    }\n  =}\n}\n
    \n

    $end(Microsteps)$

    \n\"Lingua\n

    Executing this program will yield something like this:

    \n
    1. Logical time is 1649607749415269000. Microstep is 0.\n2. Logical time is 1649607749415269000. Microstep is 1.\n3. Logical time is 1649607749415269000. Microstep is 2.\n4. Logical time is 1649607749415269000. Microstep is 3.\n5. Logical time is 1649607749415269000. Microstep is 4.
    \n

    Notice that the logical time is not advancing, but the microstep is (the logical time, in this case, gives the number of nanoseconds that have elapsed since January 1, 1970). The general rule is that every call to schedule() advances the tag by at least one microstep.

    \n

    Logical Simultaneity

    \n

    Two events are logically simultaneous only if both the logical time and the microstep are equal. The following example illustrates this:

    \n

    $start(Simultaneous)$

    \n
    target C\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    printf("Time since start: %lld, microstep: %d\\n",\n        lf_time_logical_elapsed(), lf_tag().microstep\n    );\n    if (x->is_present) {\n      printf("  x is present.\\n");\n    }\n    if (y->is_present) {\n      printf("  y is present.\\n");\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    lf_set(d.x, 1);\n    lf_schedule(repeat, 0);\n  =}\n  reaction(repeat) -> d.y {= lf_set(d.y, 1); =}\n}\n
    \n
    target Cpp\nreactor Destination {\n  input x: int\n  input y: int\n  reaction(x, y) {=\n    std::cout << "Time since start: " << get_elapsed_logical_time() << " Current Microstep: " << get_microstep() << std::endl;\n    if (x.is_present()) {\n      std::cout << "x is present" << std::endl;\n    }\n    if (y.is_present()) {\n      std::cout << "y is present" << std::endl;\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x.set(1);\n    repeat.schedule(0ms);\n  =}\n  reaction(repeat) -> d.y {= d.y.set(1); =}\n}\n
    \n
    target Python\nreactor Destination {\n  input x\n  input y\n  reaction(x, y) {=\n    print(\n        f"Time since start: {lf.time.logical_elapsed()}, "\n        f"microstep: {lf.tag().microstep}"\n    )\n    if x.is_present:\n      print("  x is present.")\n    if y.is_present:\n      print("  y is present.")\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x.set(1)\n    repeat.schedule(0)\n  =}\n  reaction(repeat) -> d.y {= d.y.set(1) =}\n}\n
    \n
    target TypeScript\nreactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    console.log(`Time since start: ${util.getElapsedLogicalTime()}, microstep: ${util.getCurrentTag().microstep}`)\n    if (x !== undefined) {\n      console.log("  x is present.")\n    }\n    if (y !== undefined) {\n      console.log("  y is present.")\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    d.x = 1\n    actions.repeat.schedule(0, null)\n  =}\n  reaction(repeat) -> d.y {= d.y = 1 =}\n}\n
    \n
    target Rust\nreactor Destination {\n  input x: u32\n  input y: u32\n  reaction(x, y) {=\n    let tag = ctx.get_tag();\n    println!(\n        "Time since start: {}, microstep: {}",\n        tag.offset_from_t0.as_nanos(),\n        tag.microstep,\n    );\n    if ctx.is_present(x) {\n      println!("  x is present.");\n    }\n    if ctx.is_present(y) {\n      println!("  y is present.");\n    }\n  =}\n}\nmain reactor {\n  logical action repeat\n  d = new Destination()\n  reaction(startup) -> d.x, repeat {=\n    ctx.set(d__x, 1);\n    ctx.schedule(repeat, Asap);\n  =}\n  reaction(repeat) -> d.y {= ctx.set(d__y, 1); =}\n}\n
    \n

    $end(Simultaneous)$

    \n\"Lingua\n

    The Destination reactor has two inputs, x and y, and it reports in a reaction to either input what is the logical time, the microstep, and which input is present. The main reactor reacts to $startup$ by sending data to the x input of Destination. It then schedules a repeat action with an <offset> of zero. The repeat reaction is invoked strictly later, one microstep later. The output printed, therefore, will look like this:

    \n
    Time since start: 0, microstep: 0\n  x is present.\nTime since start: 0, microstep: 1\n  y is present.
    \n

    The reported elapsed logical time has not advanced in the second reaction, but the fact that x is not present in the second reaction proves that the first reaction and the second are not logically simultaneous. The second occurs one microstep later.

    \n

    Alignment of Logical and Physical Times

    \n

    Recall that in Lingua Franca, logical time “chases” physical time, invoking reactions at a physical time close to their logical time. For that purpose, the microstep is ignored.

    ","headings":[{"value":"Tag vs. Time","depth":2},{"value":"Logical Simultaneity","depth":2},{"value":"Alignment of Logical and Physical Times","depth":2}],"frontmatter":{"permalink":"/docs/handbook/superdense-time","title":"Superdense Time","oneline":"Superdense time in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Actions","oneline":"Actions in Lingua Franca.","permalink":"/docs/handbook/actions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Modal Reactors","oneline":"Modal Reactors","permalink":"/docs/handbook/modal-models"}}}},"pageContext":{"id":"1-superdense-time","slug":"/docs/handbook/superdense-time","repoPath":"/packages/documentation/copy/en/topics/Superdense Time.md","previousID":"ab880406-6c38-59c6-9a2c-a8f736013224","nextID":"ddeb2577-9554-5362-9ed2-abba8f412fc1","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/target-declaration/page-data.json b/page-data/docs/handbook/target-declaration/page-data.json index 8c065c741..e62c8435f 100644 --- a/page-data/docs/handbook/target-declaration/page-data.json +++ b/page-data/docs/handbook/target-declaration/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/target-declaration","result":{"data":{"markdownRemark":{"id":"e4812e4e-df22-54fb-9a27-a82edb11ac44","excerpt":"$page-showing-target$ Every Lingua Franca program begins with a statement of this form: The gives the name of some Lingua Franca target language, which…","html":"

    $page-showing-target$

    \n

    Every Lingua Franca program begins with a statement of this form:

    \n
        target <name> <parameters>\n
    \n

    The <name> gives the name of some Lingua Franca target language, which is the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca compiler. The target languages currently supported are C, C++, Python, TypeScript, and Rust.

    \n

    Summary of Parameters

    \n

    A target specification may have optional parameters, the names and values of which depend on which specific target you are using. Each parameter is a key-value pair, where the supported keys are a subset of the following:

    \n
      \n
    • auth: A boolean specifying to apply authorization between RTI and federates when federated execution.
    • \n
    • build: A command to execute after code generation instead of the default compile command.
    • \n
    • build-type: One of Release (the default), Debug, RelWithDebInfo and MinSizeRel.
    • \n
    • cargo-dependencies: (Rust only) list of dependencies to include in the generated Cargo.toml file.
    • \n
    • cargo-features: (Rust only) List of string names of features to include.
    • \n
    • cmake: Whether to use cmake for building.
    • \n
    • cmake-include: List of paths to cmake files to guide compilation.
    • \n
    • compiler: A string giving the name of the target language compiler to use.
    • \n
    • docker: A boolean to generate a Dockerfile.
    • \n
    • external-runtime-path: Specify a pre-compiled external runtime library located to link to instead of the default.
    • \n
    • export-dependency-graph: To export the reaction dependency graph as a dot graph (for debugging).
    • \n
    • fast: A boolean specifying to execute as fast as possible without waiting for physical time to match logical time.
    • \n
    • files: An array of paths to files or directories to be copied to the directory that contains the generated sources.
    • \n
    • flags: An arrays of strings giving options to be passed to the target compiler.
    • \n
    • logging: An indicator of how much information to print when executing the program.
    • \n
    • no-compile: If true, then do not invoke a target language compiler. Just generate code.
    • \n
    • no-runtime-validation: If true, disable runtime validation.
    • \n
    • protobufs: An array of .proto files that are to be compiled and included in the generated code.
    • \n
    • runtime-version: Specify which version of the runtime system to use.
    • \n
    • rust-include: (Rust only) A set of Rust modules in the generated project.
    • \n
    • scheduler: (C only) Specification of the scheduler to us.
    • \n
    • single-file-project: (Rust only) If true, enables single-file project layout.
    • \n
    • threading: Whether to use multiple threads.
    • \n
    • timeout: A time value (with units) specifying the logical stop time of execution. See Termination.
    • \n
    • workers: If using multiple threads, how many worker threads to create.
    • \n
    \n

    Not all targets support all target parameters. The full set of target parameters supported by the $target-language$ target is:

    \n
    target C {\n    auth: <true or false>\n    build: <string>,\n    build-type: <Release, Debug, RelWithDebInfo, or MinSizeRel>,\n    cmake: <true or false>,\n    cmake-include: <string or list of strings>,\n    compiler: <string>,\n    docker: <true or false>,\n    fast: <true or false>,\n    files: <string or list of strings>,\n    flags: <string or list of strings>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    protobufs: <string or list of strings>,\n    threading: <true or false>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target Cpp {\n    build-type: <Release, Debug, RelWithDebInfo, or MinSizeRel>,\n    cmake-include: <string or list of strings>,\n    external-runtime-path: <string>,\n    export-dependency-graph <true or false>,\n    fast: <true or false>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    no-runtime-validation: <true or false>,\n    runtime-version: <string>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target Python {\n    docker: <true or false>,\n    fast: <true or false>,\n    files: <string or list of strings>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    protobufs: <string or list of strings>,\n    threading: <true or false>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target TypeScript {\n    docker: <true or false>,\n    fast: <true or false>,\n    logging: <ERROR, WARN, INFO, LOG, or DEBUG>,\n    timeout: <time>,\n};\n
    \n
    target Rust {\n    build-type: <Debug, Release, RelWithDebInfo, or MinSizeRel>,\n    cargo-features: <array of strings>,\n    cargo-dependencies: <list of key-value pairs>,\n    export-dependency-graph: <true or false>,\n    rust-include: <array of strings>,\n    single-file-project: <true or false>,\n    timeout: <time value>,\n}\n
    \n
    \n

    For example:

    \n
    target C {\n    cmake: false,\n    compiler: "cc",\n    flags: "-O3",\n    fast: true,\n    logging: log,\n    timeout: 1 secs,\n};\n
    \n

    This specifies to use compiler cc instead of the default gcc, to use optimization level 3, to execute as fast as possible, and to exit execution when logical time has advanced to 10 seconds. Note that all events at logical time 10 seconds greater than the starting logical time will be executed.

    \n
    \n

    The comma on the last parameter is optional, as is the semicolon on the last line.\nA target may support overriding the target parameters on the command line when invoking the compiled program.

    \n

    auth

    \n
    \n

    The $target-language$ target does not currently support the auth target option.

    \n
    \n
    \n

    The detailed documentation is here.

    \n
    \n

    build

    \n
    \n

    The $target-language$ target does not currently support the build target option.

    \n
    \n
    \n

    A command to execute after code generation instead of the default compile command. This is either a single string or an array of strings. The specified command(s) will be executed an environment that has the following environment variables defined:

    \n
      \n
    • LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked.
    • \n
    • LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled.
    • \n
    • LF_PACKAGE_DIRECTORY: The directory for the root of the project or package (normally the directory above the src directory).
    • \n
    • LF_SOURCE_GEN_DIRECTORY: The directory in which generated files are placed.
    • \n
    • LF_BIN_DIRECTORY: The directory into which to put binaries.
    • \n
    \n

    The command will be executed in the same directory as the .lf file being compiled. For example, if you specify

    \n
    target C {\n    build: "./compile.sh Foo"\n}\n
    \n

    then instead of invoking the C compiler after generating code, the code generator will invoke your compile.sh script, which could look something like this:

    \n
    #!/bin/bash\n# Build the generated code.\ncd ${LF_SOURCE_GEN_DIRECTORY}\ncmake .\nmake\n\n# Move the executable to the bin directory.\nmv $1 ${LF_BIN_DIRECTORY}\n\n# Invoke the executable.\n${LF_BIN_DIRECTORY}/$1\n\n# Plot the results, which have appeared in the src-gen directory.\ngnuplot ${LF_SOURCE_DIRECTORY}/$1.gnuplot\nopen $1.pdf\n
    \n

    The first few lines of this script do the same thing that is normally done when there is no build option in the target. Specifically, they use cmake to create a makefile, invoke make, and then move the executable to the bin directory. The next line, however, gives new functionality. It executes the compiled code! The final two lines assume that the program has produced a file with data to be plotted and use gnuplot to plot the data. This requires, of course, that you have gnuplot installed, and that there is a file called Foo.gnuplot in the same directory as Foo.lf. The file Foo.gnuplot contains the commands to plot the data, and might look something like the following:

    \n
    set title 'My Title'\nset xrange [0:3]\nset yrange [-2:2]\nset xlabel "Time (seconds)"\nset terminal pdf size 5, 3.5\nset output 'Foo.pdf'\nplot 'mydata1.data' using 1:2 with lines, \\\n     'mydata2.data' using 1:2 with lines\n
    \n

    This assumes that your program has written two files, mydata1.data and mydata2.data containing two columns, time and value.

    \n
    \n

    build-type

    \n
    \n

    The $target-language$ target does not currently support the build-type target option.

    \n
    \n
    \n

    This parameter works with cargo to specify how to compile the code. The following options are supported:

    \n
      \n
    • Release: Optimization is turned on and debug information is missing.
    • \n
    • Debug: Debug information is included in the executable.
    • \n
    • RelWithDebInfo: Optimization with debug information.
    • \n
    • MinSizeRel: Optimize for smallest size.
    • \n
    \n

    This defaults to Release.

    \n
    \n
    \n

    This parameter works with cmake to specify how to compile the code. The following options are supported:

    \n
      \n
    • Release: Optimization is turned on and debug information is missing.
    • \n
    • Debug: Debug information is included in the executable.
    • \n
    • RelWithDebInfo: Optimization with debug information.
    • \n
    • MinSizeRel: Optimize for smallest size.
    • \n
    \n

    This defaults to Release.

    \n
    \n
    \n

    cargo-dependencies

    \n

    This is a list of dependencies to include in the generated Cargo.toml file. The value of this parameter is a map of package name to dependency-spec.

    \n

    Here is an example for defining dependencies:

    \n
    target Rust {\n    cargo-dependencies: {\n        fxhash: {\n            version: "0.2.1",\n        },\n        rand: {\n            version: "0.8",\n            features: ["small_rng"],\n        },\n    }\n};\n
    \n

    cargo-features

    \n

    This is a list of features of the generated crate. Supported are:

    \n\n
    \n

    cmake

    \n
    \n

    The $target-language$ target does not support the cmake target option.

    \n
    \n
    \n

    The $target-language$ target does not support the cmake target option because it always uses cmake.

    \n
    \n
    \n
    target C {\n    cmake: <true or false>\n};\n
    \n

    This will enable or disable the CMake-based build system (the default is true). Enabling the CMake build system will result in a CMakeLists.txt being generated in the src-gen directory. This CMakeLists.txt is then used when cmake is invoked by the LF runtime (either the lfc or the IDE). Alternatively, the generated program can be built manually. To do so, in the src-gen/ProgramName directory, run:

    \n
    mkdir build && cd build\ncmake ../\nmake\n
    \n

    If cmake is disabled, gcc is directly invoked after code generation by default. In this case, additional target properties, such as compiler and flags can be used to gain finer control over the compilation process.

    \n
    \n

    cmake-include

    \n
    \n

    The $target-language$ target does not support the cmake-include target option.

    \n
    \n
    \n
    target C {\n    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]\n};\n
    \n
    target Cpp {\n    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]\n};\n
    \n

    This will optionally append additional custom CMake instructions to the generated CMakeLists.txt, drawing these instructions from the specified text files (e.g, foo.txt). The specified files are resolved using the same file search algorithm as used for the files target parameter. Those files will be copied into the src-gen directory that contains the generated sources. This is done to make the generated code more portable (a feature that is useful in federated execution).

    \n

    The cmake-include target property can be used, for example, to add dependencies on various packages (e.g., by using the find_package and target_link_libraries commands).

    \n

    A CMake variable called ${LF_MAIN_TARGET} can be used in the included text file(s) for convenience. This variable will contain the name of the CMake target (i.e., the name of the main reactor). For example, a foo.txt file can contain:

    \n
    find_package(m REQUIRED) # Finds the m library\n\ntarget_link_libraries( ${LF_MAIN_TARGET} m ) # Links the m library\n
    \n

    foo.txt can then be included by specifying it as an argument to cmake-include.

    \n

    Note: For a general tutorial on finding packages in CMake, see this external documentation entry. For a list of CMake find modules, see this.

    \n

    The cmake-include parameter works in conjunction with the $import$ statement. If any imported .lf file has cmake-include among its target properties, the specified text files will be appended to the current list of cmake-includes. These files will be resolved relative to the imported .lf file using the same search procedure as for the files parameter. This helps resolve dependencies in imported reactors automatically and makes the code more modular.

    \n
    \n
    \n

    CMakeInclude.lf is an example that uses this feature. A more sophisticated example of the usage of this target parameter can be found in Rhythm.lf. A distributed version can be found in DistributedCMakeInclude.lf is a test that uses this feature.

    \n

    Note: For federated execution, both cmake-include and files are kept separate for each federate as much as possible. This means that if one federate is imported, or uses an imported reactor that other federates don’t use, it will only have access to cmake-includes and files defined in the main .lf file, plus the selectively imported .lf files. DistributedCMakeIncludeSeparateCompile.lf is a test that demonstrates this feature.

    \n
    \n
    \n

    See AsyncCallback.lf for an example.

    \n
    \n

    compiler

    \n
    \n

    The $target-language$ target does not support the compiler target option.

    \n
    \n
    \n

    This parameter is a string giving the name of the C compiler to use.\nIt is used only when cmake is set to false. For example:

    \n
    target C {\n    cmake: false,\n    compiler: "cc",\n};\n
    \n

    The compiler option here specifies to use cc rather than gcc.

    \n
    \n
    \n

    This parameter is a string giving the name of the C++ compiler to use. Normally\nCMake selects the best compiler for your system, but you can use this parameter\nto point it to your preferred C++ compiler.

    \n
    \n

    docker

    \n
    \n

    This option takes a boolean argument (default is false).

    \n

    If true, a docker file will be generated in the unfederated case.

    \n

    In the federated case, a docker file for each federate will be generated. A docker-compose file will also be generated for the top-level federated reactor.

    \n
    \n
    \n

    The $target-language$ target does not support the docker target option.

    \n
    \n

    external-runtime-path

    \n
    \n

    The $target-language$ target does not support the external-runtime-path target option.

    \n
    \n
    \n

    This option takes a string argument given a path to a pre-compiled external runtime library to use instead of the default one.

    \n
    \n
    \n

    This option takes a path as string argument to a folder containing an alternative runtime crate to use instead of the default one.

    \n
    \n

    export-dependency-graph

    \n
    \n

    The $target-language$ target does not support the export-dependency-graph target option.

    \n
    \n
    \n

    This parameter takes arguments true or false to specify whether the compiled binary will export its internal dependency graph as a dot graph when executed. This is a debugging utility.

    \n
    \n

    If a CLI is generated, the target property is ignored, and the user should instead use the --export-graph flag of the generated program.

    \n
    \n
    \n

    fast

    \n

    By default, the execution of a Lingua Franca program is slowed down, if necessary, so that logical time does not elapse faster than physical time. If you wish to execute the program as fast as possible without this constraint, then specify the fast target parameter with value true.

    \n

    files

    \n
    \n

    The $target-language$ target does not support the files option.

    \n
    \n
    \n

    The files target parameter specifies array of files or directories to be copied to the directory that contains the generated sources.

    \n
    target C {\n    files: ["file1", "file2", ...]\n}\n
    \n
    target Python {\n    files: ["file1", "file2", ...]\n}\n
    \n

    The lookup procedure for these files and directories is as follows:

    \n

    1- Search in the directory containing the .lf file that has the target directive.

    \n

    2- If not found, search in LF_CLASSPATH.

    \n

    3- If still not found, search in CLASSPATH.

    \n

    4- If still not found, search for the file as a resource. Specifically, if a file begins with a forward slash /, then the path is assumed to be relative to the root directory of the Lingua Franca source tree.

    \n
    \n

    For example, if you wish to use audio on a Mac, you can specify:

    \n
    target C {\n    files: ["/lib/C/util/audio_loop_mac.c", "/lib/C/util/audio_loop.h"]\n}\n
    \n

    Your preamble code can then include these files, for example:

    \n
    preamble {=\n    #include "audio_loop_mac.c"\n=}\n
    \n

    Your reactions can then invoke functions defined in that .c file.

    \n

    Sometimes, you will need access to these files from target code in a reaction. For the C target (at least), the generated program will contain a line like this:

    \n
        #define TARGET_FILES_DIRECTORY "path"\n
    \n

    where path is the full path to the directory containing these files. This can be used in reactions, for example, to read those files.

    \n
    \n

    Moreover, the files target specification works in conjunction with the $import$ statement. If a .lf file is imported and has designated supporting files using the files target parameter, those files will be resolved relative to that .lf file and copied to the directory that contains the generated sources. This is done to make code that imports other .lf files more modular. Rhythm.lf is an example that demonstrates most of these features.

    \n
    \n

    flags

    \n
    \n

    The $target-language$ target does not support the flags parameter.

    \n
    \n
    \n

    This parameter is a list of strings giving additional arguments to pass to the target language compiler.\nIt is used only when cmake is set to false. For example:

    \n
    target C {\n    cmake: false,\n    flags: ["-g", "-I/usr/local/include", "-L/usr/local/lib", "-lpaho-mqtt3c"],\n};\n
    \n

    The flags option specifies to include debug information in the compiled code (-g); a directory to search for include files (-I/usr/local/include); a directory to search for library files (-L/usr/local/lib); a library to link with (-lpaho-mqtt3c, which will link with file libpaho-mqtt3c.so).

    \n

    Note: Using the flags standard parameter when cmake is enabled is strongly discouraged, although supported. Flags are compiler-specific, and thus interfere with CMake’s ability to find the most suitable compiler for each platform. In a similar fashion, we recommend against the use of the compiler standard parameter for the same reason. A better solution is to provide a cmake-include file, as described next.

    \n
    \n

    logging

    \n
    \n

    The $target-language$ target does not support the logging parameter.

    \n
    \n
    \n

    By default, when executing a generated Lingua Franca program, error messages, warnings, and informational messages are printed to standard out. You can get additional information printed by setting the logging parameter to LOG or DEBUG (or log or debug). The latter is more verbose. If you set the logging parameter to warn, then warnings and errors will be printed, but informational messages will not (e.g. message produced using the info_print utility function). If you set logging to error, then warning messages will also not be printed.

    \n

    The C and Python targets also support tracing, which outputs binary traces of an execution rather than human-readable text and is designed to have minimal impact on performance.

    \n
    \n
    \n

    The logging option is one of error, warn, info, log or debug. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where error < warn < info < log < debug. The default value is info, which means that messages log or debug messages will not print.

    \n
    \n
    \n

    The logging option is one of ERROR, WARN, INFO, LOG or DEBUG. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where ERROR < WARN < INFO < LOG < DEBUG. The default value is INFO, which means that messages tagged LOG and DEBUG will not print. Internally this is handled by the ulog module.

    \n
    \n

    no-compile

    \n
    \n

    The $target-language$ target does not support the no-compile target option.

    \n
    \n
    \n

    If true, then do not invoke a target language compiler nor cmake. Just generate code.

    \n
    \n

    no-runtime-validation

    \n
    \n

    The $target-language$ target does not support the no-runtime-validation target option.

    \n
    \n
    \n

    This parameter takes value true or false (the default). If this is set to true, then all runtime checks in reactor-cpp will be disabled. This brings a slight performance boost but should be used with care and only on tested programs.

    \n
    \n

    protobufs

    \n
    \n

    The $target-language$ target does not support the protobufs target option.

    \n
    \n
    \n

    Protobufs is a serialization protocol by which data in a target language can be copied over the network to a remote location. The protobufs target parameter gives an array of .proto files that are to be compiled and included in the generated code. For an example, see\nPersonProtocolBuffers.lf\nPersonProtocolBuffers.lf.

    \n
    \n

    runtime-version

    \n
    \n

    The $target-language$ target does not support the runtime-version target option.

    \n
    \n
    \n

    This argument takes a string (with quotation marks) containing any tag, branch name, or git hash in the reactor-cpp repository. This will specify the version of the runtime library that the compiled binary will link against.

    \n
    \n
    \n

    rust-include

    \n

    This specifies a set of Rust modules in the generated project. See Linking support files.

    \n

    scheduler

    \n

    This specifies the scheduler to use. SeeTarget Language Details.

    \n

    single-file-project

    \n

    If true, enables single-file project layout.

    \n
    \n

    threading

    \n
    \n

    The $target-language$ target does not support the threading target option.

    \n
    \n
    \n

    If threading is disabled (by setting threading to false), then no thread library is used, and the lf_schedule() function is not thread safe. This setting is incompatible with asynchronously scheduling any physical actions and hence this parameter will be ignored for programs that have physical actions.\nSee workers.

    \n
    \n
    \n

    The Python target uses the single threaded C runtime by default but will switch to the multithreaded C runtime if a physical action is detected. This target property can be used to override this behavior.

    \n
    \n
    \n

    Boolean flag (either true (default) or false) that controls if the project is to be compiled with support for multi-threading.

    \n

    See workers.

    \n
    \n

    timeout

    \n

    A time value (with units) specifying the logical stop time of execution. See Termination.

    \n

    workers

    \n
    \n

    The $target-language$ target does not support the workers target option.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. If threading is turned on (the default, see threading), then the generated code will use a target platform thread library and generate multi-threaded code. This can transparently execute reactions that have no dependence on one another in parallel on multiple cores. By default, threading is turned on, and the workers property is set to 0, which means that the number of workers is determined by the runtime system. Typically, it will be set to the number of cores on the machine running the code. To use a different number of worker threads, give a positive integer for this target parameter.

    \n

    With value 0, the runtime engine is free to choose the number of worker threads to use. Typically, this will equal the number of hardware threads on the machine on which the Lingua Franca code generator is run.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. With value 0 (the default), the runtime engine is free to choose the number of worker threads to use. In the $target-language$ target, the runtime system will determine the number of hardware threads on the machine on which the program is run and set the number of worker threads equal to that number.

    \n

    If the workers property is set to 1, the scheduler will not create any worker threads and instead inline the execution of reactions. This is an optimization and avoids any unnecessary synchronization. Note that, in contrast to the C target, the single threaded implementation is still thread safe and asynchronous reaction scheduling is supported.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. Note, however, that the Python core is unable to execute safely in parallel on multiple cores. As a consequence, at execution time, each reaction invocation will acquire a mutual exclusion lock before executing. Hence, there is little point in setting this to any number greater than 1.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. With value 0 (the default), the runtime engine is free to choose the number of worker threads to use and the number of worker threads may vary over time.

    \n
    \n

    Command-Line Arguments

    \n
    \n

    The generated executable may feature a command-line interface (CLI), if it uses the cargo-features: [\"cli\"] target property. When that feature is enabled:

    \n
      \n
    • some target properties become settable at runtime:\n
        \n
      • --timeout <time value>: override the default timeout mentioned as a target property. The syntax for times is just like the LF one (e.g. 1msec, \"2 seconds\").
      • \n
      • --workers <number>: override the default worker count mentioned as a target property. This option is ignored unless the runtime crate has been built with the feature parallel-runtime.
      • \n
      • --export-graph: export the dependency graph (corresponds to export-dependency-graph target property). This is a flag, i.e., absent means false, present means true. This means the value of the target property is ignored and not used as default.
      • \n
      • --log-level: corresponds to the logging target property, but note that the levels have different meanings, and the target property is ignored. See Logging levels.
      • \n
      \n
    • \n
    • parameters of the main reactor are translated to CLI parameters.\n
        \n
      • Each LF parameter named param corresponds to a CLI parameter named --main-param. Underscores in the LF parameter name are replaced by hyphens.
      • \n
      • The type of each parameters must implement the trait FromStr.
      • \n
      \n
    • \n
    \n

    When the cli feature is disabled, the parameters of the main reactor will each assume their default value.

    \n
    \n
    \n

    The generated C program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast [true | false]: Specifies whether to wait for physical time to match logical time. The default is false. If this is true, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout <duration> <units>: Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those.
    • \n
    • -w, --workers <n>: Executed using worker threads if possible. This option is ignored in the single-threaded version. That is, it is ignored if a threading option was given in the target properties with value false.
    • \n
    • -i, --id <n>: The ID of the federation that this reactor will join.
    • \n
    \n

    Any other command-line arguments result in printing the above information.

    \n
    \n
    \n

    The generated C++ program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast: If set, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout '<duration> <units>': Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those.
    • \n
    • -w, --workers <n>: Use n worker threads for executing reactions.
    • \n
    • --help: Print the above information.
    • \n
    \n

    If the main reactor declares parameters, these parameters will appear as additional CLI options that can be specified when invoking the binary (see Using Parameters).

    \n
    \n
    \n

    The Python target does not currently support any command-line arguments. You must specify properties as target parameters.

    \n
    \n
    \n

    In the TypeScript target, the generated JavaScript program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast [true | false]: Specifies whether to wait for physical time to match logical time. The default is false. If this is true, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout '<duration> <units>': Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those. For the duration and units of a timeout argument to be parsed correctly as a single value, these should be specified in quotes with no leading or trailing space (e.g. ‘5 sec’).
    • \n
    • -k, --keepalive [true | false]: Specifies whether to stop execution if there are no events to process. This defaults to false, meaning that the program will stop executing when there are no more events on the event queue. If you set this to true, then the program will keep executing until either the timeout logical time is reached or the program is externally killed. If you have physical actions, it usually makes sense to set this to true.
    • \n
    • -l, --logging [ERROR | WARN | INFO | LOG | DEBUG]: The level of logging messages from the reactor-ts runtime to to print to the console. Messages tagged with a given type (error, warn, etc.) will print if this argument is greater than or equal to the level of the message (ERROR < WARN < INFO < LOG < DEBUG).
    • \n
    • -h, --help: Print this usage guide. The program will not execute if this flag is present.
    • \n
    \n

    If provided, a command line argument will override whatever value the corresponding target property had specified in the source .lf file.

    \n

    Command line options are parsed by the command-line-arguments module with these rules. For example

    \n
    node <LF_file_name>/dist/<LF_file_name>.js -f false --keepalive=true -o '4 sec' -l INFO\n
    \n

    is a valid setting.

    \n

    Any errors in command-line arguments result in printing the above information. The program will not execute if there is a parsing error for command-line arguments.

    \n

    Custom Command-Line Arguments

    \n

    User-defined command-line arguments may be created by giving the main reactor parameters. Assigning the main reactor a parameter of type string, number, boolean, or time will add an argument with corresponding name and type to the generated program’s command-line-interface. Custom arguments will also appear in the generated program’s usage guide (from the --help option). If the generated program is executed with a value specified for a custom command-line argument, that value will override the default value for the corresponding parameter. Arguments typed string, number, and boolean are parsed in the expected way, but time arguments must be specified on the command line like the --timeout property as '<duration> <units>' (in quotes).

    \n

    Note: Custom arguments may not have the same names as standard arguments like timeout or keepalive.

    \n

    For example this reactor has a custom command line argument named customArg of type number and default value 2:

    \n
    target TypeScript;\nmain reactor clArg(customArg:number(2)) {\n    reaction (startup) {=\n        console.log(customArg);\n    =}\n}\n
    \n

    If this reactor is compiled from the file simpleCLArgs.lf, executing

    \n
    node simpleCLArgs/dist/simpleCLArgs.js\n
    \n

    outputs the default value 2. But running

    \n
    node simpleCLArgs/dist/simpleCLArgs.js --customArg=42\n
    \n

    outputs 42. Additionally, we can view documentation for the custom command line argument with the --help command.

    \n
    node simpleCLArgs/dist/simpleCLArgs.js -h\n
    \n

    The program will generate the standard usage guide, but also

    \n
    --customArg '<duration> <units>'                    Custom argument. Refer to\n                                                      <path>/simpleCLArgs.lf\n                                                      for documentation.
    \n

    Additional types for Custom Command-Line Arguments

    \n

    Main reactor parameters that are not typed string, number, boolean, or time will not create custom command-line arguments. However, that doesn’t mean it is impossible to obtain other types from the command line, just use a string and specify how the parsing is done yourself. See below for an example of a reactor that parses a custom command-line argument of type string into a state variable of type Array<number> using JSON.parse and a user-defined type guard.

    \n
    target TypeScript;\nmain reactor customType(arrayArg:string("")) {\n    preamble {=\n        function isArrayOfNumbers(x: any): x is Array<number> {\n            for (let item of x) {\n                if (typeof item !== "number") {\n                    return false;\n                }\n            }\n            return true;\n        }\n    =}\n    state foo:{=Array<number>=}({=[]=});\n    reaction (startup) {=\n        let parsedArgument = JSON.parse(customType);\n        if (isArrayOfNumbers(parsedArgument)) {\n            foo = parsedArgument;\n            }\n        else {\n            throw new Error("Custom command line argument is not an array of numbers.");\n        }\n        console.log(foo);\n    =}\n}\n
    \n
    ","headings":[{"value":"Summary of Parameters","depth":1},{"value":"auth","depth":2},{"value":"build","depth":2},{"value":"build-type","depth":2},{"value":"cargo-dependencies","depth":2},{"value":"cargo-features","depth":2},{"value":"cmake","depth":2},{"value":"cmake-include","depth":2},{"value":"compiler","depth":2},{"value":"docker","depth":2},{"value":"external-runtime-path","depth":2},{"value":"export-dependency-graph","depth":2},{"value":"fast","depth":2},{"value":"files","depth":2},{"value":"flags","depth":2},{"value":"logging","depth":2},{"value":"no-compile","depth":2},{"value":"no-runtime-validation","depth":2},{"value":"protobufs","depth":2},{"value":"runtime-version","depth":2},{"value":"rust-include","depth":2},{"value":"scheduler","depth":2},{"value":"single-file-project","depth":2},{"value":"threading","depth":2},{"value":"timeout","depth":2},{"value":"workers","depth":2},{"value":"Command-Line Arguments","depth":1},{"value":"Custom Command-Line Arguments","depth":3},{"value":"Additional types for Custom Command-Line Arguments","depth":3}],"frontmatter":{"permalink":"/docs/handbook/target-declaration","title":"Target Declaration","oneline":"The target declaration and its parameters in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Target Language Details","oneline":"Detailed reference for each target langauge.","permalink":"/docs/handbook/target-language-details"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Tracing","oneline":"Tracing (preliminary)","permalink":"/docs/handbook/tracing"}}}},"pageContext":{"id":"3-target-declaration","slug":"/docs/handbook/target-declaration","repoPath":"/packages/documentation/copy/en/reference/Target Declaration.md","previousID":"15e2a8e5-dd68-55e9-839f-18c6d342e73c","nextID":"3f4000b5-1133-5c34-807d-29c05884f149","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/target-declaration","result":{"data":{"markdownRemark":{"id":"e4812e4e-df22-54fb-9a27-a82edb11ac44","excerpt":"$page-showing-target$ Every Lingua Franca program begins with a statement of this form: The gives the name of some Lingua Franca target language, which…","html":"

    $page-showing-target$

    \n

    Every Lingua Franca program begins with a statement of this form:

    \n
        target <name> <parameters>\n
    \n

    The <name> gives the name of some Lingua Franca target language, which is the language in which reactions are written. This is also the language of the program(s) generated by the Lingua Franca compiler. The target languages currently supported are C, C++, Python, TypeScript, and Rust.

    \n

    Summary of Parameters

    \n

    A target specification may have optional parameters, the names and values of which depend on which specific target you are using. Each parameter is a key-value pair, where the supported keys are a subset of the following:

    \n
      \n
    • auth: A boolean specifying to apply authorization between RTI and federates when federated execution.
    • \n
    • build: A command to execute after code generation instead of the default compile command.
    • \n
    • build-type: One of Release (the default), Debug, RelWithDebInfo and MinSizeRel.
    • \n
    • cargo-dependencies: (Rust only) list of dependencies to include in the generated Cargo.toml file.
    • \n
    • cargo-features: (Rust only) List of string names of features to include.
    • \n
    • cmake: Whether to use cmake for building.
    • \n
    • cmake-include: List of paths to cmake files to guide compilation.
    • \n
    • compiler: A string giving the name of the target language compiler to use.
    • \n
    • docker: A boolean to generate a Dockerfile.
    • \n
    • external-runtime-path: Specify a pre-compiled external runtime library located to link to instead of the default.
    • \n
    • export-dependency-graph: To export the reaction dependency graph as a dot graph (for debugging).
    • \n
    • fast: A boolean specifying to execute as fast as possible without waiting for physical time to match logical time.
    • \n
    • files: An array of paths to files or directories to be copied to the directory that contains the generated sources.
    • \n
    • flags: An arrays of strings giving options to be passed to the target compiler.
    • \n
    • logging: An indicator of how much information to print when executing the program.
    • \n
    • no-compile: If true, then do not invoke a target language compiler. Just generate code.
    • \n
    • no-runtime-validation: If true, disable runtime validation.
    • \n
    • protobufs: An array of .proto files that are to be compiled and included in the generated code.
    • \n
    • runtime-version: Specify which version of the runtime system to use.
    • \n
    • rust-include: (Rust only) A set of Rust modules in the generated project.
    • \n
    • scheduler: (C only) Specification of the scheduler to us.
    • \n
    • single-file-project: (Rust only) If true, enables single-file project layout.
    • \n
    • threading: Whether to use multiple threads.
    • \n
    • timeout: A time value (with units) specifying the logical stop time of execution. See Termination.
    • \n
    • workers: If using multiple threads, how many worker threads to create.
    • \n
    \n

    Not all targets support all target parameters. The full set of target parameters supported by the $target-language$ target is:

    \n
    target C {\n    auth: <true or false>\n    build: <string>,\n    build-type: <Release, Debug, RelWithDebInfo, or MinSizeRel>,\n    cmake: <true or false>,\n    cmake-include: <string or list of strings>,\n    compiler: <string>,\n    docker: <true or false>,\n    fast: <true or false>,\n    files: <string or list of strings>,\n    flags: <string or list of strings>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    protobufs: <string or list of strings>,\n    threading: <true or false>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target Cpp {\n    build-type: <Release, Debug, RelWithDebInfo, or MinSizeRel>,\n    cmake-include: <string or list of strings>,\n    external-runtime-path: <string>,\n    export-dependency-graph <true or false>,\n    fast: <true or false>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    no-runtime-validation: <true or false>,\n    runtime-version: <string>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target Python {\n    docker: <true or false>,\n    fast: <true or false>,\n    files: <string or list of strings>,\n    logging: <error, warning, info, log, debug>,\n    no-compile: <true or false>,\n    protobufs: <string or list of strings>,\n    threading: <true or false>,\n    timeout: <time>,\n    workers: <non-negative integer>,\n};\n
    \n
    target TypeScript {\n    docker: <true or false>,\n    fast: <true or false>,\n    logging: <ERROR, WARN, INFO, LOG, or DEBUG>,\n    timeout: <time>,\n};\n
    \n
    target Rust {\n    build-type: <Debug, Release, RelWithDebInfo, or MinSizeRel>,\n    cargo-features: <array of strings>,\n    cargo-dependencies: <list of key-value pairs>,\n    export-dependency-graph: <true or false>,\n    rust-include: <array of strings>,\n    single-file-project: <true or false>,\n    timeout: <time value>,\n}\n
    \n
    \n

    For example:

    \n
    target C {\n    cmake: false,\n    compiler: "cc",\n    flags: "-O3",\n    fast: true,\n    logging: log,\n    timeout: 1 secs,\n};\n
    \n

    This specifies to use compiler cc instead of the default gcc, to use optimization level 3, to execute as fast as possible, and to exit execution when logical time has advanced to 10 seconds. Note that all events at logical time 10 seconds greater than the starting logical time will be executed.

    \n
    \n

    The comma on the last parameter is optional, as is the semicolon on the last line.\nA target may support overriding the target parameters on the command line when invoking the compiled program.

    \n

    auth

    \n
    \n

    The $target-language$ target does not currently support the auth target option.

    \n
    \n
    \n

    The detailed documentation is here.

    \n
    \n

    build

    \n
    \n

    The $target-language$ target does not currently support the build target option.

    \n
    \n
    \n

    A command to execute after code generation instead of the default compile command. This is either a single string or an array of strings. The specified command(s) will be executed an environment that has the following environment variables defined:

    \n
      \n
    • LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked.
    • \n
    • LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled.
    • \n
    • LF_PACKAGE_DIRECTORY: The directory for the root of the project or package (normally the directory above the src directory).
    • \n
    • LF_SOURCE_GEN_DIRECTORY: The directory in which generated files are placed.
    • \n
    • LF_BIN_DIRECTORY: The directory into which to put binaries.
    • \n
    \n

    The command will be executed in the same directory as the .lf file being compiled. For example, if you specify

    \n
    target C {\n    build: "./compile.sh Foo"\n}\n
    \n

    then instead of invoking the C compiler after generating code, the code generator will invoke your compile.sh script, which could look something like this:

    \n
    #!/bin/bash\n# Build the generated code.\ncd ${LF_SOURCE_GEN_DIRECTORY}\ncmake .\nmake\n\n# Move the executable to the bin directory.\nmv $1 ${LF_BIN_DIRECTORY}\n\n# Invoke the executable.\n${LF_BIN_DIRECTORY}/$1\n\n# Plot the results, which have appeared in the src-gen directory.\ngnuplot ${LF_SOURCE_DIRECTORY}/$1.gnuplot\nopen $1.pdf\n
    \n

    The first few lines of this script do the same thing that is normally done when there is no build option in the target. Specifically, they use cmake to create a makefile, invoke make, and then move the executable to the bin directory. The next line, however, gives new functionality. It executes the compiled code! The final two lines assume that the program has produced a file with data to be plotted and use gnuplot to plot the data. This requires, of course, that you have gnuplot installed, and that there is a file called Foo.gnuplot in the same directory as Foo.lf. The file Foo.gnuplot contains the commands to plot the data, and might look something like the following:

    \n
    set title 'My Title'\nset xrange [0:3]\nset yrange [-2:2]\nset xlabel "Time (seconds)"\nset terminal pdf size 5, 3.5\nset output 'Foo.pdf'\nplot 'mydata1.data' using 1:2 with lines, \\\n     'mydata2.data' using 1:2 with lines\n
    \n

    This assumes that your program has written two files, mydata1.data and mydata2.data containing two columns, time and value.

    \n
    \n

    build-type

    \n
    \n

    The $target-language$ target does not currently support the build-type target option.

    \n
    \n
    \n

    This parameter works with cargo to specify how to compile the code. The following options are supported:

    \n
      \n
    • Release: Optimization is turned on and debug information is missing.
    • \n
    • Debug: Debug information is included in the executable.
    • \n
    • RelWithDebInfo: Optimization with debug information.
    • \n
    • MinSizeRel: Optimize for smallest size.
    • \n
    \n

    This defaults to Release.

    \n
    \n
    \n

    This parameter works with cmake to specify how to compile the code. The following options are supported:

    \n
      \n
    • Release: Optimization is turned on and debug information is missing.
    • \n
    • Debug: Debug information is included in the executable.
    • \n
    • RelWithDebInfo: Optimization with debug information.
    • \n
    • MinSizeRel: Optimize for smallest size.
    • \n
    \n

    This defaults to Release.

    \n
    \n
    \n

    cargo-dependencies

    \n

    This is a list of dependencies to include in the generated Cargo.toml file. The value of this parameter is a map of package name to dependency-spec.

    \n

    Here is an example for defining dependencies:

    \n
    target Rust {\n    cargo-dependencies: {\n        fxhash: {\n            version: "0.2.1",\n        },\n        rand: {\n            version: "0.8",\n            features: ["small_rng"],\n        },\n    }\n};\n
    \n

    cargo-features

    \n

    This is a list of features of the generated crate. Supported are:

    \n\n
    \n

    cmake

    \n
    \n

    The $target-language$ target does not support the cmake target option.

    \n
    \n
    \n

    The $target-language$ target does not support the cmake target option because it always uses cmake.

    \n
    \n
    \n
    target C {\n    cmake: <true or false>\n};\n
    \n

    This will enable or disable the CMake-based build system (the default is true). Enabling the CMake build system will result in a CMakeLists.txt being generated in the src-gen directory. This CMakeLists.txt is then used when cmake is invoked by the LF runtime (either the lfc or the IDE). Alternatively, the generated program can be built manually. To do so, in the src-gen/ProgramName directory, run:

    \n
    mkdir build && cd build\ncmake ../\nmake\n
    \n

    If cmake is disabled, gcc is directly invoked after code generation by default. In this case, additional target properties, such as compiler and flags can be used to gain finer control over the compilation process.

    \n
    \n

    cmake-include

    \n
    \n

    The $target-language$ target does not support the cmake-include target option.

    \n
    \n
    \n
    target C {\n    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]\n};\n
    \n
    target Cpp {\n    cmake-include: ["relative/path/to/foo.txt", "relative/path/to/bar.txt", ...]\n};\n
    \n

    This will optionally append additional custom CMake instructions to the generated CMakeLists.txt, drawing these instructions from the specified text files (e.g, foo.txt). The specified files are resolved using the same file search algorithm as used for the files target parameter. Those files will be copied into the src-gen directory that contains the generated sources. This is done to make the generated code more portable (a feature that is useful in federated execution).

    \n

    The cmake-include target property can be used, for example, to add dependencies on various packages (e.g., by using the find_package and target_link_libraries commands).

    \n

    A CMake variable called ${LF_MAIN_TARGET} can be used in the included text file(s) for convenience. This variable will contain the name of the CMake target (i.e., the name of the main reactor). For example, a foo.txt file can contain:

    \n
    find_package(m REQUIRED) # Finds the m library\n\ntarget_link_libraries( ${LF_MAIN_TARGET} m ) # Links the m library\n
    \n

    foo.txt can then be included by specifying it as an argument to cmake-include.

    \n

    Note: For a general tutorial on finding packages in CMake, see this external documentation entry. For a list of CMake find modules, see this.

    \n

    The cmake-include parameter works in conjunction with the $import$ statement. If any imported .lf file has cmake-include among its target properties, the specified text files will be appended to the current list of cmake-includes. These files will be resolved relative to the imported .lf file using the same search procedure as for the files parameter. This helps resolve dependencies in imported reactors automatically and makes the code more modular.

    \n
    \n
    \n

    CMakeInclude.lf is an example that uses this feature. A more sophisticated example of the usage of this target parameter can be found in Rhythm.lf. A distributed version can be found in DistributedCMakeInclude.lf is a test that uses this feature.

    \n

    Note: For federated execution, both cmake-include and files are kept separate for each federate as much as possible. This means that if one federate is imported, or uses an imported reactor that other federates don’t use, it will only have access to cmake-includes and files defined in the main .lf file, plus the selectively imported .lf files. DistributedCMakeIncludeSeparateCompile.lf is a test that demonstrates this feature.

    \n
    \n
    \n

    See AsyncCallback.lf for an example.

    \n
    \n

    compiler

    \n
    \n

    The $target-language$ target does not support the compiler target option.

    \n
    \n
    \n

    This parameter is a string giving the name of the C compiler to use.\nIt is used only when cmake is set to false. For example:

    \n
    target C {\n    cmake: false,\n    compiler: "cc",\n};\n
    \n

    The compiler option here specifies to use cc rather than gcc.

    \n
    \n
    \n

    This parameter is a string giving the name of the C++ compiler to use. Normally\nCMake selects the best compiler for your system, but you can use this parameter\nto point it to your preferred C++ compiler.

    \n
    \n

    docker

    \n
    \n

    This option takes a boolean argument (default is false).

    \n

    If true, a docker file will be generated in the unfederated case.

    \n

    In the federated case, a docker file for each federate will be generated. A docker-compose file will also be generated for the top-level federated reactor.

    \n
    \n
    \n

    The $target-language$ target does not support the docker target option.

    \n
    \n

    external-runtime-path

    \n
    \n

    The $target-language$ target does not support the external-runtime-path target option.

    \n
    \n
    \n

    This option takes a string argument given a path to a pre-compiled external runtime library to use instead of the default one.

    \n
    \n
    \n

    This option takes a path as string argument to a folder containing an alternative runtime crate to use instead of the default one.

    \n
    \n

    export-dependency-graph

    \n
    \n

    The $target-language$ target does not support the export-dependency-graph target option.

    \n
    \n
    \n

    This parameter takes arguments true or false to specify whether the compiled binary will export its internal dependency graph as a dot graph when executed. This is a debugging utility.

    \n
    \n

    If a CLI is generated, the target property is ignored, and the user should instead use the --export-graph flag of the generated program.

    \n
    \n
    \n

    fast

    \n

    By default, the execution of a Lingua Franca program is slowed down, if necessary, so that logical time does not elapse faster than physical time. If you wish to execute the program as fast as possible without this constraint, then specify the fast target parameter with value true.

    \n

    files

    \n
    \n

    The $target-language$ target does not support the files option.

    \n
    \n
    \n

    The files target parameter specifies array of files or directories to be copied to the directory that contains the generated sources.

    \n
    target C {\n    files: ["file1", "file2", ...]\n}\n
    \n
    target Python {\n    files: ["file1", "file2", ...]\n}\n
    \n

    The lookup procedure for these files and directories is as follows:

    \n

    1- Search in the directory containing the .lf file that has the target directive.

    \n

    2- If not found, search in LF_CLASSPATH.

    \n

    3- If still not found, search in CLASSPATH.

    \n

    4- If still not found, search for the file as a resource. Specifically, if a file begins with a forward slash /, then the path is assumed to be relative to the root directory of the Lingua Franca source tree.

    \n
    \n

    For example, if you wish to use audio on a Mac, you can specify:

    \n
    target C {\n    files: ["/lib/C/util/audio_loop_mac.c", "/lib/C/util/audio_loop.h"]\n}\n
    \n

    Your preamble code can then include these files, for example:

    \n
    preamble {=\n    #include "audio_loop_mac.c"\n=}\n
    \n

    Your reactions can then invoke functions defined in that .c file.

    \n

    Sometimes, you will need access to these files from target code in a reaction. For the C target (at least), the generated program will contain a line like this:

    \n
        #define TARGET_FILES_DIRECTORY "path"\n
    \n

    where path is the full path to the directory containing these files. This can be used in reactions, for example, to read those files.

    \n
    \n

    Moreover, the files target specification works in conjunction with the $import$ statement. If a .lf file is imported and has designated supporting files using the files target parameter, those files will be resolved relative to that .lf file and copied to the directory that contains the generated sources. This is done to make code that imports other .lf files more modular. Rhythm.lf is an example that demonstrates most of these features.

    \n
    \n

    flags

    \n
    \n

    The $target-language$ target does not support the flags parameter.

    \n
    \n
    \n

    This parameter is a list of strings giving additional arguments to pass to the target language compiler.\nIt is used only when cmake is set to false. For example:

    \n
    target C {\n    cmake: false,\n    flags: ["-g", "-I/usr/local/include", "-L/usr/local/lib", "-lpaho-mqtt3c"],\n};\n
    \n

    The flags option specifies to include debug information in the compiled code (-g); a directory to search for include files (-I/usr/local/include); a directory to search for library files (-L/usr/local/lib); a library to link with (-lpaho-mqtt3c, which will link with file libpaho-mqtt3c.so).

    \n

    Note: Using the flags standard parameter when cmake is enabled is strongly discouraged, although supported. Flags are compiler-specific, and thus interfere with CMake’s ability to find the most suitable compiler for each platform. In a similar fashion, we recommend against the use of the compiler standard parameter for the same reason. A better solution is to provide a cmake-include file, as described next.

    \n
    \n

    logging

    \n
    \n

    The $target-language$ target does not support the logging parameter.

    \n
    \n
    \n

    By default, when executing a generated Lingua Franca program, error messages, warnings, and informational messages are printed to standard out. You can get additional information printed by setting the logging parameter to LOG or DEBUG (or log or debug). The latter is more verbose. If you set the logging parameter to warn, then warnings and errors will be printed, but informational messages will not (e.g. message produced using the info_print utility function). If you set logging to error, then warning messages will also not be printed.

    \n

    The C and Python targets also support tracing, which outputs binary traces of an execution rather than human-readable text and is designed to have minimal impact on performance.

    \n
    \n
    \n

    The logging option is one of error, warn, info, log or debug. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where error < warn < info < log < debug. The default value is info, which means that messages log or debug messages will not print.

    \n
    \n
    \n

    The logging option is one of ERROR, WARN, INFO, LOG or DEBUG. It specifies the level of diagnostic messages about execution to print to the console. A message will print if this parameter is greater than or equal to the level of the message, where ERROR < WARN < INFO < LOG < DEBUG. The default value is INFO, which means that messages tagged LOG and DEBUG will not print. Internally this is handled by the ulog module.

    \n
    \n

    no-compile

    \n
    \n

    The $target-language$ target does not support the no-compile target option.

    \n
    \n
    \n

    If true, then do not invoke a target language compiler nor cmake. Just generate code.

    \n
    \n

    no-runtime-validation

    \n
    \n

    The $target-language$ target does not support the no-runtime-validation target option.

    \n
    \n
    \n

    This parameter takes value true or false (the default). If this is set to true, then all runtime checks in reactor-cpp will be disabled. This brings a slight performance boost but should be used with care and only on tested programs.

    \n
    \n

    protobufs

    \n
    \n

    The $target-language$ target does not support the protobufs target option.

    \n
    \n
    \n

    Protobufs is a serialization protocol by which data in a target language can be copied over the network to a remote location. The protobufs target parameter gives an array of .proto files that are to be compiled and included in the generated code. For an example, see\nPersonProtocolBuffers.lf\nPersonProtocolBuffers.lf.

    \n
    \n

    runtime-version

    \n
    \n

    The $target-language$ target does not support the runtime-version target option.

    \n
    \n
    \n

    This argument takes a string (with quotation marks) containing any tag, branch name, or git hash in the reactor-cpp repository. This will specify the version of the runtime library that the compiled binary will link against.

    \n
    \n
    \n

    rust-include

    \n

    This specifies a set of Rust modules in the generated project. See Linking support files.

    \n

    scheduler

    \n

    This specifies the scheduler to use. SeeTarget Language Details.

    \n

    single-file-project

    \n

    If true, enables single-file project layout.

    \n
    \n

    threading

    \n
    \n

    The $target-language$ target does not support the threading target option.

    \n
    \n
    \n

    If threading is disabled (by setting threading to false), then no thread library is used, and the lf_schedule() function is not thread safe. This setting is incompatible with asynchronously scheduling any physical actions and hence this parameter will be ignored for programs that have physical actions.\nSee workers.

    \n
    \n
    \n

    The Python target uses the single threaded C runtime by default but will switch to the multithreaded C runtime if a physical action is detected. This target property can be used to override this behavior.

    \n
    \n
    \n

    Boolean flag (either true (default) or false) that controls if the project is to be compiled with support for multi-threading.

    \n

    See workers.

    \n
    \n

    timeout

    \n

    A time value (with units) specifying the logical stop time of execution. See Termination.

    \n

    workers

    \n
    \n

    The $target-language$ target does not support the workers target option.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. If threading is turned on (the default, see threading), then the generated code will use a target platform thread library and generate multi-threaded code. This can transparently execute reactions that have no dependence on one another in parallel on multiple cores. By default, threading is turned on, and the workers property is set to 0, which means that the number of workers is determined by the runtime system. Typically, it will be set to the number of cores on the machine running the code. To use a different number of worker threads, give a positive integer for this target parameter.

    \n

    With value 0, the runtime engine is free to choose the number of worker threads to use. Typically, this will equal the number of hardware threads on the machine on which the Lingua Franca code generator is run.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. With value 0 (the default), the runtime engine is free to choose the number of worker threads to use. In the $target-language$ target, the runtime system will determine the number of hardware threads on the machine on which the program is run and set the number of worker threads equal to that number.

    \n

    If the workers property is set to 1, the scheduler will not create any worker threads and instead inline the execution of reactions. This is an optimization and avoids any unnecessary synchronization. Note that, in contrast to the C target, the single threaded implementation is still thread safe and asynchronous reaction scheduling is supported.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. Note, however, that the Python core is unable to execute safely in parallel on multiple cores. As a consequence, at execution time, each reaction invocation will acquire a mutual exclusion lock before executing. Hence, there is little point in setting this to any number greater than 1.

    \n
    \n
    \n

    This parameter takes a non-negative integer and specifies the number of worker threads to execute the generated program. With value 0 (the default), the runtime engine is free to choose the number of worker threads to use and the number of worker threads may vary over time.

    \n
    \n

    Command-Line Arguments

    \n
    \n

    The generated executable may feature a command-line interface (CLI), if it uses the cargo-features: [\"cli\"] target property. When that feature is enabled:

    \n
      \n
    • some target properties become settable at runtime:\n
        \n
      • --timeout <time value>: override the default timeout mentioned as a target property. The syntax for times is just like the LF one (e.g. 1msec, \"2 seconds\").
      • \n
      • --workers <number>: override the default worker count mentioned as a target property. This option is ignored unless the runtime crate has been built with the feature parallel-runtime.
      • \n
      • --export-graph: export the dependency graph (corresponds to export-dependency-graph target property). This is a flag, i.e., absent means false, present means true. This means the value of the target property is ignored and not used as default.
      • \n
      • --log-level: corresponds to the logging target property, but note that the levels have different meanings, and the target property is ignored. See Logging levels.
      • \n
      \n
    • \n
    • parameters of the main reactor are translated to CLI parameters.\n
        \n
      • Each LF parameter named param corresponds to a CLI parameter named --main-param. Underscores in the LF parameter name are replaced by hyphens.
      • \n
      • The type of each parameters must implement the trait FromStr.
      • \n
      \n
    • \n
    \n

    When the cli feature is disabled, the parameters of the main reactor will each assume their default value.

    \n
    \n
    \n

    The generated C program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast [true | false]: Specifies whether to wait for physical time to match logical time. The default is false. If this is true, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout <duration> <units>: Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those.
    • \n
    • -w, --workers <n>: Executed using worker threads if possible. This option is ignored in the single-threaded version. That is, it is ignored if a threading option was given in the target properties with value false.
    • \n
    • -i, --id <n>: The ID of the federation that this reactor will join.
    • \n
    \n

    Any other command-line arguments result in printing the above information.

    \n
    \n
    \n

    The generated C++ program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast: If set, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout '<duration> <units>': Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those.
    • \n
    • -w, --workers <n>: Use n worker threads for executing reactions.
    • \n
    • --help: Print the above information.
    • \n
    \n

    If the main reactor declares parameters, these parameters will appear as additional CLI options that can be specified when invoking the binary (see Using Parameters).

    \n
    \n
    \n

    The Python target does not currently support any command-line arguments. You must specify properties as target parameters.

    \n
    \n
    \n

    In the TypeScript target, the generated JavaScript program understands the following command-line arguments, each of which has a short form (one character) and a long form:

    \n
      \n
    • -f, --fast [true | false]: Specifies whether to wait for physical time to match logical time. The default is false. If this is true, then the program will execute as fast as possible, letting logical time advance faster than physical time.
    • \n
    • -o, --timeout '<duration> <units>': Stop execution when logical time has advanced by the specified duration. The units can be any of nsec, usec, msec, sec, minute, hour, day, week, or the plurals of those. For the duration and units of a timeout argument to be parsed correctly as a single value, these should be specified in quotes with no leading or trailing space (e.g. ‘5 sec’).
    • \n
    • -k, --keepalive [true | false]: Specifies whether to stop execution if there are no events to process. This defaults to false, meaning that the program will stop executing when there are no more events on the event queue. If you set this to true, then the program will keep executing until either the timeout logical time is reached or the program is externally killed. If you have physical actions, it usually makes sense to set this to true.
    • \n
    • -l, --logging [ERROR | WARN | INFO | LOG | DEBUG]: The level of logging messages from the reactor-ts runtime to to print to the console. Messages tagged with a given type (error, warn, etc.) will print if this argument is greater than or equal to the level of the message (ERROR < WARN < INFO < LOG < DEBUG).
    • \n
    • -h, --help: Print this usage guide. The program will not execute if this flag is present.
    • \n
    \n

    If provided, a command line argument will override whatever value the corresponding target property had specified in the source .lf file.

    \n

    Command line options are parsed by the command-line-arguments module with these rules. For example

    \n
    node <LF_file_name>/dist/<LF_file_name>.js -f false --keepalive=true -o '4 sec' -l INFO\n
    \n

    is a valid setting.

    \n

    Any errors in command-line arguments result in printing the above information. The program will not execute if there is a parsing error for command-line arguments.

    \n

    Custom Command-Line Arguments

    \n

    User-defined command-line arguments may be created by giving the main reactor parameters. Assigning the main reactor a parameter of type string, number, boolean, or time will add an argument with corresponding name and type to the generated program’s command-line-interface. Custom arguments will also appear in the generated program’s usage guide (from the --help option). If the generated program is executed with a value specified for a custom command-line argument, that value will override the default value for the corresponding parameter. Arguments typed string, number, and boolean are parsed in the expected way, but time arguments must be specified on the command line like the --timeout property as '<duration> <units>' (in quotes).

    \n

    Note: Custom arguments may not have the same names as standard arguments like timeout or keepalive.

    \n

    For example this reactor has a custom command line argument named customArg of type number and default value 2:

    \n
    target TypeScript;\nmain reactor clArg(customArg:number(2)) {\n    reaction (startup) {=\n        console.log(customArg);\n    =}\n}\n
    \n

    If this reactor is compiled from the file simpleCLArgs.lf, executing

    \n
    node simpleCLArgs/dist/simpleCLArgs.js\n
    \n

    outputs the default value 2. But running

    \n
    node simpleCLArgs/dist/simpleCLArgs.js --customArg=42\n
    \n

    outputs 42. Additionally, we can view documentation for the custom command line argument with the --help command.

    \n
    node simpleCLArgs/dist/simpleCLArgs.js -h\n
    \n

    The program will generate the standard usage guide, but also

    \n
    --customArg '<duration> <units>'                    Custom argument. Refer to\n                                                      <path>/simpleCLArgs.lf\n                                                      for documentation.
    \n

    Additional types for Custom Command-Line Arguments

    \n

    Main reactor parameters that are not typed string, number, boolean, or time will not create custom command-line arguments. However, that doesn’t mean it is impossible to obtain other types from the command line, just use a string and specify how the parsing is done yourself. See below for an example of a reactor that parses a custom command-line argument of type string into a state variable of type Array<number> using JSON.parse and a user-defined type guard.

    \n
    target TypeScript;\nmain reactor customType(arrayArg:string("")) {\n    preamble {=\n        function isArrayOfNumbers(x: any): x is Array<number> {\n            for (let item of x) {\n                if (typeof item !== "number") {\n                    return false;\n                }\n            }\n            return true;\n        }\n    =}\n    state foo:{=Array<number>=}({=[]=});\n    reaction (startup) {=\n        let parsedArgument = JSON.parse(customType);\n        if (isArrayOfNumbers(parsedArgument)) {\n            foo = parsedArgument;\n            }\n        else {\n            throw new Error("Custom command line argument is not an array of numbers.");\n        }\n        console.log(foo);\n    =}\n}\n
    \n
    ","headings":[{"value":"Summary of Parameters","depth":1},{"value":"auth","depth":2},{"value":"build","depth":2},{"value":"build-type","depth":2},{"value":"cargo-dependencies","depth":2},{"value":"cargo-features","depth":2},{"value":"cmake","depth":2},{"value":"cmake-include","depth":2},{"value":"compiler","depth":2},{"value":"docker","depth":2},{"value":"external-runtime-path","depth":2},{"value":"export-dependency-graph","depth":2},{"value":"fast","depth":2},{"value":"files","depth":2},{"value":"flags","depth":2},{"value":"logging","depth":2},{"value":"no-compile","depth":2},{"value":"no-runtime-validation","depth":2},{"value":"protobufs","depth":2},{"value":"runtime-version","depth":2},{"value":"rust-include","depth":2},{"value":"scheduler","depth":2},{"value":"single-file-project","depth":2},{"value":"threading","depth":2},{"value":"timeout","depth":2},{"value":"workers","depth":2},{"value":"Command-Line Arguments","depth":1},{"value":"Custom Command-Line Arguments","depth":3},{"value":"Additional types for Custom Command-Line Arguments","depth":3}],"frontmatter":{"permalink":"/docs/handbook/target-declaration","title":"Target Declaration","oneline":"The target declaration and its parameters in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Target Language Details","oneline":"Detailed reference for each target langauge.","permalink":"/docs/handbook/target-language-details"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Tracing","oneline":"Tracing (preliminary)","permalink":"/docs/handbook/tracing"}}}},"pageContext":{"id":"3-target-declaration","slug":"/docs/handbook/target-declaration","repoPath":"/packages/documentation/copy/en/reference/Target Declaration.md","previousID":"15e2a8e5-dd68-55e9-839f-18c6d342e73c","nextID":"3f4000b5-1133-5c34-807d-29c05884f149","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/target-language-details/page-data.json b/page-data/docs/handbook/target-language-details/page-data.json index 681f6f180..eb2ec5a9a 100644 --- a/page-data/docs/handbook/target-language-details/page-data.json +++ b/page-data/docs/handbook/target-language-details/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/target-language-details","result":{"data":{"markdownRemark":{"id":"3af06b85-61a2-5d81-a7ad-232624b3eaf6","excerpt":"$page-showing-target$ Overview In the C reactor target for Lingua Franca, reactions are written in C and the code generator generates one or more standalone C…","html":"

    $page-showing-target$

    \n

    Overview

    \n
    \n

    In the C reactor target for Lingua Franca, reactions are written in C and the code generator generates one or more standalone C programs that can be compiled and run on several platforms. It has been tested on macOS, Linux, Windows, and at least one bare-iron embedded platform. The single-threaded version (which you get by setting the threading target parameter to false) is the most portable, requiring only a handful of common C libraries (see Included Libraries below). The multithreaded version requires a small subset of the POSIX thread library (pthreads) and transparently executes in parallel on a multicore machine while preserving the deterministic semantics of Lingua Franca.

    \n

    Note that C is not a safe language. There are many ways that a programmer can circumvent the semantics of Lingua Franca and introduce nondeterminism and illegal memory accesses. For example, it is easy for a programmer to mistakenly send a message that is a pointer to data on the stack. The destination reactors will very likely read invalid data. It is also easy to create memory leaks, where memory is allocated and never freed. Here, we provide some guidelines for a style for writing reactors that will be safe.

    \n

    NOTE: If you intend to use C++ code or import C++ libraries in the C target, we provide a special CCpp target that automatically uses a C++ compiler by default. Alternatively, you might want to use the Cpp target.

    \n
    \n
    \n

    In the C++ reactor target for Lingua Franca, reactions are written in C++ and the code generator generates a standalone C++ program that can be compiled and run on all major platforms. Our continuous integration ensures compatibility with Windows, macOS, and Linux.\nThe C++ target solely depends on a working C++ build system including a recent C++ compiler (supporting C++17) and CMake (>= 3.5). It relies on the reactor-cpp runtime, which is automatically fetched and compiled in the background by the Lingua Franca compiler.

    \n

    Note that C++ is not a safe language. There are many ways that a programmer can circumvent the semantics of Lingua Franca and introduce nondeterminism and illegal memory accesses. For example, it is easy for a programmer to mistakenly send a message that is a pointer to data on the stack. The destination reactors will very likely read invalid data. It is also easy to create memory leaks, where memory is allocated and never freed. Note, however, that the C++ reactor library is designed to prevent common errors and to encourage a safe modern C++ style. Here, we introduce the specifics of writing Reactor programs in C++ and present some guidelines for a style that will be safe.

    \n
    \n
    \n

    In the Python reactor target for Lingua Franca, reactions are written in Python. The user-written reactors are then generated into a Python 3 script that can be executed on several platforms. The Python target has been tested on Linux, macOS, and Windows. To facilitate efficient and fast execution of Python code, the generated program relies on a C extension to facilitate Lingua Franca API such as set and schedule. To learn more about the structure of the generated Python program see Implementation Details.

    \n

    Python reactors can bring the vast library of scientific modules that exist for Python into a Lingua Franca program. Moreover, since the Python reactor target is based on a fast and efficient C runtime library, Lingua Franca programs can execute much faster than native equivalent Python programs in many cases. Finally, interoperability with C reactors is planned for the future.

    \n

    In comparison to the C target, the Python target can be up to an order of magnitude slower. However, depending on the type of application and the implementation optimizations in Python, you can achieve an on-par performance to the C target in many applications.

    \n

    NOTE: A Python C\nextension is\ngenerated for each Lingua Franca program (see Implementation\nDetails). This extension module will\nhave the name LinguaFranca[your_LF_program_name].

    \n
    \n
    \n

    In the TypeScript reactor target for Lingua Franca, reactions are written in TypeScript and the code generator generates a standalone TypeScript program that can be compiled to JavaScript and run on Node.js.

    \n

    TypeScript reactors bring the strengths of TypeScript and Node.js to Lingua Franca programming. The TypeScript language and its associated tools enable static type checking for both reaction code and Lingua Franca elements like ports and actions. The Node.js JavaScript runtime provides an execution environment for asynchronous network applications. With Node.js comes Node Package Manager (npm) and its large library of supporting modules.

    \n

    In terms of raw performance on CPU intensive operations, TypeScript reactors are about two orders of magnitude slower than C reactors. But excelling at CPU intensive operations isn’t really the point of Node.js (or by extension TypeScript reactors). Node.js is about achieving high throughput on network applications by efficiently handling asynchronous I/O operations. Keep this in mind when choosing the right Lingua Franca target for your application.

    \n
    \n
    \n

    Important: The Rust target is still quite preliminary. This is early WIP documentation to let you try it out if you’re curious

    \n

    In the Rust reactor target for Lingua Franca, reactions are written in Rust and the code generator generates a standalone Rust program that can be compiled and run on platforms supported by rustc. The program depends on a runtime library distributed as the crate reactor_rt, and depends on the Rust standard library.

    \n

    Documentation for the runtime API is available here: https://lf-lang.github.io/reactor-rust/

    \n

    LF-Rust generates a Cargo project per compiled main reactor. This specification assumes in some places that the user is somewhat familiar with how Cargo works.\nIf you’re not, here’s a primer:

    \n
      \n
    • a Rust project (and its library artifact) are called a crate.
    • \n
    • Cargo is the Rust package manager and build tool. LF/Rust uses Cargo to build the generated project.
    • \n
    • Rust has extensive support for conditional compilation. Cargo features are commonly used to enable or disable the compilation of parts of a crate. A feature may also pull in additional dependencies. Cargo features only influence the compilation process; if you don’t mention the correct feature flags at compilation time, those features cannot be made available at runtime. The Rust reactor runtime crate uses Cargo features to conditionally enable some features, e.g., command-line argument parsing.
    • \n
    \n
    \n

    Requirements

    \n
    \n

    The following tools are required in order to compile the generated C source code:

    \n
      \n
    • A C compiler such as gcc
    • \n
    • A recent version of cmake (at least 3.5)
    • \n
    \n
    \n
    \n

    The following tools are required in order to compile the generated C++ source code:

    \n
      \n
    • A recent C++ compiler supporting C++17
    • \n
    • A recent version of cmake (at least 3.5)
    • \n
    \n
    \n
    \n

    To use this target, install Python 3 on your machine. See downloading Python.

    \n

    NOTE: The Python target requires a C implementation of Python (nicknamed CPython). This is what you will get if you use the above link, or with most of the alternative Python installations such as Anaconda. See Python download alternatives for more details.

    \n

    The Python reactor target relies on setuptools to be able to compile a Python\nC extension for each LF\nprogram.

    \n\n

    To install setuptools using pip3, do this:

    \n
    pip3 install setuptools\n
    \n
    \n
    \n

    First, make sure Node.js is installed on your machine. You can download Node.js here. The npm package manager comes along with Node.

    \n

    After installing Node, you may optionally install the TypeScript compiler.

    \n
    npm install -g typescript\n
    \n

    TypeScript reactor projects are created with a local copy of the TypeScript compiler, but having the TypeScript compiler globally installed can be useful for debugging type errors and type checking on the command line.

    \n
    \n
    \n

    In order to compile the generated Rust source code, you need a recent version of Cargo, the Rust package manager. See How to Install Rust and Cargo if you don’t have them on your system.

    \n

    You can use a development version of the runtime library by setting the LFC option --external-runtime-path to the root directory of the runtime library crate sources. If this variable is mentioned, LFC will ask Cargo to fetch the runtime library from there.

    \n
    \n

    Limitations

    \n
    \n
      \n
    • The C target does make any distinction between $private$ and $public$ $preamble$.
    • \n
    \n
    \n
    \n

    The C++ target does not yet implement:

    \n\n
    \n
    \n
      \n
    • The Lingua Franca lexer does not support single-quoted strings in Python. This limitation also applies to target property values. You must use double quotes.
    • \n
    \n
    \n
    \n
      \n
    • \n

      The $federated$ implementation in the TypeScript target is still quite preliminary.

      \n
    • \n
    • \n

      The TypeScript target does not yet implement methods.

      \n
    • \n
    • \n

      The TypeScript target does not yet implement modal reactors

      \n
    • \n
    \n
    \n
    \n

    The Rust target does not yet implement:

    \n\n
    \n

    The Target Specification

    \n
    \n

    To have Lingua Franca generate C code, start your .lf file with one of the following target specifications:

    \n
      target C <options>\n  target CCpp <options>\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in C, you may want to include the final semicolon for uniformity.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n

    The second form, CCpp, is used when you wish to use a C++ compiler to compile\nthe generated code, thereby allowing your C reactors to call C++ code.

    \n\n

    Here is a minimal example of a program written in the CCpp target, taken from HelloWorldCCPP.lf:

    \n
    target CCpp\nreactor HelloWorld {\n  preamble {=\n    #include <iostream> // Note that no C++ header will be included by default.\n  =}\n  reaction(startup) {=\n    std::cout << "Hello World." << std::endl;\n  =}\n}\nmain reactor {\n  a = new HelloWorld()\n}\n
    \n

    Note: Unless some feature in the C target is needed, we recommend using the Cpp target that uses a runtime that is written natively in C++.

    \n

    Note: A .lf file that uses the CCpp target cannot and should not be imported to a .lf file that uses the C target. Although these two targets use essentially the same runtime, such a scenario can cause unintended compile errors.

    \n
    \n
    \n

    To have Lingua Franca generate C++ code, start your .lf file with the following target specification:

    \n
        target Cpp\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in C++, you may want to include the final semicolon for uniformity.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n
    \n
    \n

    To have Lingua Franca generate Python code, start your .lf file with the following target specification:

    \n
        target Python\n
    \n

    Note that for all LF statements, a final semicolon is optional.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n
    \n
    \n

    To have Lingua Franca generate TypeScript code, start your .lf file with the following target specification:

    \n
        target TypeScript\n
    \n

    Note that for all LF statements, a final semicolon is optional.

    \n

    The supported target parameters and command-line options are documented in the Target Declaration documentation.

    \n
    \n
    \n

    To have Lingua Franca generate Rust code, start your .lf file with the following target specification:

    \n
        target Rust\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in Rust, you may want to include the final semicolon for uniformity.

    \n
    \n

    Parameters and State Variables

    \n
    \n

    Reactor parameters and state variables are referenced in the C code using the\nself struct. The following\nStride\nexample modifies the Count reactor in State\nDeclaration to\ninclude both a parameter and a state variable:

    \n
    reactor Count(stride: int = 1) {\n  state count: int = 1\n  output y: int\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    lf_set(y, self->count);\n    self->count += self->stride;\n  =}\n}\n
    \n

    This defines a stride parameter with type int and initial value 1 and\na count state variable with the same type and initial value.\nThese are referenced in the reaction with the syntax self->stride and self->count respectively.

    \n

    The self Struct:\nThe code generator synthesizes a struct type in C for each reactor class and a constructor that creates an instance of this struct. By convention, these instances are called self and are visible within each reactor body. The self struct contains the parameters, state variables, and values associated with actions and ports of the reactor. Parameters and state variables are accessed directly on the self struct, whereas ports and actions are directly in scope by name, as we will see below. Let’s begin with parameters.

    \n

    It may be tempting to declare state variables in the $preamble$, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    int count = 0;\n  =}\n  output y: int\n  timer t(0, 100 msec)\n\n  reaction(t) -> y {=\n    lf_set(y, count++);\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance of the reactor, those instances will share the same variable count. Hence, don’t do this! Sharing variables across instances of reactors violates a basic principle, which is that reactors communicate only by sending messages to one another. Sharing variables will make your program nondeterministic. If you have multiple instances of the above FlawedCount reactor, the outputs produced by each instance will not be predictable, and in a multithreaded implementation, will also not be repeatable.

    \n

    Array Values for Parameters

    \n

    Parameters and state variables can have array values, though some care is needed. The ArrayAsParameter example outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence: int[] = {0, 1, 2}, n_sequence: int = 3) {\n  output out: int\n  state count: int = 0\n  logical action next\n\n  reaction(startup, next) -> out, next {=\n    lf_set(out, self->sequence[self->count]);\n    self->count++;\n    if (self->count < self->n_sequence) {\n      lf_schedule(next, 0);\n    }\n  =}\n}\n
    \n

    This uses a $logical$ $action$ to repeat the reaction, sending one element of the array in each invocation.

    \n

    In C, arrays do not encode their own length, so a separate parameter n_sequence is used for the array length. Obviously, there is potential here for errors, where the array length doesn’t match the length parameter.

    \n

    Above, the parameter default value is an array with three elements, [0, 1, 2]. The syntax for giving this default value is that of a Lingua Franca list, {0, 1, 2}, which gets converted by the code generator into a C static initializer. The default value can be overridden when instantiating the reactor using a similar syntax:

    \n
      s = new Source(sequence = {1, 2, 3, 4}, n_sequence=4)\n
    \n

    Array Values for States

    \n

    A state variable can also have an array value. For example, the MovingAverage reactor computes the moving average of the last four inputs each time it receives an input:

    \n
    reactor MovingAverageImpl {\n  state delay_line: double[] = {0.0, 0.0, 0.0}\n  state index: int = 0\n  input in: double\n  output out: double\n\n  reaction(in) -> out {=\n    // Calculate the output.\n    double sum = in->value;\n    for (int i = 0; i < 3; i++) {\n      sum += self->delay_line[i];\n    }\n    lf_set(out, sum/4.0);\n\n    // Insert the input in the delay line.\n    self->delay_line[self->index] = in->value;\n\n    // Update the index for the next input.\n    self->index++;\n    if (self->index >= 3) {\n      self->index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an array of doubles with the initial value of the array being a three-element array filled with zeros.

    \n

    States and Parameters with Struct Values

    \n

    States whose type are structs can similarly be initialized. This StructAsState example illustrates this:

    \n
    target C\npreamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nmain reactor StructAsState {\n  state s: hello_t = {"Earth", 42}\n  reaction(startup) {=\n    printf("State s.name=\\"%s\\", value=%d.\\n", self->s.name, self->s.value);\n  =}\n}\n
    \n

    Notice that state s is given type hello_t, which is defined in the $preamble$. The initial value just lists the initial values of each of the fields of the struct in the order they are declared.

    \n

    Parameters are similar:

    \n
    target C\npreamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nmain reactor StructParameter(p: hello_t = {"Earth", 42}) {\n  reaction(startup) {=\n    printf("Parameter p.name=\\"%s\\", value=%d.\\n", self->p.name, self->p.value);\n  =}\n}\n
    \n
    \n
    \n

    Reactor parameters are internally declared as const by the code generator and initialized during reactor instantiation. Thus, the value of a parameter may not be changed. See Parameters and State for examples.

    \n

    Array-Valued Parameters

    \n

    Also parameters can have fixed- or variable-sized array values. The ArrayAsParameter example outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence: std::vector<int> = {0, 1, 2}) {\n  output out: size_t\n  state count: size_t = 0\n  logical action next: void\n\n  reaction(startup, next) -> out, next {=\n    out.set(sequence[count]);\n    count++;\n    if (count < sequence.size()) {\n      next.schedule();\n    }\n  =}\n}\n
    \n

    Here, the type of sequence is explicitly given as std::vector<int>.\nA more compact alternative syntax is as follows:

    \n
    sequence: int[] = {0, 1, 2}
    \n

    The type int[] is converted to std::vector<int> by the code generator.\nAnother alternative syntax is:

    \n
    sequence: int[]({0, 1, 2})
    \n

    Here, the static initializer {0, 1, 2} is passed as a single argument to the constructor of std::vector.

    \n

    The main reactor can be parameterized:

    \n
    main reactor Hello(msg: std::string("World")) {\n  reaction(startup) {=\n    std::cout << "Hello " << msg << "!\\n";\n  =}\n}\n
    \n

    This program will print “Hello World!” by default. However, since msg is a main reactor parameter, the C++ code generator will extend the command-line argument parser and allow to override msg when invoking the program. For instance,

    \n
    bin/Hello --msg Earth\n
    \n

    will result in “Hello Earth!” being printed.

    \n

    State Variables

    \n

    A reactor may declare state variables, which become properties of each instance of the reactor. For example, the following reactor (see Count.lf and CountTest.lf) will produce the output sequence 1, 2, 3, … :

    \n
    reactor Count {\n  state count: int = 0\n  output c: int\n  timer t(0, 1 s)\n  reaction(t) -> c {=\n    count++;\n    c.set(count);\n  =}\n}\n
    \n

    The declaration on the second line gives the variable the name count, declares its type to be int, and initializes its value to 0. The type and initial value can be enclosed in the C++-code delimiters {= ... =} if they are not simple identifiers, but in this case, that is not necessary.

    \n

    In the body of the reaction, the state variable is automatically in scope and can be referenced directly by its name. Since all reactions, state variables, and parameters of a reactor are members of the same class, reactions can also reference state variables (or parameters) using the this pointer: this->name.

    \n

    A state variable may be a time value, declared as follows:

    \n
      state time_value:time = 100 ms;\n
    \n

    The type of the generated time_value variable will be reactor::Duration, which is an alias for std::chrono::nanoseconds.

    \n

    For the C++ target, Lingua Franca provides two alternative styles for initializing state variables. We can write state foo:int(42) or state foo:int{42}. This allows to distinguish between the different initialization styles in C++. foo:int(42) will be translated to int foo(42) and foo:int{42} will be translated to int foo{42} in the generated code. Generally speaking, the {...} style should be preferred in C++, but it is not always applicable. Hence we allow the LF programmer to choose the style. Due to the peculiarities of C++, this is particularly important for more complex data types. For instance, state foo:std::vector<int>(4,2) would be initialized to the list [2,2,2,2] whereas state foo:std::vector<int>{4,2} would be initialized to the list [4,2].

    \n

    State variables can have array values. For example, the [MovingAverage] (https://github.com/lf-lang/lingua-franca/blob/master/test/Cpp/src/MovingAverage.lf) reactor computes the moving average of the last four inputs each time it receives an input:

    \n
    reactor MovingAverageImpl {\n  state delay_line: double[3]{0.0, 0.0, 0.0}\n  state index: int = 0\n  input in: double\n  output out: double\n\n  reaction(in) -> out {=\n    // Calculate the output.\n    double sum = *in.get();\n    for (int i = 0; i < 3; i++) {\n      sum += delay_line[i];\n    }\n    out.set(sum/4.0);\n\n    // Insert the input in the delay line.\n    delay_line[index] = *in.get();\n\n    // Update the index for the next input.\n    index++;\n    if (index >= 3) {\n      index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an fixed-size array of 3 doubles with the initial value of the being filled with zeros (note the curly braces). If the size is given in the type specification, then the code generator will declare the type of the state variable using std::array. In the example above, the type of delay_line is std::array<3, double>. If the size specifier is omitted (e.g. state x:double[]). The code generator will produce a variable-sized array using std::vector.

    \n

    The second line can equivalently be given with an assignment operator:

    \n
      state delay_line: double[3] = {0.0, 0.0, 0.0}
    \n

    State variables with more complex types such as classes or structs can be similarly initialized. See StructAsState.lf.

    \n
    \n
    \n

    Reactor parameters and state variables are referenced in the Python code using\nthe syntax self.<name>, where <name> is the name of the parameter or state\nvariable. The following\nStride\nexample modifies the Count reactor in State\nDeclaration to\ninclude both a parameter and a state variable:

    \n
    reactor Count(stride=1) {\n  state count = 1\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(self.count)\n    self.count += self.stride\n  =}\n}\n
    \n

    This defines a stride parameter with initial value 1 and a count state\nvariable with the same initial value. These are referenced in the reaction with\nthe syntax self.stride and self.count respectively. Note that state\nvariables and parameters do not have types in the Python reactor target. See Parameters\nand State for more examples.

    \n

    The Reactor Class:\nThe code generator synthesizes a class in Python for each reactor class in LF,\nwith a constructor (i.e., def __init__(self, ...):) that creates an instance\nof this class and initializes its parameters and state variables as instance\nvariables.\nThese parameters and state variables can then subsequently be accessed directly\nusing the self reference in the body of reactions.

    \n

    It may be tempting to declare state variables in the $preamble$, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    count = 0\n  =}\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(count)\n    count += 1\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance\nof the reactor, those instances will share the same variable count (because\ncount will be a class variable). Hence,\ndon’t do this! Sharing variables across instances of reactors violates a\nbasic principle, which is that reactors communicate only by sending messages to\none another. Sharing variables will make your program nondeterministic. If you\nhave multiple instances of the above FlawedCount reactor, the outputs produced\nby each instance will not be predictable, and in a multithreaded implementation,\nwill also not be repeatable.

    \n

    Array Expressions for State Variables and Parameters

    \n

    Array parameters and state variables are implemented using Python lists and initialized using a parentheized list. In the following example, the\nparameter sequence and the state variable x have an initial value that is a Python list [1, 2, 3]:

    \n
    reactor Foo(param = {= [1, 2, 3] =}) {\n  state x = {= [1, 2, 3] =}\n  ...\n}\n
    \n

    Their elements may be accessed as arrays in the body of a reaction, for example self.x[i], where i is an array index.

    \n

    The parameter may be overridden with a different list at instantiation:

    \n
    main reactor {\n  f = new Foo(param = {= [3, 4, 5, 6]} )\n}\n
    \n

    As with any ordinary Python list or tuple, len() can been used to deduce the\nlength. In the above, len(self.x) and len(self.param) will return the lengths of the two lists.

    \n

    Assigning Arbitrary Initial Expressions to State Variables and Parameters

    \n

    As used for lists above, the code delimiters {= ... =} can allow for assignment of arbitrary Python\nexpressions as initial values for state variables and parameters. The following example, taken from\nStructAsState.lf\ndemonstrates this usage:

    \n
    main reactor StructAsState {\n  preamble {=\n    class hello:\n      def __init__(self, name, value):\n        self.name = name\n        self.value = value\n  =}\n  state s = {= self.hello("Earth", 42) =}\n\n  reaction(startup) {=\n    # will print "State s.name="Earth", value=42."\n    print("State s.name=\\"{:s}\\", value={:d}.".format(self.s.name, self.s.value))\n  =}\n}\n
    \n

    Notice that a class hello is defined in the preamble. The state variable s is then initialized to an instance of hello constructed within the {= ... =} delimiters.

    \n
    \n
    \n

    In the TypeScript target, all TypeScript types are generally acceptable for parameters and state variables. Custom types (and classes) must be defined in the $preamble$ before they may be used.

    \n

    To benefit from type checking, you should declare types for your reactor elements. If a type isn’t declared for a state variable, it is assigned the type unknown.

    \n

    For example, the following reactor will produce the output sequence 0, 1, 2, 3, … :

    \n
    reactor Count {\n  state count:number = 0;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    count++;\n    y = count;\n  =}\n}\n
    \n

    The declaration on the second line gives the variable the name “count”, declares its type to be number, and initializes its value to 0. The type and initial value can be enclosed in the Typescript-code delimiters {= ... =} if they are not simple identifiers, but in this case, that is not necessary.

    \n

    In the body of the reaction, the reactor’s state variable is referenced by way of a local variable of the same name. The local variable will contain the current value of the state at the beginning of the reaction. The final value of the local variable will be used to update the state at the end of the reaction.

    \n

    It may be tempting to declare state variables in the preamble, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    let count = 0;\n  =}\n  output y:number;\n  timer t(0, 100 msec);\n  reaction(t) -> y {=\n    count++;\n    y = count;\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance of the reactor, those instances will share the same variable count. Hence, don’t do this! Sharing variables across instances of reactors violates a basic principle, which is that reactors communicate only by sending messages to one another. Sharing variables will make your program nondeterministic. If you have multiple instances of the above FlawedCount reactor, the outputs produced by each instance will not be predictable, and in an asynchronous implementation, will also not be repeatable.

    \n

    A state variable may be a time value, declared as follows:

    \n
      state time_value:time = 100 ms\n
    \n

    The time_value variable will be of type TimeValue, which is an object used to represent a time in the TypeScript Target. Refer to the section on timed behavior for more information.

    \n

    A state variable can have an array or object value. For example, the following reactor computes the moving average of the last four inputs each time it receives an input (from MovingAverageImpl):

    \n
    reactor MovingAverage {\n  state delay_line: {= Array<number> =} = {= [0.0, 0.0, 0.0] =}\n  state index: number = 0\n  input x: number\n  output out: number\n\n  reaction(x) -> out {=\n    x = x as number;\n    // Calculate the output.\n    let sum = x;\n    for (let i = 0; i < 3; i++) {\n      sum += delay_line[i];\n    }\n    out = sum/4.0;\n\n    // Insert the input in the delay line.\n    delay_line[index] = x;\n\n    // Update the index for the next input.\n    index++;\n    if (index >= 3) {\n      index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an array of numbers with the initial value of the array being a three-element array filled with zeros.

    \n

    States whose type are objects can similarly be initialized. Declarations can take an object literal as the initial value:

    \n
    state myLiteral:{= {foo: number, bar: string} =} = {= {foo: 42, bar: "baz"} =};\n
    \n

    or use new:

    \n
    state mySet:{=Set<number>=} = {= new Set<number>() =};\n
    \n

    Reactor parameters are also referenced in the TypeScript code as local variables. The example below modifies the Count reactor so that its stride is a parameter:

    \n
    target TypeScript\nreactor Count(stride:number = 1) {\n  state count:number = 0;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    y = count;\n    count += stride;\n  =}\n}\nreactor Display {\n  input x:number;\n  reaction(x) {=\n    console.log("Received: " + x + ".");\n  =}\n}\nmain reactor Stride {\n  c = new Count(stride = 2);\n  d = new Display();\n  c.y -> d.x;\n}\n
    \n

    The second line defines the stride parameter, gives its type, and gives its initial value. As with state variables, the type and initial value can be enclosed in {= ... =} if necessary. The parameter is referenced in the reaction by referring to the local variable stride.

    \n

    When the reactor is instantiated, the default parameter value can be overridden. This is done in the above example near the bottom with the line:

    \n
      c = new Count(stride = 2);\n
    \n

    If there is more than one parameter, use a comma separated list of assignments.

    \n

    Parameters in Lingua Franca are immutable. To encourage correct usage, parameter variables within a reaction are local const variables. If you feel tempted to use a mutable parameter, instead try using the parameter to initialize state and modify the state variable instead. This is illustrated below by a further modification to the Stride example where it takes an initial “start” value for count as a second parameter:

    \n
    target TypeScript\nreactor Count(stride:number = 1, start:number = 5) {\n  state count:number = start;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    y = count;\n    count += stride;\n  =}\n}\nreactor Display {\n  input x:number;\n  reaction(x) {=\n    console.log("Received: " + x + ".");\n  =}\n}\nmain reactor Stride {\n  c = new Count(stride = 2, start = 10);\n  d = new Display();\n  c.y -> d.x;\n}\n
    \n

    Array or Object Parameters

    \n

    Parameters can have array or object values. Here is an example that outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence:{=Array<number>=} = {= [0, 1, 2] =}) {\n  output out:number;\n  state count:number(0);\n  logical action next;\n  reaction(startup, next) -> out, next {=\n    out = sequence[count];\n    count++;\n    if (count < sequence.length) {\n      actions.next.schedule(0, null);\n    }\n  =}\n}\n
    \n

    Above, the parameter default value is an array with three elements, [0, 1, 2]. The syntax for giving this default value is a TypeScript array literal. Since this is TypeScript syntax, not Lingua Franca syntax, the initial value needs to be surrounded with the target code delimiters, {= ... =}. The default value can be overridden when instantiating the reactor using a similar syntax:

    \n
      s = new Source(sequence = {= [1, 2, 3, 4] =});\n
    \n

    Both default and overridden values for parameters can also be created with the new keyword:

    \n
    reactor Source(sequence:{=Array<number>=} = {= new Array<number>() =}) {\n
    \n

    and

    \n
    s = new Source(sequence = {= new Array<number() =});\n
    \n
    \n
    \n

    Parameters and state variables in Rust are accessed on the self structure, as shown in Parameter Declaration.

    \n
    \n

    Inputs and Outputs

    \n
    \n

    In the body of a reaction in the C target, the value of an input is obtained using the syntax name->value, where name is the name of the input port. See, for example, the Destination reactor in Input and Output Declarations.

    \n

    To set the value of outputs, use lf_set. See, for example, the Double reactor in Input and Output Declarations.)

    \n

    An output may even be set in different reactions of the same reactor at the same tag. In this case, one reaction may wish to test whether the previously invoked reaction has set the output. It can check name->is_present to determine whether the output has been set. For example, the Source reactor in the test case TestForPreviousOutput will always produce the output 42:

    \n
    reactor Source {\n  output out: int\n  reaction(startup) -> out {=\n    // Set a seed for random number generation based on the current time.\n    srand(time(0));\n    // Randomly produce an output or not.\n    if (rand() % 2) {\n      lf_set(out, 21);\n    }\n  =}\n  reaction(startup) -> out {=\n    if (out->is_present) {\n      lf_set(out, 2 * out->value);\n    } else {\n      lf_set(out, 42);\n    }\n  =}\n}\n
    \n

    The first reaction may or may not set the output to 21. The second reaction doubles the output if it has been previously produced and otherwise produces 42.

    \n

    Sending and Receiving Data

    \n

    You can define your own data types in C and send and receive those. Consider the StructAsType example:

    \n
    preamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nreactor StructAsType {\n  output out:hello_t;\n  reaction(startup) -> out {=\n    struct hello_t temp = {"Earth", 42};\n    lf_set(out, temp);\n  =}\n}\n
    \n

    The $preamble$ code defines a struct data type. In the reaction to $startup$, the reactor creates an instance of this struct on the stack (as a local variable named temp) and then copies that struct to the output using the lf_set macro.

    \n

    For large structs, it may be inefficient to create a struct on the stack and copy it to the output, as done above. You can use a pointer type instead. See below for details.

    \n

    A reactor receiving the struct message uses the struct as normal in C:

    \n
    reactor Print() {\n  input in:hello_t;\n  reaction(in) {=\n    printf("Received: name = %s, value = %d\\n", in->value.name, in->value.value);\n  =}\n}\n
    \n

    The preamble should not be repeated in this reactor definition if the two reactors are defined together because this will trigger an error when the compiler thinks that hello_t is being redefined.

    \n

    Persistent Inputs

    \n

    In the C target, inputs are persistent. You can read an input even when there is no event present and the value of that input will be the most recently received value or an instance of the input type filled with zeros. For example:

    \n
    target C\nreactor Source {\n  output out: int\n  timer t(100 ms, 200 ms)\n  state count: int = 1\n  reaction(t) -> out {=\n    lf_set(out, self->count++);\n  =}\n}\nreactor Sink {\n  input in: int\n  timer t(0, 100 ms)\n  reaction(t) in {=\n    printf("Value of the input is %d at time %lld\\n", in->value, lf_time_logical_elapsed());\n  =}\n}\nmain reactor {\n  source = new Source()\n  sink = new Sink()\n  source.out -> sink.in\n}\n
    \n

    The Source reactor produces output 1 at 100ms and 2 at 300ms.\nThe Sink reactor reads every 100ms starting at 0.\nNotice that it uses the input in but is not triggered by it.\nThe result of running this program is:

    \n
    Value of the input is 0 at time 0\nValue of the input is 1 at time 100000000\nValue of the input is 1 at time 200000000\nValue of the input is 2 at time 300000000\nValue of the input is 2 at time 400000000\n...
    \n

    The first output is 0 (an int initialized with zero), and subsequently, each output is read twice.

    \n

    Fixed Length Array Inputs and Outputs

    \n

    When inputs and outputs are fixed-length arrays, the memory to contain the array is automatically provided as part of the reactor instance. You can write directly to it, and then just call lf_set_present to alert the system that the output is present. For example:

    \n
    reactor Source {\n  output out: int[3]\n  reaction(startup) -> out {=\n    out->value[0] = 0;\n    out->value[1] = 1;\n    out->value[2] = 2;\n    lf_set_present(out);\n  =}\n}\n
    \n

    In general, this will work for any data type that can be copied by a simple assignment operator (see below for how to handle more complex data types).

    \n

    Reading the array is equally simple:

    \n
    reactor Print(scale: int(1)) {  // The scale parameter is just for testing.\n  input in: int[3]\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < 3; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value[i]);\n    }\n   printf("]\\n");\n  =}\n}\n
    \n

    Variable Length Array Inputs and Outputs

    \n

    Above, the array size is fixed and must be known throughout the program. A more flexible mechanism leaves the array size unspecified in the types of the inputs and outputs and uses lf_set_array instead of lf_set to inform the system of the array length. For example,

    \n
    reactor Source {\n  output out: int[]\n  reaction(startup) -> out {=\n    // Dynamically allocate an output array of length 3.\n    int* array = (int*)malloc(3 * sizeof(int));\n    // Populate the array.\n    array[0] = 0;\n    array[1] = 1;\n    array[2] = 2;\n    // Set the output, specifying the array length.\n    lf_set_array(out, array, 3);\n  =}\n}\n
    \n

    The array length will be available at the receiving end, which may look like this:

    \n
    reactor Print {\n  input in: int[]\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < in->length; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value[i]);\n    }\n    printf("]\\n");\n  =}\n}\n
    \n

    Dynamically Allocated Data

    \n

    A much more flexible way to communicate complex data types is to set dynamically allocated memory on an output port. This can be done in a way that automatically handles freeing the memory when all users of the data are done with it. The reactor that allocates the memory cannot know when downstream reactors are done with the data, so Lingua Franca provides utilities for managing this using reference counting. You can specify a destructor on a port and pass a pointer to a dynamically allocated object as illustrated in the SetDestructor example.

    \n

    Suppose the data structure of interest, its constructor, destructor, and copy_constructor are defined as follows:

    \n
    preamble {=\n  typedef struct int_array_t {\n    int* data;\n    size_t length;\n  } int_array_t;\n\n  int_array_t* int_array_constructor(size_t length) {\n    int_array_t* result = (int_array_t*) malloc(sizeof(int_array_t));\n    result->data = (int*) calloc(length, sizeof(int));\n    result->length = length;\n    return result;\n  }\n\n  void int_array_destructor(void* array) {\n    free(((int_array_t*) array)->data);\n    free(array);\n  }\n\n  void* int_array_copy_constructor(void* array) {\n    int_array_t* source = (int_array_t*) array;\n    int_array_t* copy = (int_array_t*) malloc(sizeof(int_array_t));\n    copy->data = (int*) calloc(source->length, sizeof(int));\n    copy->length = source->length;\n    for (size_t i = 0; i < source->length; i++) {\n      copy->data[i] = source->data[i];\n    }\n    return (void*) copy;\n  }\n=}\n
    \n

    Then, the sender reactor would use lf_set_destructor to specify how the memory set on an output port should be freed:

    \n
    reactor Source {\n  output out:int_array_t*;\n  reaction(startup) -> out {=\n    lf_set_destructor(out, int_array_destructor);\n    lf_set_copy_constructor(out, int_array_copy_constructor);\n  }\n  reaction(startup) -> out {=\n    int_array_t* array =  int_array_constructor(2);\n    for (size_t i = 0; i < array->length; i++) {\n      array->data[i] = i;\n    }\n    lf_set(out, array);\n  =}\n}\n
    \n

    The first reaction specifies the destructor and copy constructor (the latter of which will be used if any downstream reactor has a mutable input or wishes to make a writable copy).

    \n

    IMPORTANT: The array constructed should be sent to only one output port using lf_set. If you need to send it to more than one output port or to use it as the payload of an action, you should use lf_set_token.

    \n

    FIXME: Show how to do this.

    \n

    A reactor receiving this array is straightforward. It just references the array elements as usual in C, as illustrated by this example:

    \n
    reactor Print() {\n  input in:int_array_t*;\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < in->value->length; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value->data[i]);\n    }\n    printf("]\\n");\n  =}\n}\n
    \n

    The deallocation of memory for the data will occur automatically after the last reactor that receives a pointer to the data has finished using it, using the destructor specified by lf_set_destructor or free if none specified.

    \n

    Occasionally, you will want an input or output type to be a pointer, but you don’t want the automatic memory allocation and deallocation. A simple example is a string type, which in C is char*. Consider the following (erroneous) reactor:

    \n
    reactor Erroneous {\n  output out:char*;\n  reaction(startup) -> out {=\n    lf_set(out, "Hello World");\n  =}\n}\n
    \n

    An output data type that ends with * signals to Lingua Franca that the message\nis dynamically allocated and must be freed downstream after all recipients are\ndone with it. But the \"Hello World\" string here is statically allocated, so an\nerror will occur when the last downstream reactor to use this message attempts\nto free the allocated memory. To avoid this for strings, you can use a special\nstring type as follows:

    \n
    reactor Fixed {\n  output out:string;\n  reaction(startup) -> out {=\n    lf_set(out, "Hello World");\n  =}\n}\n
    \n

    The string type is equivalent to char*, but since it doesn’t end with *, it does not signal to Lingua Franca that the type is dynamically allocated. Lingua Franca only handles allocation and deallocation for types that are specified literally with a final * in the type name. The same trick can be used for any type where you don’t want automatic allocation and deallocation. E.g., the SendsPointer example looks like this:

    \n
    reactor SendsPointer  {\n  preamble {=\n    typedef int* int_pointer;\n  =}\n  output out:int_pointer\n  reaction(startup) -> out {=\n    static int my_constant = 42;\n    lf_set(out, &my_constant;)\n  =}\n}\n
    \n

    The above technique can be used to abuse the reactor model of computation by communicating pointers to shared variables. This is generally a bad idea unless those shared variables are immutable. The result will likely be nondeterministic. Also, communicating pointers across machines that do not share memory will not work at all.

    \n

    Mutable Inputs

    \n

    Although it cannot be enforced in C, a receiving reactor should not modify the values provided by an input. Inputs are logically immutable because there may be several recipients. Any recipient that wishes to modify the input should make a copy of it. Fortunately, a utility is provided for this pattern. Consider the ArrayScale example, here modified to use the above int_array_t data type:

    \n
    reactor ArrayScale(scale:int(2)) {\n  mutable input in:int_array_t*;\n  output out:int_array_t*;\n  reaction(in) -> out {=\n    for(int i = 0; i < in->length; i++) {\n      in->value[i] *= self->scale;\n    }\n    lf_set_token(out, in->token);\n  =}\n}\n
    \n

    Here, the input is declared $mutable$, which means that any reaction is free to\nmodify the input. If this reactor is the only recipient of the array or the last\nrecipient of the array, then this will not make a copy of the array but rather use\nthe original array. Otherwise, it will use a copy. By default, memcpy is used to copy the data. However, the sender can also specify\na copy constructor to be used by calling lf_set_copy_constructor on the\noutput port, as explained below.

    \n

    Important: Notice that the above ArrayScale reactor modifies the array and then forwards it to its output port using the lf_set_token() macro. That macro further delegates to downstream reactors the responsibility for freeing dynamically allocated memory once all readers have completed their work. It will not work to just use lf_set, passing it the value.\nThis will result in a memory error, yielding a message like the following:

    \n
        malloc: *** error for object 0x600002674070: pointer being freed was not allocated
    \n

    If the above code were not to forward the array, then the dynamically allocated memory will be automatically freed when this reactor is done with it.

    \n

    Three of the above reactors can be combined into a pipeline as follows:

    \n
    main reactor ArrayScaleTest {\n  s = new Source();\n  c = new ArrayScale();\n  p = new Print();\n  s.out -> c.in;\n  c.out -> p.in;\n}\n
    \n

    In this composite, the array is allocated by ArrayPrint, modified by ArrayScale, and deallocated (freed) after Print has reacted. No copy is necessary because ArrayScale is the only recipient of the original array.

    \n

    Inputs and outputs can also be dynamically allocated structs. In fact, Lingua Franca’s C target will treat any input or output data type that ends with [] or * specially by providing utilities for allocating memory and modifying and forwarding. Deallocation of the allocated memory is automatic. The complete set of utilities is given below.

    \n

    String Types

    \n

    String types in C are char*. But, as explained above, types ending with * are interpreted specially to provide automatic memory management, which we generally don’t want with strings (a string that is a compile-time constant must not be freed). You could enclose the type as {= char* =}, but to avoid this awkwardness, the header files include a typedef that permits using string instead of char*. For example (from DelayString.lf):

    \n
    reactor DelayString(delay:time = 100 ms)) {\n  input in:string;\n  output out:string;\n  logical action a:string;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // The following copies the char*, not the string.\n    lf_schedule_copy(a, self->delay, &(in->value), 1);\n  =}\n}\n
    \n

    Macros For Setting Output Values

    \n

    In all of the following, <out> is the name of the output and <value> is the value to be sent.

    \n
    \n

    lf_set(<out>, <value>);

    \n
    \n

    Set the specified output (or input of a contained reactor) to the specified\nvalue using shallow copy. lf_set can be used with all supported data types\n(including type declarations that end with * or []).

    \n
    \n

    lf_set_token(<out>, <token>);

    \n
    \n

    This version is used to directly set the underlying reference-counted token in\noutputs with a type declaration ending with * (any pointer) or [] (any\narray). The <value> argument should be a struct of type token_t. It should\nbe rarely necessary to have the need to create your own (dynamically allocated)\ninstance of token_t.

    \n

    Consider the\nSetToken.lf\nexample:

    \n
    reactor Source {\n  output out:int*\n  logical action a:int\n  reaction(startup) -> a {=\n    lf_schedule_int(a, MSEC(200), 42);\n  =}\n  reaction(a) -> out {=\n    lf_set_token(out, a->token);\n  =}\n}\n
    \n

    Here, the first reaction schedules an integer-valued action to trigger after 200 milliseconds. As explained below, action payloads are carried by tokens. The second reaction grabs the token rather than the value using the syntax a->token (the name of the action followed by ->token). It then forwards the token to the output. The output data type is int* not int because the token carries a pointer to dynamically allocated memory that contains the value. All inputs and outputs with types ending in * or [] are carried by tokens.

    \n
    \n

    lf_set_destructor(<out>, <destructor>);

    \n
    \n

    Specify the destructor destructor used to deallocate any dynamic data set on the output port out.

    \n
    \n

    lf_set_copy_constructor(<out>, <copy_constructor>);

    \n
    \n

    Specify the copy_constructor used to copy construct any dynamic data set on the output port out if the receiving port is $mutable$.

    \n

    lf_set (and lf_set_token) will overwrite any output value previously set at the same logical time and will cause the final output value to be sent to all reactors connected to the output. They also set a local <out>->is_present variable to true. This can be used to subsequently test whether the output value has been set.

    \n
    \n
    \n

    In the body of a reaction in the C++ target, the value of an input is obtained using the syntax *name.get(), where name is the name of the input port. Similarly, outputs are set using a set() method on an output port. For examples, see Inputs and Outputs.

    \n

    Note that get() always returns a pointer to the actual value. Thus the pointer needs to be dereferenced with * to obtain the value. (See Sending and Receiving Large Data Types for an explanation of the exact mechanisms behind this pointer access).\nTo determine whether an input is present, name.is_present() can be used. Since get() returns a nullptr if no value is present, name.get() != nullptr can be used alternatively for checking presence.

    \n

    Sending and Receiving Large Data Types

    \n

    You can define your own data types in C++ or use types defined in a library and send and receive those. Consider the StructAsType example:

    \n
    public preamble {=\n  struct Hello {\n    std::string name;\n    int value;\n  };\n=}\nreactor StructAsType {\n  output out: Hello;\n  reaction(startup) -> out {=\n    Hello hello{"Earth, 42};\n    out.set(hello);\n  =}\n}\n
    \n

    The $public$ $preamble$ code defines a struct data type. In the reaction to $startup$, the reactor creates an instance of this struct on the stack (as a local variable named hello) and then copies that instance to the output using the set() method. For this reason, the C++ reactor runtime provides more sophisticated ways to allocate objects and send them via ports.

    \n

    The C++ library defines two types of smart pointers that the runtime uses internally to implement the exchange of data between ports. These are reactor::MutableValuePtr<T> and reactor::ImmutableValuePtr<T>. reactor::MutableValuePtr<T> is a wrapper around std::unique_ptr and provides read and write access to the value hold, while ensuring that the value has a unique owner. In contrast, reactor::ImmutableValuePtr<T> is a wrapper around std::shared_pointer and provides read only (const) access to the value it holds. This allows data to be shared between reactions of various reactors, while guarantee data consistency. Similar to std::make_unique and std::make_shared, the reactor library provides convenient function for creating mutable and immutable values pointers: reactor::make_mutable_value<T>(...) and reactor::make_immutable_value<T>(...).

    \n

    In fact this code from the example above:

    \n
        Hello hello{"Earth, 42"};\n    out.set(hello);\n
    \n

    implicitly invokes reactor::make_immutable_value<Hello>(hello) and could be rewritten as

    \n
        Hello hello{"Earth, 42"};\n    out.set(reactor::make_immutable_value<Hello>(hello));\n
    \n

    This will invoke the copy constructor of Hello, copying its content from the hello instance to the newly created reactor::ImmutableValuePtr<Hello>.

    \n

    Since copying large objects is inefficient, the move semantics of C++ can be used to move the ownership of object instead of copying it. This can be done in the following two ways. First, by directly creating a mutable or immutable value pointer, where a mutable pointer allows modification of the object after it has been created:

    \n
        auto hello = reactor::make_mutable_value<Hello>("Earth", 42);\n    hello->name = "Mars";\n    out.set(std::move(hello));\n
    \n

    An example of this can be found in StructPrint.lf. Not that after the call to std::move, hello is nullptr and the reaction cannot modify the object anymore. Alternatively, if no modification is requires, the object can be instantiated directly in the call to set() as follows:

    \n
        out.set({"Earth", 42});\n
    \n

    An example of this can be found in StructAsTypeDirect.

    \n

    Getting a value from an input port of type T via get() always returns an reactor::ImmutableValuePtr<T>. This ensures that the value cannot be modified by multiple reactors receiving the same value, as this could lead to an inconsistent state and nondeterminism in a multi-threaded execution. An immutable value pointer can be converted to a mutable pointer by calling get_mutable_copy. For instance, the ArrayScale reactor modifies elements of the array it receives before sending it to the next reactor:

    \n
    reactor Scale(scale:int = 2) {\n  input in:int[3];\n  output out:int[3];\n\n  reaction(in) -> out {=\n    auto array = in.get().get_mutable_copy();\n    for(int i = 0; i < array->size(); i++) {\n      (*array)[i] = (*array)[i] * scale;\n    }\n    out.set(std::move(array));\n  =}\n}\n
    \n

    Currently get_mutable_copy() always copies the contained value to safely create a mutable pointer. However, a future implementation could optimize this by checking if any other reaction is accessing the same value. If not, the value can simply be moved from the immutable pointer to a mutable one.

    \n
    \n
    \n

    In the body of a reaction in the Python target, the value of an input is\nobtained using the syntax name.value, where name is the name of the input\nport. To determine whether an input is present, use name.is_present. To\nproduce an output, use the syntax name.set(value). The value can be any\nvalid Python object. For simple examples, see Inputs and\nOutputs.

    \n

    Sending and Receiving Objects

    \n

    You can define your own data types in Python and send and receive those. Consider the StructAsType example:

    \n
    target Python {\n  files: include/hello.py\n}\npreamble {=\n  import hello\n=}\nreactor Source {\n  output out;\n  reaction(startup) -> out {=\n    temp = hello.hello("Earth", 42)\n    out.set(temp)\n  =}\n}\n
    \n

    The top-level preamble has imported the hello module, which contains the following class:

    \n
    class hello:\n    def __init__(self, name = "", value = 0):\n        self.name = name\n        self.value = value\n
    \n

    In the reaction to startup, the reactor has created an instance object of this class (as local variable named temp) and passed it downstream using the set method on output port out.

    \n

    The set method is defined as follows:

    \n
    \n

    <port>.set(<value>): Set the specified output port (or input of a contained\nreactor) to the specified value. This value can be any Python object\n(including None and objects of type Any). The value is\ncopied and therefore the variable carrying the value can be subsequently\nmodified without changing the output.

    \n
    \n

    A reactor receiving the class object message can subsequently access the object\nusing <port>.value:

    \n
    reactor Print(expected(42)) {\n  input _in;\n  reaction(_in) {=\n    print("Received: name = {:s}, value = {:d}\\n".format(_in.value.name,\n                                                         _in.value.value))\n  =}\n}\n
    \n

    Note: The hello module has been imported using a top-level preamble, therefore, the contents of the module are available to all reactors defined in the current Lingua Franca file (a similar situation arises if the hello class itself was in the top-level preamble).

    \n
    \n
    \n

    In the TypeScript target, all TypeScript types are generally acceptable for inputs and outputs with one notable exception:

    \n
      \n
    • undefined is not a valid type for an input, output, or action. This is because undefined is used to designate the absence of an input, output, or action during a reaction.
    • \n
    \n

    As with parameters and state variables, custom types (and classes) must be defined in the preamble before they may be used.

    \n

    To benefit from type checking, you should declare types for your reactor elements. If a type isn’t declared for an input, output, or action, it is assigned the reactor-ts type Present which is defined as

    \n
    export type Present = (number | string | boolean | symbol | object | null);\n
    \n

    In the body of a reaction in the TypeScript target, inputs are simply referred to by name. An input of type t is available within the body of a reaction as a local variable of type t | undefined. To determine whether an input is present, test the value of the input against undefined. An undefined input is not present.

    \n

    WARNING Be sure to use the === or !== operator and not == or != to test against undefined. In JavaScript/TypeScript the comparison undefined == null yields the value true. It may also be tempting to rely upon the falsy evaluation of undefined within an if statement, but this may introduce bugs. For example a reaction that tests the presence of input x with if (x) { ... } will not correctly identify potentially valid present values such as 0, false, or \"\".

    \n

    For example, the Determinism.lf test case includes the following reactor:

    \n
    reactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    let sum = 0;\n    if (x !== undefined) {\n      sum += x;\n    }\n    if (y !== undefined) {\n      sum += y;\n    }\n    console.log("Received " + sum);\n    if (sum != 2) {\n      util.requestErrorStop("FAILURE: Expected 2.")\n    }\n  =}\n}\n
    \n

    The reaction refers to the inputs x and y by name and tests for their presence by testing x and y against undefined. If a reaction is triggered by just one input, then normally it is not necessary to test for its presence. It will always be present. However TypeScript’s type system is not smart enough to know such an input will never have type undefined if there’s no test against undefined within the reaction. An explicit type annotation (for example x = x as t; where t is the type of the input) may be necessary to avoid type errors from the compiler. In the above example, there are two triggers, so the reaction has no assurance that both will be present.

    \n

    Inputs declared in the uses part of the reaction do not trigger the reaction. Consider this modification of the above reaction:

    \n
    reaction(x) y {=\n  let sum = x as number;\n  if (y !== undefined) {\n    sum += y;\n  }\n  console.log("Received " + sum + ".");\n=}\n
    \n

    It is no longer necessary to test for the presence of x because that is the only trigger. The input y, however, may or may not be present at the logical time that this reaction is triggered. Hence, the code must test for its presence.

    \n

    The effects portion of the reaction specification can include outputs and actions. Actions will be described below. Like inputs, an output of type t is available within the body of a reaction as a local variable of type t | undefined. The local variable for each output is initialized to the output’s current value. Outputs are set by assigning a (non-undefined) value to its local variable (no changes will be made to an output if it has the value undefined at the end of a reaction). Whatever value an output’s local variable has at the end of the reaction will be set to that output. If an output’s local variable has the value undefined at the end of the reaction, that output will not be set and connected downstream inputs will be absent. For example, we can further modify the above example as follows:

    \n
    output z:number;\nreaction(x) y -> z {=\n  let sum = x as number;\n  if (y !== undefined) {\n    sum += y;\n  }\n  z = sum;\n=}\n
    \n

    If an output gets set more than once at any logical time, downstream reactors will see only the final value that is set. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

    \n

    An output may even be set in different reactions of the same reactor at the same logical time. In this case, one reaction may wish to test whether the previously invoked reaction has set the output. It can do that using a !== undefined test for that output. For example, the following reactor will always produce the output 42:

    \n
    reactor TestForPreviousOutput {\n  output out:number;\n  reaction(startup) -> out {=\n    if (Math.random() > 0.5) {\n      out = 21;\n    }\n  =}\n  reaction(startup) -> out {=\n    let previous_output = out;\n    if (previous_output) {\n      out = 2 * previous_output;\n    } else {\n      out = 42;\n    }\n  =}\n}\n
    \n

    The first reaction may or may not set the output to 21. The second reaction doubles the output if it has been previously produced and otherwise produces 42.

    \n

    Sending and Receiving Custom Types

    \n

    You can define your own data types in TypeScript and send and receive those. Consider the following example:

    \n
    reactor CustomType {\n  preamble {=\n    type custom = string | null;\n  =}\n  output out:custom;\n  reaction(startup) -> out {=\n    out = null;\n  =}\n}\n
    \n

    The $preamble$ code defines a custom union type of string and null.

    \n
    \n
    \n

    Inputs and outputs in the Rust target are accessed using the set and get methods of the ctx objects, as shown in Inputs and Outputs.

    \n
    \n

    Time

    \n
    \n

    In the C target, the value of a time instant or interval is an integer specifying a number of nanoseconds. An instant is the number of nanoseconds that have elapsed since January 1, 1970. An interval is the difference between two instants. When an LF program starts executing, logical time is (normally) set to the instant provided by the operating system. (On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    Time in the C target is a int64_t, which is a 64-bit signed number. Since a 64-bit number has a limited range, this measure of time instants will overflow in approximately the year 2262. For better code clarity, two types are defined in tag.h, instant_t and interval_t, which you can use for time instants and intervals respectively. These are both equivalent to int64_t, but using those types will insulate your code against changes and platform-specific customizations.

    \n

    Lingua Franca uses a superdense model of time. A reaction is invoked at a logical tag, a struct consisting of a time value (an instant_t, which is a int64_t) and a microstep value (a microstep_t, which is an uint32_t). The tag is guaranteed to not increase during the execution of a reaction. Outputs produced by a reaction have the same tag as the inputs, actions, or timers that trigger the reaction, and hence are logically simultaneous.

    \n

    The time structs and functions for working with time are defined in tag.h. The most useful functions are:

    \n
      \n
    • tag_t lf_tag(): Get the current tag at which this reaction has been invoked.
    • \n
    • int lf_tag_compare(tag_t, tag_t): Compare two tags, returning -1, 0, or 1 for less than, equal, and greater than.
    • \n
    • instant_t lf_time_logical(): Get the current logical time (the first part of the current tag).
    • \n
    • interval_t lf_time_logical_elapsed(): Get the logical time elapsed since program start.
    • \n
    \n

    There are also some useful functions for accessing physical time:

    \n
      \n
    • instant_t lf_time_physical(): Get the current physical time.
    • \n
    • instant_t lf_time_physical_elapsed(): Get the physical time elapsed since program start.
    • \n
    • instant_t lf_time_start(): Get the starting physical and logical time.
    • \n
    \n

    The last of these is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime example:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t logical = lf_time_logical();\n    printf("Logical time is %ld.\\n", logical);\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    Start execution at time Sun Oct 13 10:18:36 2019\nplus 353609000 nanoseconds.\nLogical time is 1570987116353609000.\nLogical time is 1570987117353609000.\nLogical time is 1570987118353609000.\n...
    \n

    The first two lines give the current time-of-day provided by the execution platform at the start of execution. This is used to initialize logical time. Subsequent values of logical time are printed out in their raw form, rather than the friendlier form in the first two lines. If you look closely, you will see that each number is one second larger than the previous number, where one second is 1000000000 nanoseconds.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    interval_t elapsed = lf_time_logical_elapsed();\n    printf("Elapsed logical time is %ld.\\n", elapsed);\n  =}\n}\n
    \n

    This will produce:

    \n
    Start execution at time Sun Oct 13 10:25:22 2019\nplus 833273000 nanoseconds.\nElapsed logical time is 0.\nElapsed logical time is 1000000000.\nElapsed logical time is 2000000000.\n...
    \n

    You can also get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t physical = lf_time_physical();\n    printf("Physical time is %ld.\\n", physical);\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Start execution at time Sun Oct 13 10:35:59 2019\nplus 984992000 nanoseconds.\nPhysical time is 1570988159986108000.\nPhysical time is 1570988160990219000.\nPhysical time is 1570988161990067000.\n...
    \n

    Finally, you can get elapsed physical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t elapsed_physical = lf_time_physical_elapsed();\n    printf("Elapsed physical time is %ld.\\n", elapsed_physical);\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Elapsed physical time is 657000.\nElapsed physical time is 1001856000.\nElapsed physical time is 2004761000.\n...
    \n

    Notice that these numbers are increasing by roughly one second each time. If you set the fast target parameter to true, then logical time will elapse much faster than physical time.

    \n

    Working with nanoseconds in C code can be tedious if you are interested in longer durations. For convenience, a set of macros are available to the C programmer to convert time units into the required nanoseconds. For example, you can specify 200 msec in C code as MSEC(200) or two weeks as WEEKS(2). The provided macros are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these. Examples are given in the next section.

    \n
    \n
    \n

    Timers are specified exactly as in the Time and Timers. When working with time in the C++ code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    The reactor-cpp library uses std::chrono for representing time. Specifically, the library defines two types for representing durations and timepoints: reactor::Duration and reactor::TimePoint. reactor::Duration is an alias for std::chrono::nanosecods. reactor::TimePoint is alias for std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>. As you can see from these definitions, the smallest time step that can be represented is one nanosecond. Note that reactor::TimePoint describes a specific point in time and is associated with a specific clock, whereas reactor::Duration defines a time interval between two time points.

    \n

    Lingua Franca uses a superdense model of logical time. A reaction is invoked at a logical tag. In the C++ library, a tag is represented by the class reactor::Tag. In essence, this class is a tuple of a reactor::TimePoint representing a specific point in logical time and a microstep value (of type reactor::mstep_t, which is an alias for unsigned long). reactor::Tag provides two methods for getting the time point or the microstep:

    \n
    const TimePoint& time_point() const;\nconst mstep_t& micro_step() const;\n
    \n

    The C++ code in reaction bodies has access to library functions that allow to retrieve the current logical or physical time:

    \n
      \n
    • TimePoint get_physical_time(): Get the current physical time.
    • \n
    • TimePoint get_logcial_time(): Get the current logical time.
    • \n
    • Duration get_elapsed_physical_time(): Get the physical time elapsed since program start.
    • \n
    • Duration get_elapsed_logical_time(): Get the logical time elapsed since program start.
    • \n
    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime example:

    \n
    main reactor {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    auto logical = get_logical_time();\n    std::cout << "Logical time is " << logical << std::endl;\n  =}\n}\n
    \n

    Note that the << operator is overloaded for both reactor::TimePoint and reactor::Duration and will print the time information accordingly.

    \n

    When executing the above program, you will see something like this:

    \n
    [INFO]  Starting the execution\nLogical time is 2021-05-19 14:06:09.496828396\nLogical time is 2021-05-19 14:06:10.496828396\nLogical time is 2021-05-19 14:06:11.496828396\nLogical time is 2021-05-19 14:06:11.496828396\n...
    \n

    If you look closely, you will see that each printed logical time is one second larger than the previous one.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    auto elapsed = get_elapsed_logical_time();\n    std::cout << "Elapsed logical time is " << elapsed << std::endl;\n    std::cout << "In seconds: " <<  std::chrono::duration_cast<std::chrono::seconds>(elapsed) << std::endl;\n  =}\n}\n
    \n

    Using std::chrono it is also possible to convert between time units and directly print the number of elapsed seconds as seen above. The resulting output of this program will be:

    \n
    [INFO]  Starting the execution\nElapsed logical time is 0 nsecs\nIn seconds: 0 secs\nElapsed logical time is 1000000000 nsecs\nIn seconds: 1 secs\nElapsed logical time is 2000000000 nsecs\nIn seconds: 2 secs\n...
    \n

    You can also get physical and elapsed physical time:

    \n
    main reactor {\n  timer t(0, 1 sec);\n\treaction(t) {=\n    auto logical = get_logical_time();\n    auto physical = get_physical_time();\n    auto elapsed = get_elapsed_physical_time();\n    std::cout << "Physical time is " << physical << std::endl;\n    std::cout << "Elapsed physical time is " << elapsed << std::endl;\n    std::cout << "Time lag is " << physical - logical << std::endl;\n  =}\n}\n
    \n

    Notice that the physical times are increasing by roughly one second in each reaction. The output also shows the lag between physical and logical time. If you set the fast target parameter to true, then physical time will elapse much faster than logical time. The above program will produce something like this:

    \n
    [INFO]  Starting the execution\nPhysical time is 2021-05-19 14:25:18.070523014\nElapsed physical time is 2601601 nsecs\nTime lag is 2598229 nsecs\nPhysical time is 2021-05-19 14:25:19.068038275\nElapsed physical time is 1000113888 nsecs\nTime lag is 113490 nsecs\n[INFO]  Physical time is Terminating the execution\n2021-05-19 14:25:20.068153026\nElapsed physical time is 2000228689 nsecs\nTime lag is 228241 nsecs
    \n

    For specifying time durations in code chrono provides convenient literal operators in std::chrono_literals. This namespace is automatically included for all reaction bodies. Thus, we can simply write:

    \n
    std::cout << 42us << std::endl;\nstd::cout << 1ms << std::endl;\nstd::cout << 3s << std::endl;\n
    \n

    which prints:

    \n
    42 usecs\n1 msecs\n3 secs
    \n
    \n
    \n

    Timers are specified exactly as in the Time and Timers. When working with time in the Python code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    In the Python target, similar to the C target, the value of a time instant or\ninterval is an integer specifying a number of nanoseconds. An instant is the\nnumber of nanoseconds that have elapsed since January 1, 1970. An interval is\nthe difference between two instants.

    \n

    The functions for working with time and tags are:

    \n
      \n
    • lf.tag() -> Tag: Returns a Tag instance of the current tag at which this reaction has been invoked.
    • \n
    • lf.tag_compare(Tag, Tag) -> int: Compare two Tag instances, returning -1, 0, or 1 for less than, equal, and greater than. Tags can also be compared using rich comparators (ex. <, >, ==), which returns True or False.
    • \n
    • lf.time.logical() -> int: Get the current logical time (the first part of the current tag).
    • \n
    • lf.time.logical_elapsed() -> int: Get the logical time elapsed since program start.
    • \n
    \n

    Tags can be initialized using Tag(time=some_number, microstep=some_other_number).

    \n

    There are also some useful functions for accessing physical time:

    \n
      \n
    • lf.time.physical() -> int: Get the current physical time.
    • \n
    • lf.time.physical_elapsed() -> int: Get the physical time elapsed since program start.
    • \n
    • lf.time.start() -> int: Get the starting physical and logical time.
    • \n
    \n

    The last of these is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime.lf example:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    logical = lf.time.logical()\n    print("Logical time is ", logical)\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nLogical time is  1604587862864237900\nLogical time is  1604587863864237900\nLogical time is  1604587864864237900\n...
    \n

    The first two lines give the current time-of-day provided by the execution platform at the start of execution. This is used to initialize logical time. Subsequent values of logical time are printed out in their raw form, rather than the friendlier form in the first two lines. If you look closely, you will see that each number is one second larger than the previous number, where one second is 1000000000 nanoseconds.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    elapsed = lf.time.logical_elapsed()\n    print("Elapsed logical time is ", elapsed)\n  =}\n}\n
    \n

    This will produce:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nElapsed logical time is  0\nElapsed logical time is  1000000000\nElapsed logical time is  2000000000\n...
    \n

    You can also get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    physical = lf.time.physical()\n    print("Physical time is ", physical)\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nPhysical time is  1604587862864343500\nPhysical time is  1604587863864401900\nPhysical time is  1604587864864395200\n...
    \n

    Finally, you can get elapsed physical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    elapsed_physical = lf.time.physical_elapsed()\n    print("Elapsed physical time is ", elapsed_physical)\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nElapsed physical time is  110200\nElapsed physical time is  1000185400\nElapsed physical time is  2000178600\n...
    \n

    Notice that these numbers are increasing by roughly one second each time. If you set the fast target parameter to true, then logical time will elapse much faster than physical time.

    \n

    Working with nanoseconds in the Python code can be tedious if you are interested in longer durations. For convenience, a set of functions are available to the Python programmer to convert time units into the required nanoseconds. For example, you can specify 200 msec in Python code as MSEC(200) or two weeks as WEEKS(2). The provided functions are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these. Examples are given in the next section.

    \n
    \n
    \n

    See Summary of Time Functions and Utility Function Reference for a quick API reference.

    \n

    Timers are specified exactly as in the Time and Timers section. When working with time in the TypeScript code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    A TimeValue is an class defined in the TypeScript target library file time.ts to represent a time instant or interval. For your convenience TimeValue and other classes from the time.ts library mentioned in these instructions are automatically imported into scope of your reactions. An instant is the number of nanoseconds that have elapsed since January 1, 1970. An interval is the difference between two instants. When an LF program starts executing, logical time is (normally) set to the instant provided by the operating system. (On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    Internally a TimeValue uses two numbers to represent the time. To prevent overflow (which would occur for time intervals spanning more than 0.29 years if a single JavaScript number, which has 2^53 bits of precision, were to be used), we use two numbers to store a time value. The first number denotes the number of whole seconds in the interval or instant; the second number denotes the remaining number of nanoseconds in the interval or instant. The first number represents the number of seconds, the second number represents the number of nanoseconds. These fields are not accessible to the programmer, instead TimeValues may be manipulated by an API with functions for addition, subtraction, and comparison.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider:

    \n
    target TypeScript;\nmain reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let logical:TimeValue = util.getCurrentLogicalTime()\n    console.log("Logical time is " + logical + ".");\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    Logical time is (1584666585 secs; 805146880 nsecs).\nLogical time is (1584666586 secs; 805146880 nsecs).\nLogical time is (1584666587 secs; 805146880 nsecs).\n...
    \n

    Subsequent values of logical time are printed out in their raw form, of seconds and nanoseconds. If you look closely, you will see that each number is one second larger than the previous number.

    \n

    You can also obtain the elapsed logical time since the start of execution, rather than exact logical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let logical:TimeValue = util.getElapsedLogicalTime()\n    console.log("Logical time is " + logical + ".");\n  =}\n}\n
    \n

    This will produce:

    \n
    Logical time is (0 secs; 0 nsecs).\nLogical time is (1 secs; 0 nsecs).\nLogical time is (2 secs; 0 nsecs).\n...
    \n

    You can get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let physical:TimeValue = util.getCurrentPhysicalTime()\n    console.log("Physical time is " + physical + ".");\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Physical time is (1584666801 secs; 644171008 nsecs).\nPhysical time is (1584666802 secs; 642269952 nsecs).\nPhysical time is (1584666803 secs; 642278912 nsecs).\n...
    \n

    Notice that these numbers are increasing by roughly one second each time.

    \n

    You can also get elapsed physical time from the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let physical:TimeValue = util.getElapsedPhysicalTime()\n    console.log("Physical time is " + physical + ".");\n  =}\n}\n
    \n

    This will produce something like:

    \n
    Physical time is (0 secs; 2260992 nsecs).\nPhysical time is (1 secs; 166912 nsecs).\nPhysical time is (2 secs; 136960 nsecs).\n...
    \n

    You can create a TimeValue yourself with the UnitBasedTimeValue class. UnitBasedTimeValue is a subclass of TimeValue and can be used wherever you could also use a TimeValue directly obtained from one of the util functions. A UnitBasedTimeValue is constructed with a whole number and a TimeUnit. A TimeUnit is an enum from the time.ts library with convenient labels for common time units. These are nsec, usec, msec, sec (or secs), minute (or minutes), hour (or hours), day (or days), and week (or weeks).

    \n

    This reactor has an example of a UnitBasedTimeValue.

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let myTimeValue:TimeValue = new UnitBasedTimeValue(200, TimeUnit.msec);\n    let logical:TimeValue = util.getCurrentLogicalTime()\n    console.log("My custom time value is " + myTimeValue + ".");\n  =}\n
    \n

    This will produce:

    \n
    My custom time value is 200 msec.\nMy custom time value is 200 msec.\nMy custom time value is 200 msec.\n...
    \n

    Tags

    \n

    The TypeScript target provides a utility to get the current Tag of a reaction. Recall that time in Lingua Franca is superdense and each TimeValue is paired with an integer “microstep” index to track the number of iterations at a particular TimeValue. A Tag is this combination of a TimeValue and a “microstep”. The time.ts library provides functions for adding, subtracting, and comparing Tags.

    \n

    You can get the current Tag in your reactions. This example illustrates tags with a Zero-Delay Action:

    \n
    target TypeScript;\nmain reactor GetTime {\n  timer t(0, 1 sec);\n  logical action a;\n  reaction(t) -> a {=\n    let superdense:Tag = util.getCurrentTag();\n    console.log("First iteration - the tag is: " + superdense + ".");\n    actions.a.schedule(0, null);\n  =}\n  reaction(a) {=\n    let superdense:Tag = util.getCurrentTag();\n    let timePart:TimeValue = superdense.time;\n    let microstepPart:number = superdense.microstep;\n    console.log("Second iteration - the time part of the tag is:  " + timePart + ".");\n    console.log("Second iteration - the microstep part of the tag is:  " + microstepPart + ".");\n  =}\n}\n
    \n

    This will produce:

    \n
    First iteration - the tag is: ((1584669987 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669987 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\nFirst iteration - the tag is: ((1584669988 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669988 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\nFirst iteration - the tag is: ((1584669989 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669989 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\n...
    \n

    The first reaction prints the “First iteration” part of the output at microstep 0. The second reaction occurs one microstep later (explained in Scheduling Delayed Reactions) and illustrates how to split a Tag into its constituent TimeValue and microstep.

    \n

    Summary of Time Functions

    \n

    See Time. These time functions are defined in the time.ts library of reactor-ts.

    \n

    UnitBasedTimeValue(value: number, unit:TimeUnit) Constructor for UnitBasedTimeValue, a programmer-friendly subclass of TimeValue. Use a number and a TimeUnit enum.

    \n
    enum TimeUnit {\n  nsec,\n  usec,\n  msec,\n  sec,\n  secs,\n  minute,\n  minutes,\n  hour,\n  hours,\n  day,\n  days,\n  week,\n  weeks,\n}\n
    \n

    TimeValue.add(other: TimeValue): TimeValue Adds this to other.

    \n

    TimeValue.subtract(other: TimeValue): TimeValue Subtracts other from this. A negative result is an error.

    \n

    TimeValue.difference(other: TimeValue): TimeValue Obtain absolute value of other minus this.

    \n

    TimeValue.isEqualTo(other: TimeValue): boolean Returns true if this and other represents the same TimeValue. Otherwise false.

    \n

    TimeValue.isZero(): boolean Returns true if this represents a 0 TimeValue.

    \n

    TimeValue.isEarlierThan(other: TimeValue): boolean Returns true if this < other. Otherwise false.

    \n

    Tag.isSmallerThan(other: Tag): boolean Returns true if this < other. Otherwise false.

    \n

    Tag.isSimultaneousWith(other: Tag): boolean Returns true if this and other represents the same Tag. Otherwise false.

    \n

    Tag.getLaterTag(delay: TimeValue): Tag Returns a tag with the time part of this TimeValue incremented by delay.

    \n

    Tag.getMicroStepLater(): Tag Returns a tag with the microstep part of this TimeValue incremented by 1.

    \n

    getTimeDifference(other: Tag): TimeValue Returns a TimeValue that represents the absolute (i.e., positive) time difference between this and other.

    \n
    \n
    \n

    FIXME: details needed here on time in Rust.

    \n
    \n

    Actions

    \n
    \n

    Actions are described in Actions. If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. This is particularly useful for physical actions that are externally triggered because it enables the action to convey information to the reactor. This could be, for example, the body of an incoming network message or a numerical reading from a sensor.

    \n

    Recall from Composing Reactors that the $after$ keyword on a connection between ports introduces a logical delay. This is actually implemented using a logical action. We illustrate how this is done using the DelayInt example:

    \n
    reactor Delay(delay: time = 100 ms) {\n  input in: int\n  output out: int\n  logical action a: int\n  reaction(a) -> out {=\n    if (a->has_value && a->is_present) lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // Use specialized form of schedule for integer payloads.\n    lf_schedule_int(a, self->delay, in->value);\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
      delay = new Delay();\n  source.out -> delay.in;\n  delay.in -> sink.out\n
    \n

    is equivalent to

    \n
        source.out -> sink.in after 100 ms\n
    \n

    (except that our Delay reactor will only work with data type int).

    \n

    Note: The reaction to a is given before the reaction to in above. This is important because if both inputs are present at the same tag, the first reaction must be executed before the second. Because of this reaction ordering, it is possible to create a program that has a feedback loop where the output of the Delay reactor propagates back to an input at the same tag. If the reactions were given in the opposite order, then such a program would result in a causality loop.

    \n

    In the Delay reactor, the action a is specified with a type int. The reaction to the input in declares as its effect the action a. This declaration makes it possible for the reaction to schedule a future triggering of a. The reaction uses one of several variants of the lf_schedule function, namely lf_schedule_int, a convenience function provided because integer payloads on actions are very common. We will see below, however, that payloads can have any data type.

    \n

    The first reaction declares that it is triggered by a and has effect out. To\nread the value, it uses the a->value variable. Because this reaction is first,\nthe out at any logical time can be produced before the input in is even\nknown to be present. Hence, this reactor can be used in a feedback loop, where\nout triggers a downstream reactor to send a message back to in of this same\nreactor. If the reactions were given in the opposite order, there would be a\ncausality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it as follows:

    \n
      reaction(a) -> out {=\n    if (a->has_value) {\n      lf_set(out, a->value);\n    }\n  =}\n
    \n

    It is possible to both be triggered by and schedule an action in the same\nreaction. For example, the\nfollowing CountSelf\nreactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay: time = 100 msec) {\n  output out: int\n  logical action a: int\n  reaction(startup) -> a, out {=\n    lf_set(out, 0);\n    lf_schedule_int(a, self->delay, 1);\n  =}\n  reaction(a) -> a, out {=\n    lf_set(out, a->value);\n    lf_schedule_int(a, self->delay, a->value + 1);\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    The C++ provides a simple interface for scheduling actions via a schedule() method. Actions are described in the Actions document. Consider the Schedule reactor:

    \n
    reactor Schedule {\n  input x: int\n  logical action a: void\n  reaction(x) -> a {=\n    a.schedule(200ms);\n  =}\n\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time.count()\n          << " after start" << std::endl;\n  =}\n}\n
    \n

    When this reactor receives an input x, it calls schedule() on the action a, specifying a logical time offset of 200 milliseconds. The action a will be triggered at a logical time 200 milliseconds after the arrival of input x. At that logical time, the second reaction will trigger and will use the get_elapsed_logical_time() function to determine how much logical time has elapsed since the start of execution.

    \n

    Notice that after the logical time offset of 200 msec, there may be another input x simultaneous with the action a. Because the reaction to a is given first, it will execute first. This becomes important when such a reactor is put into a feedback loop (see below).

    \n

    Physical actions work exactly as described in the Physical Actions section.

    \n

    Zero-Delay Actions

    \n

    If the specified delay in a schedule() is omitted or is zero, then the action a will be triggered one microstep later in superdense time (see Superdense Time). Hence, if the input x arrives at metric logical time t, and you call schedule() in one of the following ways:

    \n
    a.schedule();\na.schedule(0s);\na.schedule(reactor::Duration::zero());\n
    \n

    then when the reaction to a is triggered, the input x will be absent (it was present at the previous microstep). The reaction to x and the reaction to a occur at the same metric time t, but separated by one microstep, so these two reactions are not logically simultaneous.

    \n

    As discussed above the he metric time is visible to the programmer and can be obtained in a reaction using either get_elapsed_logical_time() or get_logical_time().

    \n

    As described in the Action document, action declarations can have a min_delay parameter. This modifies the timestamp further. Also, the action declaration may be physical rather than logical, in which case, the assigned timestamp will depend on the physical clock of the executing platform.

    \n

    Actions With Values

    \n

    If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. This is particularly useful for physical actions that are externally triggered because it enables the action to convey information to the reactor. This could be, for example, the body of an incoming network message or a numerical reading from a sensor.

    \n

    Recall from the Composing Reactors section that the after keyword on a connection between ports introduces a logical delay. This is actually implemented using a logical action. We illustrate how this is done using the DelayInt example:

    \n
    reactor Delay(delay: time = 100 ms) {\n  input in: int\n  output out: int\n  logical action d: int\n  reaction(d) -> out {=\n    if (d.is_present()) {\n      out.set(d.get());\n    }\n  =}\n  reaction(in) -> d {=\n    d.schedule(in.get(), delay);\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
    d = new Delay();\nsource.out -> d.in;\nd.in -> sink.out\n
    \n

    is equivalent to

    \n
    source.out -> sink.in after 100 ms\n
    \n

    (except that our Delay reactor will only work with data type int).

    \n

    Note: The reaction to d is given before the reaction to in above. This is important because if both inputs are present at the same tag, the first reaction must be executed before the second. Because of this reaction ordering, it is possible to create a program that has a feedback loop where the output of the Delay reactor propagates back to an input at the same tag. If the reactions were given in the opposite order, then such a program would result in a causality loop.

    \n

    The action d is specified with a type int. The reaction to the input in declares as its effect the action d. This declaration makes it possible for the reaction to schedule a future triggering of d. In the C++ target, actions use the same mechanism for passing data via value pointers as do ports. In the example above, the reactor::ImmutablValuePtr<int> derived by the call to in.get() is passed directly to schedule(). Similarly, the value can later be retrieved from the action with d.get() and passed to the output port.

    \n

    The first reaction declares that it is triggered by d and has effect out. Because this reaction is first, the out at any logical time can be produced before the input in is even known to be present. Hence, this reactor can be used in a feedback loop, where out triggers a downstream reactor to send a message back to in of this same reactor. If the reactions were given in the opposite order, there would be causality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it using is_present():

    \n
    reaction(d) -> out {=\n  if (d.is_present()) {\n    out.set(d.get());\n  }\n=}\n
    \n

    It is possible to both be triggered by and schedule an action the same reaction. For example, this reactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay:time(100 msec)) {\n  output out:int;\n  logical action a:int;\n  reaction(startup) -> a, out {=\n    out.set(0);\n    a.schedule_int(1, delay);\n  =}\n  reaction(a) -> a, out {=\n    out.set(a.get());\n    a.schedule_int(*a.get() + 1, delay);\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    Actions are described here. Actions can carry a\nvalue, a Python object that becomes available to any reaction triggered by\nthe action. This is particularly useful for physical actions that are externally\ntriggered because it enables the action to convey information to the reactor.\nThis could be, for example, the body of an incoming network message or a\nnumerical reading from a sensor. Note that actions do not have a data\ntype in the Python target, even when they carry a value.

    \n

    Recall from Composing Reactors that the\n$after$ keyword on a connection between ports introduces a logical delay. This\nis actually implemented using a logical action. We illustrate how this is done\nusing the\nDelay reactor in the DelayInt\nexample:

    \n
    reactor Delay(delay = 100 ms) {\n  input _in\n  output out\n  logical action a\n  reaction(a) -> out {=\n    if (a.value is not None) and a.is_present:\n      out.set(a.value)\n  =}\n  reaction(_in) -> a {=\n    a.schedule(self.delay, _in.value)\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
        delay = new Delay()\n    <source_port_reference> -> delay._in\n    delay._in -> <destination_port_reference>\n
    \n

    is equivalent to

    \n
        <source_port_reference> -> <destination_port_reference> after 100 ms\n
    \n

    In the Delay reactor, the reaction to the input _in declares as its effect\nthe action a. This declaration makes it possible for the reaction to schedule\na future triggering of a using the\na.schedule()\nmethod.

    \n

    The first reaction declares that it is triggered by a and has effect out. To\nread the value, it uses the a.value syntax. Because this reaction is first,\nthe out at any logical time can be produced before the input _in is even\nknown to be present. Hence, this reactor can be used in a feedback loop, where\nout triggers a downstream reactor to send a message back to _in of this same\nreactor. If the reactions were given in the opposite order, there would be a\ncausality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it as follows:

    \n
      reaction(a) -> out {=\n    if (a.value is not None):\n      out.set(a.value)\n  =}\n
    \n

    It is possible to both be triggered by and schedule an action in the same\nreaction. For example, the\nfollowing CountSelf\nreactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay = 100 ms) {\n  output out\n  logical action a\n  reaction(startup) -> a, out {=\n    out.set(0)\n    a.schedule(self.delay, 1)\n  =}\n  reaction(a) -> a, out {=\n    out.set(a.value)\n    a.schedule(self.delay, a.value + 1)\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    Each action listed as an effect for a reaction is available as a schedulable object in the reaction body via the actions object. The TypeScript target provides a special actions object with a property for each schedulable action. Schedulable actions (of type t) have the object method:

    \n
    schedule: (extraDelay: TimeValue | 0, value?: T) => void;\n
    \n

    The first argument can either be the literal 0 (shorthand for 0 seconds) or a TimeValue/UnitBasedTimeValue. The second argument is the value for the action. Consider the following reactor:

    \n
    target TypeScript;\nreactor Schedule {\n  input x:number;\n  logical action a;\n  reaction(x) -> a {=\n    actions.a.schedule(new UnitBasedTimeValue(200, TimeUnit.msec), null);\n  =}\n  reaction(a) {=\n    let elapsedTime = util.getElapsedLogicalTime();\n    console.log("Action triggered at logical time " + elapsedTime + " after start.");\n  =}\n}\n
    \n

    When this reactor receives an input x, it calls schedule() on the action a, so it will be triggered at the logical time offset (200 msec) with a null value. The action a will be triggered at a logical time 200 milliseconds after the arrival of input x. This will trigger the second reaction, which will use the util.getElapsedLogicalTime() function to determine how much logical time has elapsed since the start of execution. The second argument to the schedule() function is a value, data that can be carried by the action, which is explained below. In the above example, there is no value.

    \n

    Zero-Delay Actions

    \n

    If the specified delay in a schedule() call is zero, then the action a will be triggered one microstep later in superdense time (see Superdense Time). Hence, if the input x arrives at metric logical time t, and you call schedule() as follows:

    \n
    actions.a.schedule(0);\n
    \n

    then when a reaction to a is triggered, the input x will be absent (it was present at the previous microstep). The reaction to x and the reaction to a occur at the same metric time t, but separated by one microstep, so these two reactions are not logically simultaneous. These reactions execute with different Tags.

    \n

    Actions With Values

    \n

    If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. The most common use of this is to implement a logical delay, where a value provided at an input is produced on an output with a larger logical timestamp. To accomplish that, it is much easier to use the after keyword on a connection between reactors. Nevertheless, in this section, we explain how to directly use actions with value. In fact, the after keyword is implemented as described below.

    \n

    If you are familiar with other targets (like C) you may notice it is much easier to schedule actions with values in TypeScript because of TypeScript/JavaScript’s garbage collected memory management. The following example implements a logical delay using an action with a value.

    \n
    reactor Delay(delay:time(100 ms)) {\n  input x:number;\n  output out:number;\n  logical action a:number;\n  reaction(x) -> a {=\n    actions.a.schedule(delay, x as number);\n  =}\n  reaction(a) -> out {=\n    if (a !== null){\n      out = a as number\n    }\n  =}\n}\n
    \n

    The action a is specified with a type number. The first reaction declares a as its effect. This declaration makes it possible for the reaction to schedule a future triggering of a. It’s necessary to explicitly annotate the type of x as a number in the schedule function because TypeScript doesn’t know the only trigger of a reaction must be present in that reaction.

    \n

    The second reaction declares that it is triggered by a and has effect out. When a reaction triggers or uses an action the value of that action is made available within the reaction as a local variable with the name of the action. This variable will take on the value of the action and it will have the value undefined if that action is absent because it was not scheduled for this reaction.

    \n

    The local variable cannot be used directly to schedule an action. As described above, an action a can only be scheduled in a reaction when it is 1) declared as an effect and 2) referenced through a property of the actions object. The reason for this implementation is that actions.a refers to the action, not its value, and it is possible to use both the action and the value in the same reaction. For example, the following reaction will produce a counting sequence after it is triggered the first time:

    \n
    reaction(a) -> out, a {=\n  if (a !== null) {\n    a = a as number;\n    out = a;\n    let newValue = a++;\n    actions.a.schedule(delay, newValue);\n  }\n=}\n
    \n
    \n
    \n

    Actions may carry values if they mention a data type, for instance:

    \n
    logical action act: u32;\n
    \n

    Within a reaction, you can schedule that action with a value like so

    \n
    ctx.schedule_with_v(act, Asap, 30);\n
    \n

    you can get the value from another reaction like so:

    \n
    if let Some(value) = ctx.get_action(act) {\n  // a value is present at this tag\n} else {\n  // value was not specified\n}\n
    \n

    If an action does not mention a data type, the type defaults to ().

    \n
    \n

    Schedule Functions

    \n
    \n

    Actions with values can be rather tricky to use because the value must usually be carried in dynamically allocated memory. It will not work for value to refer to a state variable of the reactor because that state variable will likely have changed value by the time the reactions to the action are invoked. Several variants of the lf_schedule function are provided to make it easier to pass values across time in varying circumstances.

    \n
    \n

    lf_schedule(<action>, <offset>);

    \n
    \n

    This is the simplest version as it carries no value. The action need not have a data type.

    \n
    \n

    lf_schedule_int(<action>, <offset>, <value>);

    \n
    \n

    This version carries an int value. The data type of the action is required to be int.

    \n
    \n

    lf_schedule_token(<action>, <offset>, <value>);

    \n
    \n

    This version carries a token, which has type token_t and points to the value, which can have any type. There is a create_token() function that can be used to create a token, but programmers will rarely need to use this. Instead, you can use lf_schedule_value() (see below), which will automatically create a token. Alternatively, for inputs with types ending in * or [], the value is wrapped in a token, and the token can be obtained using the syntax inputname->token in a reaction and then forwarded using lf_schedule_token() (see Dynamically Allocated Structs above). If the input is mutable, the reaction can then even modify the value pointed to by the token and/or use lf_schedule_token() to send the token to a future logical time. For example, the DelayPointer reactor realizes a logical delay for any data type carried by a token:

    \n
    reactor DelayPointer(delay:time(100 ms)) {\n  input in:void*;\n  output out:void*;\n  logical action a:void*;\n  reaction(a) -> out {=\n    // Using lf_set_token delegates responsibility for\n    // freeing the allocated memory downstream.\n    lf_set_token(out, a->token);\n  =}\n  reaction(in) -> a {=\n    // Schedule the actual token from the input rather than\n    // a new token with a copy of the input value.\n    lf_schedule_token(a, self->delay, in->token);\n  =}\n}\n
    \n
    \n

    lf_schedule_value(<action>, <offset>, <value>, <length>);

    \n
    \n

    This version is used to send into the future a value that has been dynamically allocated using malloc. It will be automatically freed when it is no longer needed. The value argument is a pointer to the memory containing the value. The length argument should be 1 if it is a not an array and the array length otherwise. This length will be needed downstream to interpret the data correctly. See ScheduleValue.lf.

    \n
    \n

    lf_schedule_copy(<action>, <offset>, <value>, <length>);

    \n
    \n

    This version is for sending a copy of some data pointed to by the <value> argument. The data is assumed to be a scalar or array of type matching the <action> type. The <length> argument should be 1 if it is a not an array and the array length otherwise. This length will be needed downstream to interpret the data correctly.

    \n

    Occasionally, an action payload may not be dynamically allocated nor freed. For example, it could be a pointer to a statically allocated string. If you know this to be the case, the DelayString reactor will realize a logical time delay on such a string:

    \n
    reactor DelayString(delay:time(100 msec)) {\n  input in:string;\n  output out:string;\n  logical action a:string;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // The following copies the char*, not the string.\n    lf_schedule_copy(a, self->delay, &(in->value), 1);\n  =}\n}\n
    \n

    The data type string is an alias for char*, but Lingua Franca does not know this, so it creates a token that contains a copy of the pointer to the string rather than a copy of the string itself.

    \n
    \n
    \n

    FIXME: Give a list of schedule() functions with descriptions.

    \n
    \n
    \n

    The Python reactor target provides a .schedule() method to trigger an action at a\nfuture logical time. The .schedule() method also optionally allows for a value\nto be sent into the future. For example, take the\nScheduleValue.lf:

    \n
    main reactor ScheduleValue {\n  logical action a;\n  reaction(startup) -> a {=\n    value = "Hello"\n    a.schedule(0, value)\n  =}\n  reaction(a) {=\n    print("Received: ", a.value)\n    if a.value != "Hello":\n      sys.stderr.write("FAILURE: Should have received 'Hello'\\n")\n      exit(1)\n  =}\n}\n
    \n

    In this example, the logical action a is scheduled one\nmicrostep in the future with a string value\ncontaining \"Hello\".

    \n
    \n
    \n

    FIXME: List them here

    \n
    \n
    \n

    Within a reaction, actions may be scheduled using the schedule function:

    \n
    // schedule without additional delay\nctx.schedule(act, Asap);\n// schedule with an additional delay\nctx.schedule(act, after!(20 ms));\n// that's shorthand for\nctx.schedule(act, After(Duration.of_millis(20)));\n
    \n
    \n

    Stopping Execution

    \n
    \n

    A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method request_stop(), which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which request_stop() was called. For example, if the current tag is (2 seconds, 0), the last (stop) tag will be (2 seconds, 1). In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

    \n
    \n

    The timeout target property will take precedence over this function. For example, if a program has a timeout of 2 seconds and request_stop() is called at the (2 seconds, 0) tag, the last tag will still be (2 seconds, 0).

    \n
    \n
    \n
    \n

    A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method lf.request_stop(), which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which lf.request_stop() was called. For example, if the current tag is (2 seconds, 0), the last (stop) tag will be (2 seconds, 1). In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

    \n
    \n

    The timeout target property will take precedence over this function. For example, if a program has a timeout of 2 seconds and request_stop() is called at the (2 seconds, 0) tag, the last tag will still be (2 seconds, 0).

    \n
    \n
    \n
    \n

    A reaction may request that the execution stops after all events with the current timestamp have been processed by calling environment()->sync_shutdown(). There is also a method environment()->async_shutdown()\nwhich may be invoked from outside an reaction, like an external thread.

    \n
    \n
    \n

    A reaction may request that the execution stop by calling the function util.requestShutdown() which takes no arguments. Execution will not stop immediately when this function is called; all events with the current tag will finish processing and execution will continue for one more microstep to give shutdown triggers a chance to execute. After this additional step, execution will terminate.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n

    Log and Debug Information

    \n
    \n

    A suite of useful functions is provided in util.h for producing messages to be made visible when the generated program is run. Of course, you can always use printf, but this is not a good choice for logging or debug information, and it is not a good choice when output needs to be redirected to a window or some other user interface (see for example the sensor simulator). Also, in federated execution, these functions identify which federate is producing the message. The functions are listed below. The arguments for all of these are identical to printf with the exception that a trailing newline is automatically added and therefore need not be included in the format string.

    \n
      \n
    • \n

      LF_PRINT_DEBUG(format, ...): Use this for verbose messages that are only needed during debugging. Nothing is printed unless the target parameter logging is set to debug. THe overhead is minimized when nothing is to be printed.

      \n
    • \n
    • \n

      LF_PRINT_LOG(format, ...): Use this for messages that are useful logs of the execution. Nothing is printed unless the target parameter logging is set to log or debug. This is a macro so that overhead is minimized when nothing is to be printed.

      \n
    • \n
    • \n

      lf_print(format, ...): Use this for messages that should normally be printed but may need to be redirected to a user interface such as a window or terminal (see register_print_function below). These messages can be suppressed by setting the logging target property to warn or error.

      \n
    • \n
    • \n

      lf_print_warning(format, ...): Use this for warning messages. These messages can be suppressed by setting the logging target property to error.

      \n
    • \n
    • \n

      lf_print_error(format, ...): Use this for error messages. These messages are not suppressed by any logging target property.

      \n
    • \n
    • \n

      lf_print_error_and_exit(format, ...): Use this for catastrophic errors.

      \n
    • \n
    \n

    In addition, a utility function is provided to register a function to redirect printed outputs:

    \n
      \n
    • lf_register_print_function(function): Register a function that will be used instead of printf to print messages generated by any of the above functions. The function should accept the same arguments as printf.
    • \n
    \n
    \n
    \n

    The reactor-cpp library provides logging utilities in logging.hh for producing messages to be made visible when the generated program is run. Of course std::cout or printf can be used for the same purpose, but the logging mechanism provided by reactor-cpp is thread-safe ensuring that messages produced in parallel reactions are not interleaved with each other and provides common way for turning messages of a certain severity on and off.

    \n

    In particular, reactor-cpp provides the following logging interfaces:

    \n
      \n
    • reactor::log::Debug(): for verbose debug messages
    • \n
    • reactor::log::Info(): for info messages of general interest, info is the default severity level
    • \n
    • reactor::log::Warning(): for warning messages
    • \n
    • reactor::log::Error(): for errors
    • \n
    \n

    These utilities can be used analogues to std::cout. For instance:

    \n
    reactor::log::Info() << "Hello World! It is " << get_physical_time();\n
    \n

    Note that unlike std::cout the new line delimiter is automatically added to the end of the message.

    \n

    Which type of messages are actually produced by the compiled program can be controlled with the log-level target property.

    \n
    \n
    \n

    The Python supports the logging target specification. This will cause the runtime to produce more or less information about the execution. However, user-facing functions for different logging levels are not yet implemented (see issue #619).

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    The executable reacts to the environment variable RUST_LOG, which sets the logging level of the application. Possible values are\noff, error, warn, info, debug, trace

    \n

    Error and warning logs are on by default. Enabling a level enables all greater levels (i.e., RUST_LOG=info also enables warn and error, but not trace or debug).

    \n

    Logging can also be turned on with the --log-level CLI option, if the application features a CLI.

    \n

    Note that the logging target property is ignored, as its levels do not match the Rust standard levels we use (those of the log crate).

    \n

    Note that when building with a release profile (i.e., target property build-type is not Debug), all log statements with level debug and trace are removed from the executable, and cannot be turned on at runtime. A warning is produced by the executable if you try to use these levels explicitly.

    \n
    \n

    Libraries Available to Programmers

    \n
    \n

    Libraries Available in All Programs

    \n

    Reactions in C can use a number of pre-defined functions, macros, and constants without having to explicitly include any header files:

    \n
      \n
    • \n

      Time and tags (tag.h):

      \n
        \n
      • Specifying time value, such as MSEC and FOREVER
      • \n
      • Time data types, such as tag_t and instant_t
      • \n
      • Obtaining tag and time information, e.g. lf_time_logical and lf_time_physical
      • \n
      \n
    • \n
    • \n

      Ports

      \n
        \n
      • Writing to output ports, such as lf_set and lf_set_token (set.h)
      • \n
      • Iterating over sparse multiports, such as lf_multiport_iterator and lf_multiport_next (port.h)
      • \n
      \n
    • \n
    • \n

      Scheduling actions

      \n
        \n
      • Schedule future events, such as lf_schedule and lf_schedule_value (api.h)
      • \n
      \n
    • \n
    • \n

      File Access

      \n
        \n
      • LF_SOURCE_DIRECTORY: A C string giving the full path to the directory containing the .lf file of the program.
      • \n
      • LF_PACKAGE_DIRECTORY: A C string giving the full path to the directory that is the root of the project or package (normally, the directory above the src directory).
      • \n
      • LF_FILE_SEPARATOR: A C string giving the file separator for the platform containing the .lf file (”/” for Unix-like systems, ”\\” for Windows).
      • \n
      \n
    • \n
    \n

    These are useful when your application needs to open and read additional files. For example, the following C code can be used to open a file in a subdirectory called dir of the directory that contains the .lf file:

    \n
        const char* path = LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "dir" LF_FILE_SEPARATOR "filename"\n    FILE* fp = fopen(path, "rb");
    \n
      \n
    • \n

      Miscellaneous

      \n
        \n
      • Changing modes in modal models, lf_set_mode (set.h)
      • \n
      • Checking deadlines, lf_check_deadline (api.h)
      • \n
      • Defining and recording tracepoints, such as register_user_trace_event and tracepoint (trace.h)
      • \n
      • Printing utilities, such as lf_print and lf_print_error (util.h)
      • \n
      • Logging utilities, such as LF_PRINT_LOG and LF_PRINT_DEBUG (util.h)
      • \n
      \n
    • \n
    \n

    Standard C Libraries

    \n

    The generated C code automatically includes the following standard C libraries (see also the C standard library header files):

    \n
      \n
    • limits.h (Defines INT_MIN, INT_MAX, etc.)
    • \n
    • stdbool.h (Defines bool datatype and true and false constants)
    • \n
    • stddef.h (Defines size_t, NULL, etc.)
    • \n
    • stdint.h (Defines int64_t, int32_t, etc.)
    • \n
    • stdlib.h (Defines exit, getenv, atoi, etc.)
    • \n
    \n

    Hence, programmers are free to use functions from these libraries without explicitly providing a #include statement. Nevertheless, providing one is harmless and may be good form. In particular, future releases may not include these header files

    \n

    Available Libraries Requiring #include

    \n

    More sophisticated library functions require a #include statement in a $preamble$.\nSpecifically, platform.h includes the following:

    \n
      \n
    • Sleep functions such as lf_sleep
    • \n
    • Mutual exclusion such as lf_critial_section_enter and lf_critical_section_exit
    • \n
    • Threading functions such as lf_thread_create
    • \n
    \n

    The threading functions are only available for platforms that support multithreading.

    \n

    Available Libraries Requiring #include, a files entry, and a cmake-include

    \n

    A few utility libraries are provided, but require considerably more setup.\nThese also help to illustrate how to incorporate your own libraries.

    \n\n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    Scheduler Target Property

    \n

    The scheduler target property is used to select the scheduler used by the C runtime. This scheduler determines the exact order in which reactions are processed, as long as the order complies with the deterministic semantics of Lingua Franca. It also assigns reactions to user-level threads and can thereby influence the assignment of reactions to processors.

    \n

    Because the C runtime scheduler operates at a higher level of abstraction than the OS, none of the scheduling policies that we currently support allow preemption; furthermore, they do not control migration of threads between processors.

    \n

    Another limitation of these schedulers is that they are constrained to process the reaction graph breadth-first. We define the level of a reaction r to be the length of the longest chain of causally dependent reactions that are all (causally) upstream of r. Current LF schedulers process one level of reactions at a time, but this constraint is more restrictive than necessary to implement Lingua Franca’s semantics and is notable only for its effect on execution times.

    \n

    The following schedulers are available:

    \n
      \n
    • NP (non-preemptive): This scheduler is the default scheduler. It ignores deadlines.
    • \n
    • GEDF_NP (global earliest-deadline-first, non-preemptive): When the semantics of Lingua Franca allows for concurrent execution of two or more ready reactions with the same level at a particular tag, this scheduler will prioritize the reaction with the earliest deadline to run first. Reactions with no explicit deadline implicitly have an infinitely late deadline.
    • \n
    • adaptive: This experimental scheduler behaves similarly to the NP scheduler, with the additional limitation that it is designed for applications that have potentially wide variability in physical execution times. It performs experiments to measure execution times at runtime to determine the degree of exploitable parallelism in various parts of the program. This lets it automate judgments which are made more naively by the other schedulers and which are typically made by the programmer in general-purpose languages.
    • \n
    \n
    \n

    Target Implementation Details

    \n
    \n

    Included Libraries

    \n

    Definitions for the following do not need to be explicitly included because the code generator exposes them in the user namespace automatically:

    \n
      \n
    • Functions and macros used to set ports and iterate over multiports
    • \n
    • Functions and macros used to schedule actions
    • \n
    • Functions and macros used to set a reactor’s mode
    • \n
    • Functions and macros used to create trace points
    • \n
    • Logging utility functions
    • \n
    • Typedefs relating to time and logical time, including tag_t, instant_t, interval_t, and microstep_t
    • \n
    • API functions for obtaining timing information about the current program execution, including the current physical and logical time
    • \n
    \n

    Some standard C libraries are exposed to the user through reactor.h, including stddef.h,\nstdio.h, and stdlib.h. In addition, math.h gets automatically included. However, users who wish to avoid breaking changes between releases should\nconsider including these libraries explicitly instead of relying on their being exposed by the\nruntime.

    \n

    Users who wish to include functionality that has a platform-specific implementation may choose to\nexplicitly include platform.h, which provides a uniform interface for various concurrency\nprimitives and sleep functions.

    \n

    Multithreaded Implementation

    \n

    By default, the C runtime system uses multiple worker threads in order to take advantage of multicore execution. The number of worker threads will match the number of cores on the machine unless the workers argument is given in the target statement or the --workers command-line argument is given.

    \n

    Upon initialization, the main thread will create the specified number of worker threads.\nExecution proceeds in a manner similar to the single threaded implementation\nexcept that the worker threads concurrently draw reactions from the reaction queue.\nThe execution algorithm ensures that no reaction executes until all reactions that it depends on have executed or it has been determined that they will not execute at the current tag.

    \n

    Single Threaded Implementation

    \n

    By giving the single-threaded (target option)[/docs/handbook/target-declaration#single-threaded] or the --single-threaded (command-line argument)[/docs/handbook/target-declaration#command-line-arguments], the generated program will execute the program using only a single thread. This option is most useful for creating programs to run on bare-metal microprocessors that have no threading support. On such platforms, mutual exclusion is typically realized by disabling interrupts.

    \n

    The execution strategy is to have two queues of pending accessor invocations, one that is sorted by\ntag (the event queue) and one that is sorted by priority (the reaction queue).\nExecution proceeds as follows:

    \n
      \n
    1. \n

      At initialization, an event for each timer is put on the event queue and logical time is initialized to the current time, represented as the number of nanoseconds elapsed since January 1, 1970.

      \n
    2. \n
    3. \n

      At each logical time, pull all events from event queue that have the same earliest tag, find the reactions that these events trigger, and put them on the reaction queue. If there are no events on the event queue, then exit the program (unless the --keepalive true (command-line argument)[/docs/handbook/target-declaration#command-line-arguments] is given).

      \n
    4. \n
    5. \n

      Wait until physical time matches or exceeds that earliest timestamp (unless the --fast true (command-line argument)[/docs/handbook/target-declaration#command-line-arguments] is given). Then advance logical time to match that earliest timestamp.

      \n
    6. \n
    7. \n

      Execute reactions in order of priority from the reaction queue. These reactions may produce outputs, which results in more events getting put on the reaction queue. Those reactions are assured of having lower priority than the reaction that is executing. If a reaction calls lf_schedule(), an event will be put on the event queue, not the reaction queue.

      \n
    8. \n
    9. \n

      When the reaction queue is empty, go to 2.

      \n
    10. \n
    \n
    \n
    \n

    Unlike the C target, the Cpp target implements more of the analysis and setup of a Lingua Franca in the runtime system. The runtime system is define in the reactor-cpp repository on GitHub. See that repo for details.

    \n
    \n
    \n

    The Python target is built on top of the C runtime to enable maximum efficiency where possible. It uses the single-threaded C runtime by default but will switch to the multi-threaded C runtime if a physical action is detected. The threading target property can be used to override this behavior.

    \n

    Running lfc on a XXX.lf program that uses the Python target specification on a\nLinux machine will create the following files (other operating systems will have\na slightly different structure and/or files):

    \n
    ├── src\n│   └── XXX.lf\n└── src-gen\n    └── XXX\n        ###### Files related to the Python C extension module for XXX ######\n        ├── build               # Temporary files for setuptools\n        ├── core                # Core C runtime files\n        ├── ctarget.c           # C target API implementations\n        ├── ctarget.h           # C target API definitions\n        ├── LinguaFrancaXXX*.so # The Python C extension module for XXX\n        ├── pythontarget.c      # Python target API implementations\n        ├── pythontarget.h      # Python target API definitions\n        ├── setup.py            # Setup file used to build the Python C extension\n        ├── XXX.c               # Source code of the Python C extension\n        ###### Files containing the Python code ######\n        └── XXX.py              # Python file containing reactors and reaction code\n
    \n

    There are two major components in the src-gen/XXX directory that together enable the execution of a Python target application:

    \n
      \n
    • An XXX.py file containing the user code (e.g., reactor definitions and reactions).
    • \n
    • The source code for a Python C extension module called LinguaFrancaXXX containing the C runtime, as well as hooks to execute the user-defined reactions.
    • \n
    \n

    The interactions between the src-gen/XXX/XXX.py file and the LinguaFrancaXXX module are explained below.

    \n

    The XXX.py file containing user code

    \n

    The XXX.py file contains all the reactor definitions in the form of Python classes. The contents of a reactor are converted as follows:

    \n
      \n
    • Each Reaction in a reactor definition will be converted to a class method.
    • \n
    • Each Parameter will be converted to a class property to make it read-only.
    • \n
    • Each State variable will be converted to an instance variable.
    • \n
    • Each trigger and effect will be converted to an object passed as a method function argument to reaction methods, allowing the body of the reaction to access them.
    • \n
    • Each reactor Preamble will be put in the class definition verbatim.
    • \n
    \n

    Finally, each reactor class instantiation will be converted to a Python object class instantiation.

    \n

    For example, imagine the following program:

    \n
    # src/XXX.lf\ntarget Python;\nreactor Foo(bar(0)) {\n  preamble {=\n    import random\n  =}\n  state baz\n  input _in\n  logical action act\n  reaction(_in, act) {=\n    # Body of the reaction\n    self.random.seed() # Note the use of self\n  =}\n}\nmain reactor {\n  foo = new Foo()\n}\n
    \n

    Th reactor Foo and its instance, foo, will be converted to

    \n
    # src-gen/XXX/XXX.py\n...\n# Python class for reactor Foo\nclass _Foo:\n\n    # From the preamble, verbatim:\n    import random\n    def __init__(self, **kwargs):\n        #Define parameters and their default values\n        self._bar = 0\n        # Handle parameters that are set in instantiation\n        self.__dict__.update(kwargs)\n\n        # Define state variables\n        self.baz = None\n\n    @property\n    def bar(self):\n        return self._bar\n\n    def reaction_function_0(self , _in, act):\n        # Body of the reaction\n        self.random.seed() # Note the use of self\n        return 0\n\n\n# Instantiate classes\nxxx_lf = [None] * 1\nxxx_foo_lf = [None] * 1\n# Start initializing XXX of class XXX\nfor xxx_i in range(1):\n    bank_index = xxx_i\n    xxx_lf[0] = _XXX(\n        _bank_index = 0,\n    )\n    # Start initializing XXX.foo of class Foo\n    for xxx_foo_i in range(1):\n        bank_index = xxx_foo_i\n        xxx_foo_lf[0] = _Foo(\n            _bank_index = 0,\n            _bar=0,\n        )\n...\n
    \n

    The generated LinguaFrancaXXX Python module (a C extension module)

    \n

    The rest of the files in src-gen/XXX form a Python C extension\nmodule\ncalled LinguaFrancaXXX that can be built by executing python3 setup.py build_ext --inplace in the src-gen/XXX/ folder. In this case, Python will\nread the instructions in the src-gen/XXX/setup.py file and build a\nLinguaFrancaXXX module in src-gen/XXX/. The --inplace flag puts the\ncompiled extension (the LinguaFrancaXXX*.so in the example above) in the\nsrc-gen directory alongside the XXX.py file.

    \n

    As mentioned before, the LinguaFrancaXXX module is separate from\nsrc-gen/XXX/XXX.py but interacts with it. Next, we explain this interaction.

    \n

    Interactions between XXX.py and LinguaFrancaXXX

    \n

    The LinguaFrancaXXX module is imported in src-gen/XXX/XXX.py:

    \n
    from LinguaFrancaXXX import *\n
    \n

    This is done to enable the main function in src-gen/XXX/XXX.py to make a call to the start() function, which is part of the generated (and installed) LinguaFrancaXXX module. This function will start the main event handling loop of the C runtime.

    \n

    From then on, LinguaFrancaXXX will call reactions that are defined in src-gen/XXX/XXX.py when needed.

    \n

    The LinguaFrancaBase package

    \n

    LinguaFrancaBase is a package that contains several helper methods and definitions that are necessary for the Python target to work. This module is installable via python3 -m pip install LinguaFrancaBase but is automatically installed if needed during the installation of LinguaFrancaXXX. The source code of this package can be found on GitHub.

    \n

    This package’s modules are imported in the XXX.py program:

    \n
    from LinguaFrancaBase.constants import * #Useful constants\nfrom LinguaFrancaBase.functions import * #Useful helper functions\nfrom LinguaFrancaBase.classes import * #Useful classes\n
    \n

    Already imported Python modules

    \n

    The following packages are already imported and thus do not need to be re-imported by the user:

    \n
    import os\nimport sys\nimport copy\n
    \n
    \n
    \n

    When a TypeScript reactor is compiled, the generated code is placed inside a project directory. This is because there are two steps of compilation. First, the Lingua Franca compiler generates a TypeScript project from the TypeScript reactor code. Second, the Lingua Franca compiler runs a TypeScript compiler on the generated TypeScript project to produce executable JavaScript. This is illustrated below:

    \n
    Lingua Franca (.lf) ==> TypeScript (.ts) ==> JavaScript (.js)\n
    \n

    Assuming the directory containing our Lingua Franca file Foo.lf is named TS, the compiler will generate the following:

    \n
      \n
    1. TS/package.json
    2. \n
    3. TS/node_modules
    4. \n
    5. TS/Foo/tsconfig.json
    6. \n
    7. TS/Foo/babel.config.js
    8. \n
    9. TS/Foo/src/
    10. \n
    11. TS/Foo/dist/
    12. \n
    \n

    Items 1, 3, and 4 are configuration files for the generated project. Item 2 is a node_modules directory with contents specified by item 1. Item 5 is the directory for generated TypeScript code. Item 6 is the directory for compiled JavaScript code. In addition to the generated code for your Lingua Franca program, items 5 and 6 include libraries from the reactor-ts submodule.

    \n

    The Lingua Franca compiler automatically invokes other programs as it compiles a Lingua Franca (.lf) file to a Node.js executable JavaScript (.js) file. The files package.json, babel.config.js, and tsconfig.json are used to configure the behavior of those other programs. Whenever you compile a .lf file for the first time, the Lingua Franca compiler will copy default versions of these configuration files into the new project so the other programs can run. The Lingua Franca compiler will only copy a default configuration file into a project if that file is not already present in the generated project. This means you, the reactor programmer, may safely modify these configuration files to control the finer points of compilation. Beware, other generated files in the project’s src and dist directories may be overwritten by the compiler.

    \n

    package.json

    \n

    Node.js uses a package.json file to describe metadata relevant to a Node project. This includes a list of project dependencies (i.e. modules) used by the project. When the Lingua Franca compiler copies a default package.json file into a Lingua Franca project that doesn’t already have a package.json, the compiler runs the command npm install to create a node_modules directory. The default package.json only lists dependencies for the reactor-ts submodule. Follow these instructions to modify package.json if you want to use other Node modules in your reactors.

    \n

    tsconfig.json

    \n

    After generating a TypeScript program from a .lf file, the Lingua Franca compiler uses the TypeScript compiler tsc to run a type check. The behavior of tsc is configured by the tsconfig.json file. You probably won’t need to modify tsconfig.json, but you can if you know what you’re doing.

    \n

    babel.config.js

    \n

    If the tsc type check was successful, the Lingua Franca compiler uses babel to compile the generated TypeScript code into JavaScript. (This blog post articulates the advantages of using babel over tsc to generate JavaScript.) There are many different flavors of JavaScript and the babel.config.js file specifies exactly what babel should generate. This is the file to edit if you want the Lingua Franca compiler to produce a different version of JavaScript as its final output.

    \n

    Debugging Type Errors

    \n

    Let’s take the minimal reactor example, and intentionally break it by adding a type error into the reaction.

    \n
    target TypeScript;\nmain reactor ReactionTypeError {\n    timer t;\n    reaction(t) {=\n        let foo:number = "THIS IS NOT A NUMBER";\n        console.log("Hello World.");\n    =}\n}\n
    \n

    This reactor will not compile, and should you attempt to compile it you will get an output from the compiler which looks something like this:

    \n
    --- Standard output from command:\nsrc/ReactionTypeError.ts(23,25): error TS2322: Type '"THIS IS NOT A NUMBER"' is not assignable to type 'number'.\n\n--- End of standard output.
    \n

    In particular the output

    \n
    src/ReactionTypeError.ts(23,25): error TS2322: Type '"THIS IS NOT A NUMBER"' is not assignable to type 'number'.
    \n

    identifies the problem: surprisingly, the string \"THIS IS NOT A NUMBER\" is not a number. However the line information (23,25) is a little confusing because it points to the location of the type error in the generated .ts file ReactionTypeError/src/ReactionTypeError.ts not in the original .lf file ReactionTypeError.lf. The .ts files produced by the TypeScript code generator are quite readable if you are familiar with the reactor-ts submodule, but even if you aren’t familiar it is not too difficult to track down the problem. Just open ReactionTypeError/src/ReactionTypeError.ts in your favorite text editor (we recommend Visual Studio for its excellent TypeScript integration) and look at line 23.

    \n
    14        this.addReaction(\n15            new Triggers(this.t),\n16            new Args(this.t),\n17            function (this, __t: Readable<Tag>) {\n18                // =============== START react prologue\n19                const util = this.util;\n20                let t = __t.get();\n21                // =============== END react prologue\n22                try {\n23                    let foo:number = "THIS IS NOT A NUMBER";\n24                    console.log("Hello World.");\n25                } finally {\n26                    // =============== START react epilogue\n27\n28                    // =============== END react epilogue\n29                }\n30            }\n31        );\n
    \n

    There (inside the try block) we can find the problematic reaction code. Reaction code is copied verbatim into generated .ts files.

    \n

    It can be a bit harder to interpret type errors outside of reaction code, but most type error messages are still relatively clear. For example if you attempt to connect a reactor output to an incompatibly typed input like:

    \n
    target TypeScript;\nmain reactor ConnectionError {\n    s = new Sender();\n    r = new Receiver();\n    s.foo -> r.bar;\n}\nreactor Sender {\n    output foo:number;\n}\nreactor Receiver {\n    input bar:string;\n}\n
    \n

    you should get an error like

    \n
    --- Standard output from command:\nsrc/InputTypeError.ts(36,23): error TS2345: Argument of type 'OutPort<number>' is not assignable to parameter of type 'Port<string>'.\n  Types of property 'value' are incompatible.\n    Type 'number | undefined' is not assignable to type 'string | undefined'.\n      Type 'number' is not assignable to type 'string | undefined'.\n\n--- End of standard output.
    \n

    The key message being Argument of type 'OutPort<number>' is not assignable to parameter of type 'Port<string>'.

    \n

    One last tip: if you attempt to reference a port, action, timer etc. named foo that isn’t declared in the triggers, uses, or effects declaration of the reaction, you will get the error Cannot find name 'foo' in the reaction body.

    \n

    Utility Function Reference

    \n

    These utility functions may be called within a TypeScript reaction:

    \n

    util.requestShutdown(): void Ends execution after one microstep. See Stopping Execution.

    \n

    util.getCurrentTag(): Tag Gets the current (logical) tag. See Tags.

    \n

    util.getCurrentLogicalTime(): TimeValue Gets the current logical TimeValue. See Time.

    \n

    util.getCurrentPhysicalTime(): TimeValue Gets the current physical TimeValue. See Time.

    \n

    util.getElapsedLogicalTime(): TimeValue Gets the elapsed logical TimeValue from execution start. See Time.

    \n

    util.getElapsedPhysicalTime(): TimeValue Gets the elapsed physical TimeValue from execution start. See Time.

    \n

    util.success(): void Invokes the reactor-ts App’s default success callback. FIXME: Currently doesn’t do anything in Lingua Franca.

    \n

    util.failure(): void Invokes the reactor-ts App’s default failure callback. Throws an error.

    \n

    Building Reactor-ts Documentation

    \n

    To build and view proper documentation for time.ts (and other reactor-ts libraries), install typedoc and run

    \n
    typedoc --out docs src\n
    \n

    from the root of the reactor-ts. You probably already have the reactor-ts submodule at

    \n
    lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/
    \n

    You should see an output like.

    \n
    Using TypeScript 3.8.3 from /usr/local/lib/node_modules/typescript/lib\nRendering [========================================] 100%\n\nDocumentation generated at /Users/<username>/git/lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/docs
    \n

    Open that path in a browser with /index.html appended to the end like

    \n
    /Users/<username>/git/lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/docs/index.html
    \n

    to navigate the docs.

    \n
    \n
    \n

    Target Properties

    \n

    Target properties may be mentioned like so:

    \n
    target Rust {\n    // enables single-file project layout\n    single-file-project: false,\n    // timeout for the execution. The program will shutdown at most after the specified duration.\n    timeout: 3 sec,\n\n    cargo-features: ["cli"]\n}\n
    \n

    See Target Declaration for the full list of supported target properties.

    \n

    The Executable

    \n

    The executable name is the name of the main reactor transformed to snake_case: main reactor RustProgram will generate rust_program. See Command-Line Arguments for details.

    \n

    File layout

    \n

    The Rust code generator generates a Cargo project with a classical layout:

    \n
    ├── Cargo.lock\n├── Cargo.toml\n├── src\n│   ├── main.rs\n│   └── reactors\n│       ├── mod.rs\n|       ├── ...\n|\n└── target\n    ├── ...
    \n

    The module structure is as follows:

    \n
      \n
    • the crate has a module reactors
    • \n
    • each LF reactor has its own submodule of reactors. For instance, Minimal.lf will generate minimal.rs. The name is transformed to snake_case.
    • \n
    \n

    This means that to refer to the contents of another reactor module, e.g. that of Other.lf, you have to write super::other::Foo. This is relevant to access preamble items.

    \n

    Single-file layout

    \n

    The Rust target supports an alternative file layout, where all reactors are generated into the main.rs file, making the project fit in a single file (excluding Cargo.toml). The module structure is unchanged: the file still contains a mod reactors { ... } within which each reactor has its mod foo { ... }. You can thus change the layout without having to update any LF code.

    \n

    Set the target property single-file-project: true to use this layout.

    \n

    Note: this alternative layout is provided for the purposes of making self-contained benchmark files. Generating actual runnable benchmarks from an LF file may be explored in the future.

    \n

    Specifying dependencies

    \n

    The Rust code generator leverages Cargo to allow LF programs to profit from Rust’s large package ecosystem. The code generator may also link support files written in pure Rust into the generated crate. Target properties are used to achieve all this.

    \n

    Adding cargo dependencies

    \n

    The cargo-dependencies target property may be used to specify dependencies on crates coming from crates.io. Here’s an example:

    \n
    target Rust {\n   cargo-dependencies: {\n      termcolor: "0.8"\n   }\n};\n
    \n

    The value of the cargo-dependencies property is a map of crate identifiers to a dependency-spec. An informal example follows:

    \n
    cargo-dependencies: {\n   // Name-of-the-crate: "version"\n   rand: "0.8",\n   // Equivalent to using an explicit map:\n   rand: {\n     version: "0.8"\n   },\n   // The map allows specifying more details\n   rand: {\n     // A path to a local unpublished crate.\n     // Note 'path' is mutually exclusive with 'git'.\n     path: "/home/me/Git/local-rand-clone"\n   },\n   rand: {\n     // A URL to a git repo\n     git: "https://github.com/me/rand",\n     // Specify an explicit Git revision number\n     rev: "abcdef1234"\n   },\n   rand: {\n     version: "0.8",\n     // you can specify cargo features\n     features: ["some-cargo-feature",]\n   }\n}\n
    \n

    When a dependency-spec is specified as an object, its key-value pairs correspond directly to those of a Cargo dependency specification. For instance for the following dependency spec:

    \n
       rand: {\n     version: "0.8",\n     // you can specify cargo features\n     features: ["some-cargo-feature",]\n   }\n
    \n

    we add the following to the generated Cargo.toml:

    \n
    [dependencies.rand]\nversion = "0.8"\nfeatures = ["some-cargo-feature"]\n
    \n

    Not all keys are necessarily supported though, e.g. the registry key is not supported (yet).

    \n

    Configuring the runtime

    \n

    The runtime crate can be configured just like other crates, using the cargo-dependencies target property, e.g.:

    \n
    cargo-dependencies: {\n   reactor_rt: {\n     features: ["parallel-runtime"]\n   }\n}\n
    \n

    The dependency is always included, with defaults picked by LFC. The location information (path/git/version key) is optional.\nSee reactor_rt for the supported features.

    \n

    Linking support files

    \n

    You can link-in additional rust modules using the rust-include target property:

    \n
    target Rust {\n  rust-include: ["foo.rs"]\n};\n
    \n

    The property is a list of paths (relative to the directory containing the .lf file). Each path should either point to a Rust file (.rs), or a directory that contains a mod.rs file. Each of those will be copied to the src directory of the generated Cargo project, and linked in to the main.rs file.

    \n

    To refer to the included module, you can use e.g. crate::foo if your module is named foo.

    \n

    Generation scheme

    \n

    Each reactor generates its own struct which contains state variables. For instance,

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    LFGenerated Rust
    \n
    reactor SomeReactor {\n  state field: u32(0)\n}\n
    \n
    \n
    struct SomeReactor {\n  field: u32\n}\n
    \n
    \n

    In the following we refer to that struct as the state struct.

    \n

    Reactions

    \n

    Reactions are each generated in a separate method of the reactor struct. Reaction names are unspecified and may be mangled to prevent explicit calling. The parameters of that method are

    \n
      \n
    • &mut self: the state struct described above,
    • \n
    • ctx: &mut ReactionCtx: the context object for the reaction execution,
    • \n
    • For each dependency, a parameter is generated.\n
        \n
      • If the dependency is a component of this reactor, the name of the parameter is just the name of the component
      • \n
      • If the dependency is a port of a child reactor, the name of the parameter is <name of the child instance>__<name of the port>, e.g. child__out for child.out.
      • \n
      • The type of the parameter depends on the kind of dependency and of component:
      • \n
      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      ComponentUse/trigger dependencyEffect dependency
      \n\n\n

      Port of type T

      \n
      \n

      &ReadablePort<T>

      \n
      \n

      WritablePort<T>

      \n
      \n

      Logical action of type T

      \n
      \n

      &LogicalAction<T>

      \n
      \n

      &mut LogicalAction<T>

      \n
      \n

      Physical action of type T

      \n
      \n

      &PhysicalActionRef<T>

      \n
      \n

      &mut PhysicalActionRef<T>

      \n
      Timer\n

      &Timer

      \n
      \n

      n/a

      \n
      \n

      Port bank of type T

      \n
      \n

      &ReadablePortBank<T>

      \n
      \n

      WritablePortBank<T>

      \n
      \n

      Undeclared dependencies, and dependencies on timers and startup or shutdown, do not generate a parameter.

      \n

      The ReactionCtx object is a mediator to manipulate all those dependency objects. It has methods to set ports, schedule actions, retrieve the current logical time, etc.

      \n

      For instance:

      \n
      reactor Source {\n    output out: i32;\n    reaction(startup) -> out {=\n        ctx.set(out, 76600)\n    =}\n}\n
      \n

      In this example, the context object ctx is used to set a port to a value. The port is in scope as out.

      \n
      \n

      ⚠ TODO when the runtime crate is public link to the docs, they should be the most exhaustive documentation.

      \n
      \n
    \n````","headings":[{"value":"Overview","depth":2},{"value":"Requirements","depth":2},{"value":"Limitations","depth":2},{"value":"The Target Specification","depth":2},{"value":"Parameters and State Variables","depth":2},{"value":"Array Values for Parameters","depth":3},{"value":"Array Values for States","depth":3},{"value":"States and Parameters with Struct Values","depth":3},{"value":"Array-Valued Parameters","depth":3},{"value":"State Variables","depth":3},{"value":"Array Expressions for State Variables and Parameters","depth":3},{"value":"Assigning Arbitrary Initial Expressions to State Variables and Parameters","depth":3},{"value":"Array or Object Parameters","depth":3},{"value":"Inputs and Outputs","depth":2},{"value":"Sending and Receiving Data","depth":3},{"value":"Persistent Inputs","depth":3},{"value":"Fixed Length Array Inputs and Outputs","depth":3},{"value":"Variable Length Array Inputs and Outputs","depth":3},{"value":"Dynamically Allocated Data","depth":3},{"value":"Mutable Inputs","depth":3},{"value":"String Types","depth":3},{"value":"Macros For Setting Output Values","depth":3},{"value":"Sending and Receiving Large Data Types","depth":3},{"value":"Sending and Receiving Objects","depth":3},{"value":"Sending and Receiving Custom Types","depth":3},{"value":"Time","depth":2},{"value":"Tags","depth":3},{"value":"Summary of Time Functions","depth":3},{"value":"Actions","depth":2},{"value":"Zero-Delay Actions","depth":3},{"value":"Actions With Values","depth":3},{"value":"Zero-Delay Actions","depth":3},{"value":"Actions With Values","depth":3},{"value":"Schedule Functions","depth":3},{"value":"Stopping Execution","depth":2},{"value":"Log and Debug Information","depth":2},{"value":"Libraries Available to Programmers","depth":2},{"value":"Libraries Available in All Programs","depth":4},{"value":"Standard C Libraries","depth":4},{"value":"Available Libraries Requiring #include","depth":4},{"value":"Available Libraries Requiring #include, a files entry, and a cmake-include","depth":4},{"value":"Scheduler Target Property","depth":2},{"value":"Target Implementation Details","depth":2},{"value":"Included Libraries","depth":3},{"value":"Multithreaded Implementation","depth":3},{"value":"Single Threaded Implementation","depth":3},{"value":"The XXX.py file containing user code","depth":3},{"value":"The generated LinguaFrancaXXX Python module (a C extension module)","depth":3},{"value":"Interactions between XXX.py and LinguaFrancaXXX","depth":3},{"value":"The LinguaFrancaBase package","depth":3},{"value":"Already imported Python modules","depth":3},{"value":"package.json","depth":3},{"value":"tsconfig.json","depth":3},{"value":"babel.config.js","depth":3},{"value":"Debugging Type Errors","depth":3},{"value":"Utility Function Reference","depth":3},{"value":"Building Reactor-ts Documentation","depth":3},{"value":"Target Properties","depth":3},{"value":"The Executable","depth":3},{"value":"File layout","depth":3},{"value":"Single-file layout","depth":4},{"value":"Specifying dependencies","depth":3},{"value":"Adding cargo dependencies","depth":4},{"value":"Configuring the runtime","depth":4},{"value":"Linking support files","depth":4},{"value":"Generation scheme","depth":3},{"value":"Reactions","depth":4}],"frontmatter":{"permalink":"/docs/handbook/target-language-details","title":"Target Language Details","oneline":"Detailed reference for each target langauge.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Expressions","oneline":"Expressions in Lingua Franca.","permalink":"/docs/handbook/expressions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Target Declaration","oneline":"The target declaration and its parameters in Lingua Franca.","permalink":"/docs/handbook/target-declaration"}}}},"pageContext":{"id":"3-target-language-details","slug":"/docs/handbook/target-language-details","repoPath":"/packages/documentation/copy/en/reference/Target Language Details.md","previousID":"24e3b5ae-ac22-5a79-956a-4258d40ae77c","nextID":"de456861-0847-5726-aba2-da3ba779bd9d","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/target-language-details","result":{"data":{"markdownRemark":{"id":"3af06b85-61a2-5d81-a7ad-232624b3eaf6","excerpt":"$page-showing-target$ Overview In the C reactor target for Lingua Franca, reactions are written in C and the code generator generates one or more standalone C…","html":"

    $page-showing-target$

    \n

    Overview

    \n
    \n

    In the C reactor target for Lingua Franca, reactions are written in C and the code generator generates one or more standalone C programs that can be compiled and run on several platforms. It has been tested on macOS, Linux, Windows, and at least one bare-iron embedded platform. The single-threaded version (which you get by setting the threading target parameter to false) is the most portable, requiring only a handful of common C libraries (see Included Libraries below). The multithreaded version requires a small subset of the POSIX thread library (pthreads) and transparently executes in parallel on a multicore machine while preserving the deterministic semantics of Lingua Franca.

    \n

    Note that C is not a safe language. There are many ways that a programmer can circumvent the semantics of Lingua Franca and introduce nondeterminism and illegal memory accesses. For example, it is easy for a programmer to mistakenly send a message that is a pointer to data on the stack. The destination reactors will very likely read invalid data. It is also easy to create memory leaks, where memory is allocated and never freed. Here, we provide some guidelines for a style for writing reactors that will be safe.

    \n

    NOTE: If you intend to use C++ code or import C++ libraries in the C target, we provide a special CCpp target that automatically uses a C++ compiler by default. Alternatively, you might want to use the Cpp target.

    \n
    \n
    \n

    In the C++ reactor target for Lingua Franca, reactions are written in C++ and the code generator generates a standalone C++ program that can be compiled and run on all major platforms. Our continuous integration ensures compatibility with Windows, macOS, and Linux.\nThe C++ target solely depends on a working C++ build system including a recent C++ compiler (supporting C++17) and CMake (>= 3.5). It relies on the reactor-cpp runtime, which is automatically fetched and compiled in the background by the Lingua Franca compiler.

    \n

    Note that C++ is not a safe language. There are many ways that a programmer can circumvent the semantics of Lingua Franca and introduce nondeterminism and illegal memory accesses. For example, it is easy for a programmer to mistakenly send a message that is a pointer to data on the stack. The destination reactors will very likely read invalid data. It is also easy to create memory leaks, where memory is allocated and never freed. Note, however, that the C++ reactor library is designed to prevent common errors and to encourage a safe modern C++ style. Here, we introduce the specifics of writing Reactor programs in C++ and present some guidelines for a style that will be safe.

    \n
    \n
    \n

    In the Python reactor target for Lingua Franca, reactions are written in Python. The user-written reactors are then generated into a Python 3 script that can be executed on several platforms. The Python target has been tested on Linux, macOS, and Windows. To facilitate efficient and fast execution of Python code, the generated program relies on a C extension to facilitate Lingua Franca API such as set and schedule. To learn more about the structure of the generated Python program see Implementation Details.

    \n

    Python reactors can bring the vast library of scientific modules that exist for Python into a Lingua Franca program. Moreover, since the Python reactor target is based on a fast and efficient C runtime library, Lingua Franca programs can execute much faster than native equivalent Python programs in many cases. Finally, interoperability with C reactors is planned for the future.

    \n

    In comparison to the C target, the Python target can be up to an order of magnitude slower. However, depending on the type of application and the implementation optimizations in Python, you can achieve an on-par performance to the C target in many applications.

    \n

    NOTE: A Python C\nextension is\ngenerated for each Lingua Franca program (see Implementation\nDetails). This extension module will\nhave the name LinguaFranca[your_LF_program_name].

    \n
    \n
    \n

    In the TypeScript reactor target for Lingua Franca, reactions are written in TypeScript and the code generator generates a standalone TypeScript program that can be compiled to JavaScript and run on Node.js.

    \n

    TypeScript reactors bring the strengths of TypeScript and Node.js to Lingua Franca programming. The TypeScript language and its associated tools enable static type checking for both reaction code and Lingua Franca elements like ports and actions. The Node.js JavaScript runtime provides an execution environment for asynchronous network applications. With Node.js comes Node Package Manager (npm) and its large library of supporting modules.

    \n

    In terms of raw performance on CPU intensive operations, TypeScript reactors are about two orders of magnitude slower than C reactors. But excelling at CPU intensive operations isn’t really the point of Node.js (or by extension TypeScript reactors). Node.js is about achieving high throughput on network applications by efficiently handling asynchronous I/O operations. Keep this in mind when choosing the right Lingua Franca target for your application.

    \n
    \n
    \n

    Important: The Rust target is still quite preliminary. This is early WIP documentation to let you try it out if you’re curious

    \n

    In the Rust reactor target for Lingua Franca, reactions are written in Rust and the code generator generates a standalone Rust program that can be compiled and run on platforms supported by rustc. The program depends on a runtime library distributed as the crate reactor_rt, and depends on the Rust standard library.

    \n

    Documentation for the runtime API is available here: https://lf-lang.github.io/reactor-rust/

    \n

    LF-Rust generates a Cargo project per compiled main reactor. This specification assumes in some places that the user is somewhat familiar with how Cargo works.\nIf you’re not, here’s a primer:

    \n
      \n
    • a Rust project (and its library artifact) are called a crate.
    • \n
    • Cargo is the Rust package manager and build tool. LF/Rust uses Cargo to build the generated project.
    • \n
    • Rust has extensive support for conditional compilation. Cargo features are commonly used to enable or disable the compilation of parts of a crate. A feature may also pull in additional dependencies. Cargo features only influence the compilation process; if you don’t mention the correct feature flags at compilation time, those features cannot be made available at runtime. The Rust reactor runtime crate uses Cargo features to conditionally enable some features, e.g., command-line argument parsing.
    • \n
    \n
    \n

    Requirements

    \n
    \n

    The following tools are required in order to compile the generated C source code:

    \n
      \n
    • A C compiler such as gcc
    • \n
    • A recent version of cmake (at least 3.5)
    • \n
    \n
    \n
    \n

    The following tools are required in order to compile the generated C++ source code:

    \n
      \n
    • A recent C++ compiler supporting C++17
    • \n
    • A recent version of cmake (at least 3.5)
    • \n
    \n
    \n
    \n

    To use this target, install Python 3 on your machine. See downloading Python.

    \n

    NOTE: The Python target requires a C implementation of Python (nicknamed CPython). This is what you will get if you use the above link, or with most of the alternative Python installations such as Anaconda. See Python download alternatives for more details.

    \n

    The Python reactor target relies on setuptools to be able to compile a Python\nC extension for each LF\nprogram.

    \n\n

    To install setuptools using pip3, do this:

    \n
    pip3 install setuptools\n
    \n
    \n
    \n

    First, make sure Node.js is installed on your machine. You can download Node.js here. The npm package manager comes along with Node.

    \n

    After installing Node, you may optionally install the TypeScript compiler.

    \n
    npm install -g typescript\n
    \n

    TypeScript reactor projects are created with a local copy of the TypeScript compiler, but having the TypeScript compiler globally installed can be useful for debugging type errors and type checking on the command line.

    \n
    \n
    \n

    In order to compile the generated Rust source code, you need a recent version of Cargo, the Rust package manager. See How to Install Rust and Cargo if you don’t have them on your system.

    \n

    You can use a development version of the runtime library by setting the LFC option --external-runtime-path to the root directory of the runtime library crate sources. If this variable is mentioned, LFC will ask Cargo to fetch the runtime library from there.

    \n
    \n

    Limitations

    \n
    \n
      \n
    • The C target does make any distinction between $private$ and $public$ $preamble$.
    • \n
    \n
    \n
    \n

    The C++ target does not yet implement:

    \n\n
    \n
    \n
      \n
    • The Lingua Franca lexer does not support single-quoted strings in Python. This limitation also applies to target property values. You must use double quotes.
    • \n
    \n
    \n
    \n
      \n
    • \n

      The $federated$ implementation in the TypeScript target is still quite preliminary.

      \n
    • \n
    • \n

      The TypeScript target does not yet implement methods.

      \n
    • \n
    • \n

      The TypeScript target does not yet implement modal reactors

      \n
    • \n
    \n
    \n
    \n

    The Rust target does not yet implement:

    \n\n
    \n

    The Target Specification

    \n
    \n

    To have Lingua Franca generate C code, start your .lf file with one of the following target specifications:

    \n
      target C <options>\n  target CCpp <options>\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in C, you may want to include the final semicolon for uniformity.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n

    The second form, CCpp, is used when you wish to use a C++ compiler to compile\nthe generated code, thereby allowing your C reactors to call C++ code.

    \n\n

    Here is a minimal example of a program written in the CCpp target, taken from HelloWorldCCPP.lf:

    \n
    target CCpp\nreactor HelloWorld {\n  preamble {=\n    #include <iostream> // Note that no C++ header will be included by default.\n  =}\n  reaction(startup) {=\n    std::cout << "Hello World." << std::endl;\n  =}\n}\nmain reactor {\n  a = new HelloWorld()\n}\n
    \n

    Note: Unless some feature in the C target is needed, we recommend using the Cpp target that uses a runtime that is written natively in C++.

    \n

    Note: A .lf file that uses the CCpp target cannot and should not be imported to a .lf file that uses the C target. Although these two targets use essentially the same runtime, such a scenario can cause unintended compile errors.

    \n
    \n
    \n

    To have Lingua Franca generate C++ code, start your .lf file with the following target specification:

    \n
        target Cpp\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in C++, you may want to include the final semicolon for uniformity.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n
    \n
    \n

    To have Lingua Franca generate Python code, start your .lf file with the following target specification:

    \n
        target Python\n
    \n

    Note that for all LF statements, a final semicolon is optional.

    \n

    For options to the target specification, see detailed documentation of the target options.

    \n
    \n
    \n

    To have Lingua Franca generate TypeScript code, start your .lf file with the following target specification:

    \n
        target TypeScript\n
    \n

    Note that for all LF statements, a final semicolon is optional.

    \n

    The supported target parameters and command-line options are documented in the Target Declaration documentation.

    \n
    \n
    \n

    To have Lingua Franca generate Rust code, start your .lf file with the following target specification:

    \n
        target Rust\n
    \n

    Note that for all LF statements, a final semicolon is optional. If you are writing your code in Rust, you may want to include the final semicolon for uniformity.

    \n
    \n

    Parameters and State Variables

    \n
    \n

    Reactor parameters and state variables are referenced in the C code using the\nself struct. The following\nStride\nexample modifies the Count reactor in State\nDeclaration to\ninclude both a parameter and a state variable:

    \n
    reactor Count(stride: int = 1) {\n  state count: int = 1\n  output y: int\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    lf_set(y, self->count);\n    self->count += self->stride;\n  =}\n}\n
    \n

    This defines a stride parameter with type int and initial value 1 and\na count state variable with the same type and initial value.\nThese are referenced in the reaction with the syntax self->stride and self->count respectively.

    \n

    The self Struct:\nThe code generator synthesizes a struct type in C for each reactor class and a constructor that creates an instance of this struct. By convention, these instances are called self and are visible within each reactor body. The self struct contains the parameters, state variables, and values associated with actions and ports of the reactor. Parameters and state variables are accessed directly on the self struct, whereas ports and actions are directly in scope by name, as we will see below. Let’s begin with parameters.

    \n

    It may be tempting to declare state variables in the $preamble$, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    int count = 0;\n  =}\n  output y: int\n  timer t(0, 100 msec)\n\n  reaction(t) -> y {=\n    lf_set(y, count++);\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance of the reactor, those instances will share the same variable count. Hence, don’t do this! Sharing variables across instances of reactors violates a basic principle, which is that reactors communicate only by sending messages to one another. Sharing variables will make your program nondeterministic. If you have multiple instances of the above FlawedCount reactor, the outputs produced by each instance will not be predictable, and in a multithreaded implementation, will also not be repeatable.

    \n

    Array Values for Parameters

    \n

    Parameters and state variables can have array values, though some care is needed. The ArrayAsParameter example outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence: int[] = {0, 1, 2}, n_sequence: int = 3) {\n  output out: int\n  state count: int = 0\n  logical action next\n\n  reaction(startup, next) -> out, next {=\n    lf_set(out, self->sequence[self->count]);\n    self->count++;\n    if (self->count < self->n_sequence) {\n      lf_schedule(next, 0);\n    }\n  =}\n}\n
    \n

    This uses a $logical$ $action$ to repeat the reaction, sending one element of the array in each invocation.

    \n

    In C, arrays do not encode their own length, so a separate parameter n_sequence is used for the array length. Obviously, there is potential here for errors, where the array length doesn’t match the length parameter.

    \n

    Above, the parameter default value is an array with three elements, [0, 1, 2]. The syntax for giving this default value is that of a Lingua Franca list, {0, 1, 2}, which gets converted by the code generator into a C static initializer. The default value can be overridden when instantiating the reactor using a similar syntax:

    \n
      s = new Source(sequence = {1, 2, 3, 4}, n_sequence=4)\n
    \n

    Array Values for States

    \n

    A state variable can also have an array value. For example, the MovingAverage reactor computes the moving average of the last four inputs each time it receives an input:

    \n
    reactor MovingAverageImpl {\n  state delay_line: double[] = {0.0, 0.0, 0.0}\n  state index: int = 0\n  input in: double\n  output out: double\n\n  reaction(in) -> out {=\n    // Calculate the output.\n    double sum = in->value;\n    for (int i = 0; i < 3; i++) {\n      sum += self->delay_line[i];\n    }\n    lf_set(out, sum/4.0);\n\n    // Insert the input in the delay line.\n    self->delay_line[self->index] = in->value;\n\n    // Update the index for the next input.\n    self->index++;\n    if (self->index >= 3) {\n      self->index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an array of doubles with the initial value of the array being a three-element array filled with zeros.

    \n

    States and Parameters with Struct Values

    \n

    States whose type are structs can similarly be initialized. This StructAsState example illustrates this:

    \n
    target C\npreamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nmain reactor StructAsState {\n  state s: hello_t = {"Earth", 42}\n  reaction(startup) {=\n    printf("State s.name=\\"%s\\", value=%d.\\n", self->s.name, self->s.value);\n  =}\n}\n
    \n

    Notice that state s is given type hello_t, which is defined in the $preamble$. The initial value just lists the initial values of each of the fields of the struct in the order they are declared.

    \n

    Parameters are similar:

    \n
    target C\npreamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nmain reactor StructParameter(p: hello_t = {"Earth", 42}) {\n  reaction(startup) {=\n    printf("Parameter p.name=\\"%s\\", value=%d.\\n", self->p.name, self->p.value);\n  =}\n}\n
    \n
    \n
    \n

    Reactor parameters are internally declared as const by the code generator and initialized during reactor instantiation. Thus, the value of a parameter may not be changed. See Parameters and State for examples.

    \n

    Array-Valued Parameters

    \n

    Also parameters can have fixed- or variable-sized array values. The ArrayAsParameter example outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence: std::vector<int> = {0, 1, 2}) {\n  output out: size_t\n  state count: size_t = 0\n  logical action next: void\n\n  reaction(startup, next) -> out, next {=\n    out.set(sequence[count]);\n    count++;\n    if (count < sequence.size()) {\n      next.schedule();\n    }\n  =}\n}\n
    \n

    Here, the type of sequence is explicitly given as std::vector<int>.\nA more compact alternative syntax is as follows:

    \n
    sequence: int[] = {0, 1, 2}
    \n

    The type int[] is converted to std::vector<int> by the code generator.\nAnother alternative syntax is:

    \n
    sequence: int[]({0, 1, 2})
    \n

    Here, the static initializer {0, 1, 2} is passed as a single argument to the constructor of std::vector.

    \n

    The main reactor can be parameterized:

    \n
    main reactor Hello(msg: std::string("World")) {\n  reaction(startup) {=\n    std::cout << "Hello " << msg << "!\\n";\n  =}\n}\n
    \n

    This program will print “Hello World!” by default. However, since msg is a main reactor parameter, the C++ code generator will extend the command-line argument parser and allow to override msg when invoking the program. For instance,

    \n
    bin/Hello --msg Earth\n
    \n

    will result in “Hello Earth!” being printed.

    \n

    State Variables

    \n

    A reactor may declare state variables, which become properties of each instance of the reactor. For example, the following reactor (see Count.lf and CountTest.lf) will produce the output sequence 1, 2, 3, … :

    \n
    reactor Count {\n  state count: int = 0\n  output c: int\n  timer t(0, 1 s)\n  reaction(t) -> c {=\n    count++;\n    c.set(count);\n  =}\n}\n
    \n

    The declaration on the second line gives the variable the name count, declares its type to be int, and initializes its value to 0. The type and initial value can be enclosed in the C++-code delimiters {= ... =} if they are not simple identifiers, but in this case, that is not necessary.

    \n

    In the body of the reaction, the state variable is automatically in scope and can be referenced directly by its name. Since all reactions, state variables, and parameters of a reactor are members of the same class, reactions can also reference state variables (or parameters) using the this pointer: this->name.

    \n

    A state variable may be a time value, declared as follows:

    \n
      state time_value:time = 100 ms;\n
    \n

    The type of the generated time_value variable will be reactor::Duration, which is an alias for std::chrono::nanoseconds.

    \n

    For the C++ target, Lingua Franca provides two alternative styles for initializing state variables. We can write state foo:int(42) or state foo:int{42}. This allows to distinguish between the different initialization styles in C++. foo:int(42) will be translated to int foo(42) and foo:int{42} will be translated to int foo{42} in the generated code. Generally speaking, the {...} style should be preferred in C++, but it is not always applicable. Hence we allow the LF programmer to choose the style. Due to the peculiarities of C++, this is particularly important for more complex data types. For instance, state foo:std::vector<int>(4,2) would be initialized to the list [2,2,2,2] whereas state foo:std::vector<int>{4,2} would be initialized to the list [4,2].

    \n

    State variables can have array values. For example, the [MovingAverage] (https://github.com/lf-lang/lingua-franca/blob/master/test/Cpp/src/MovingAverage.lf) reactor computes the moving average of the last four inputs each time it receives an input:

    \n
    reactor MovingAverageImpl {\n  state delay_line: double[3]{0.0, 0.0, 0.0}\n  state index: int = 0\n  input in: double\n  output out: double\n\n  reaction(in) -> out {=\n    // Calculate the output.\n    double sum = *in.get();\n    for (int i = 0; i < 3; i++) {\n      sum += delay_line[i];\n    }\n    out.set(sum/4.0);\n\n    // Insert the input in the delay line.\n    delay_line[index] = *in.get();\n\n    // Update the index for the next input.\n    index++;\n    if (index >= 3) {\n      index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an fixed-size array of 3 doubles with the initial value of the being filled with zeros (note the curly braces). If the size is given in the type specification, then the code generator will declare the type of the state variable using std::array. In the example above, the type of delay_line is std::array<3, double>. If the size specifier is omitted (e.g. state x:double[]). The code generator will produce a variable-sized array using std::vector.

    \n

    The second line can equivalently be given with an assignment operator:

    \n
      state delay_line: double[3] = {0.0, 0.0, 0.0}
    \n

    State variables with more complex types such as classes or structs can be similarly initialized. See StructAsState.lf.

    \n
    \n
    \n

    Reactor parameters and state variables are referenced in the Python code using\nthe syntax self.<name>, where <name> is the name of the parameter or state\nvariable. The following\nStride\nexample modifies the Count reactor in State\nDeclaration to\ninclude both a parameter and a state variable:

    \n
    reactor Count(stride=1) {\n  state count = 1\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(self.count)\n    self.count += self.stride\n  =}\n}\n
    \n

    This defines a stride parameter with initial value 1 and a count state\nvariable with the same initial value. These are referenced in the reaction with\nthe syntax self.stride and self.count respectively. Note that state\nvariables and parameters do not have types in the Python reactor target. See Parameters\nand State for more examples.

    \n

    The Reactor Class:\nThe code generator synthesizes a class in Python for each reactor class in LF,\nwith a constructor (i.e., def __init__(self, ...):) that creates an instance\nof this class and initializes its parameters and state variables as instance\nvariables.\nThese parameters and state variables can then subsequently be accessed directly\nusing the self reference in the body of reactions.

    \n

    It may be tempting to declare state variables in the $preamble$, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    count = 0\n  =}\n  output y\n  timer t(0, 100 msec)\n  reaction(t) -> y {=\n    y.set(count)\n    count += 1\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance\nof the reactor, those instances will share the same variable count (because\ncount will be a class variable). Hence,\ndon’t do this! Sharing variables across instances of reactors violates a\nbasic principle, which is that reactors communicate only by sending messages to\none another. Sharing variables will make your program nondeterministic. If you\nhave multiple instances of the above FlawedCount reactor, the outputs produced\nby each instance will not be predictable, and in a multithreaded implementation,\nwill also not be repeatable.

    \n

    Array Expressions for State Variables and Parameters

    \n

    Array parameters and state variables are implemented using Python lists and initialized using a parentheized list. In the following example, the\nparameter sequence and the state variable x have an initial value that is a Python list [1, 2, 3]:

    \n
    reactor Foo(param = {= [1, 2, 3] =}) {\n  state x = {= [1, 2, 3] =}\n  ...\n}\n
    \n

    Their elements may be accessed as arrays in the body of a reaction, for example self.x[i], where i is an array index.

    \n

    The parameter may be overridden with a different list at instantiation:

    \n
    main reactor {\n  f = new Foo(param = {= [3, 4, 5, 6]} )\n}\n
    \n

    As with any ordinary Python list or tuple, len() can been used to deduce the\nlength. In the above, len(self.x) and len(self.param) will return the lengths of the two lists.

    \n

    Assigning Arbitrary Initial Expressions to State Variables and Parameters

    \n

    As used for lists above, the code delimiters {= ... =} can allow for assignment of arbitrary Python\nexpressions as initial values for state variables and parameters. The following example, taken from\nStructAsState.lf\ndemonstrates this usage:

    \n
    main reactor StructAsState {\n  preamble {=\n    class hello:\n      def __init__(self, name, value):\n        self.name = name\n        self.value = value\n  =}\n  state s = {= self.hello("Earth", 42) =}\n\n  reaction(startup) {=\n    # will print "State s.name="Earth", value=42."\n    print("State s.name=\\"{:s}\\", value={:d}.".format(self.s.name, self.s.value))\n  =}\n}\n
    \n

    Notice that a class hello is defined in the preamble. The state variable s is then initialized to an instance of hello constructed within the {= ... =} delimiters.

    \n
    \n
    \n

    In the TypeScript target, all TypeScript types are generally acceptable for parameters and state variables. Custom types (and classes) must be defined in the $preamble$ before they may be used.

    \n

    To benefit from type checking, you should declare types for your reactor elements. If a type isn’t declared for a state variable, it is assigned the type unknown.

    \n

    For example, the following reactor will produce the output sequence 0, 1, 2, 3, … :

    \n
    reactor Count {\n  state count:number = 0;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    count++;\n    y = count;\n  =}\n}\n
    \n

    The declaration on the second line gives the variable the name “count”, declares its type to be number, and initializes its value to 0. The type and initial value can be enclosed in the Typescript-code delimiters {= ... =} if they are not simple identifiers, but in this case, that is not necessary.

    \n

    In the body of the reaction, the reactor’s state variable is referenced by way of a local variable of the same name. The local variable will contain the current value of the state at the beginning of the reaction. The final value of the local variable will be used to update the state at the end of the reaction.

    \n

    It may be tempting to declare state variables in the preamble, as follows:

    \n
    reactor FlawedCount {\n  preamble {=\n    let count = 0;\n  =}\n  output y:number;\n  timer t(0, 100 msec);\n  reaction(t) -> y {=\n    count++;\n    y = count;\n  =}\n}\n
    \n

    This will produce a sequence of integers, but if there is more than one instance of the reactor, those instances will share the same variable count. Hence, don’t do this! Sharing variables across instances of reactors violates a basic principle, which is that reactors communicate only by sending messages to one another. Sharing variables will make your program nondeterministic. If you have multiple instances of the above FlawedCount reactor, the outputs produced by each instance will not be predictable, and in an asynchronous implementation, will also not be repeatable.

    \n

    A state variable may be a time value, declared as follows:

    \n
      state time_value:time = 100 ms\n
    \n

    The time_value variable will be of type TimeValue, which is an object used to represent a time in the TypeScript Target. Refer to the section on timed behavior for more information.

    \n

    A state variable can have an array or object value. For example, the following reactor computes the moving average of the last four inputs each time it receives an input (from MovingAverageImpl):

    \n
    reactor MovingAverage {\n  state delay_line: {= Array<number> =} = {= [0.0, 0.0, 0.0] =}\n  state index: number = 0\n  input x: number\n  output out: number\n\n  reaction(x) -> out {=\n    x = x as number;\n    // Calculate the output.\n    let sum = x;\n    for (let i = 0; i < 3; i++) {\n      sum += delay_line[i];\n    }\n    out = sum/4.0;\n\n    // Insert the input in the delay line.\n    delay_line[index] = x;\n\n    // Update the index for the next input.\n    index++;\n    if (index >= 3) {\n      index = 0;\n    }\n  =}\n}\n
    \n

    The second line declares that the type of the state variable is an array of numbers with the initial value of the array being a three-element array filled with zeros.

    \n

    States whose type are objects can similarly be initialized. Declarations can take an object literal as the initial value:

    \n
    state myLiteral:{= {foo: number, bar: string} =} = {= {foo: 42, bar: "baz"} =};\n
    \n

    or use new:

    \n
    state mySet:{=Set<number>=} = {= new Set<number>() =};\n
    \n

    Reactor parameters are also referenced in the TypeScript code as local variables. The example below modifies the Count reactor so that its stride is a parameter:

    \n
    target TypeScript\nreactor Count(stride:number = 1) {\n  state count:number = 0;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    y = count;\n    count += stride;\n  =}\n}\nreactor Display {\n  input x:number;\n  reaction(x) {=\n    console.log("Received: " + x + ".");\n  =}\n}\nmain reactor Stride {\n  c = new Count(stride = 2);\n  d = new Display();\n  c.y -> d.x;\n}\n
    \n

    The second line defines the stride parameter, gives its type, and gives its initial value. As with state variables, the type and initial value can be enclosed in {= ... =} if necessary. The parameter is referenced in the reaction by referring to the local variable stride.

    \n

    When the reactor is instantiated, the default parameter value can be overridden. This is done in the above example near the bottom with the line:

    \n
      c = new Count(stride = 2);\n
    \n

    If there is more than one parameter, use a comma separated list of assignments.

    \n

    Parameters in Lingua Franca are immutable. To encourage correct usage, parameter variables within a reaction are local const variables. If you feel tempted to use a mutable parameter, instead try using the parameter to initialize state and modify the state variable instead. This is illustrated below by a further modification to the Stride example where it takes an initial “start” value for count as a second parameter:

    \n
    target TypeScript\nreactor Count(stride:number = 1, start:number = 5) {\n  state count:number = start;\n  output y:number;\n  timer t(0, 100 ms);\n  reaction(t) -> y {=\n    y = count;\n    count += stride;\n  =}\n}\nreactor Display {\n  input x:number;\n  reaction(x) {=\n    console.log("Received: " + x + ".");\n  =}\n}\nmain reactor Stride {\n  c = new Count(stride = 2, start = 10);\n  d = new Display();\n  c.y -> d.x;\n}\n
    \n

    Array or Object Parameters

    \n

    Parameters can have array or object values. Here is an example that outputs the elements of an array as a sequence of individual messages:

    \n
    reactor Source(sequence:{=Array<number>=} = {= [0, 1, 2] =}) {\n  output out:number;\n  state count:number(0);\n  logical action next;\n  reaction(startup, next) -> out, next {=\n    out = sequence[count];\n    count++;\n    if (count < sequence.length) {\n      actions.next.schedule(0, null);\n    }\n  =}\n}\n
    \n

    Above, the parameter default value is an array with three elements, [0, 1, 2]. The syntax for giving this default value is a TypeScript array literal. Since this is TypeScript syntax, not Lingua Franca syntax, the initial value needs to be surrounded with the target code delimiters, {= ... =}. The default value can be overridden when instantiating the reactor using a similar syntax:

    \n
      s = new Source(sequence = {= [1, 2, 3, 4] =});\n
    \n

    Both default and overridden values for parameters can also be created with the new keyword:

    \n
    reactor Source(sequence:{=Array<number>=} = {= new Array<number>() =}) {\n
    \n

    and

    \n
    s = new Source(sequence = {= new Array<number() =});\n
    \n
    \n
    \n

    Parameters and state variables in Rust are accessed on the self structure, as shown in Parameter Declaration.

    \n
    \n

    Inputs and Outputs

    \n
    \n

    In the body of a reaction in the C target, the value of an input is obtained using the syntax name->value, where name is the name of the input port. See, for example, the Destination reactor in Input and Output Declarations.

    \n

    To set the value of outputs, use lf_set. See, for example, the Double reactor in Input and Output Declarations.)

    \n

    An output may even be set in different reactions of the same reactor at the same tag. In this case, one reaction may wish to test whether the previously invoked reaction has set the output. It can check name->is_present to determine whether the output has been set. For example, the Source reactor in the test case TestForPreviousOutput will always produce the output 42:

    \n
    reactor Source {\n  output out: int\n  reaction(startup) -> out {=\n    // Set a seed for random number generation based on the current time.\n    srand(time(0));\n    // Randomly produce an output or not.\n    if (rand() % 2) {\n      lf_set(out, 21);\n    }\n  =}\n  reaction(startup) -> out {=\n    if (out->is_present) {\n      lf_set(out, 2 * out->value);\n    } else {\n      lf_set(out, 42);\n    }\n  =}\n}\n
    \n

    The first reaction may or may not set the output to 21. The second reaction doubles the output if it has been previously produced and otherwise produces 42.

    \n

    Sending and Receiving Data

    \n

    You can define your own data types in C and send and receive those. Consider the StructAsType example:

    \n
    preamble {=\n  typedef struct hello_t {\n    char* name;\n    int value;\n  } hello_t;\n=}\nreactor StructAsType {\n  output out:hello_t;\n  reaction(startup) -> out {=\n    struct hello_t temp = {"Earth", 42};\n    lf_set(out, temp);\n  =}\n}\n
    \n

    The $preamble$ code defines a struct data type. In the reaction to $startup$, the reactor creates an instance of this struct on the stack (as a local variable named temp) and then copies that struct to the output using the lf_set macro.

    \n

    For large structs, it may be inefficient to create a struct on the stack and copy it to the output, as done above. You can use a pointer type instead. See below for details.

    \n

    A reactor receiving the struct message uses the struct as normal in C:

    \n
    reactor Print() {\n  input in:hello_t;\n  reaction(in) {=\n    printf("Received: name = %s, value = %d\\n", in->value.name, in->value.value);\n  =}\n}\n
    \n

    The preamble should not be repeated in this reactor definition if the two reactors are defined together because this will trigger an error when the compiler thinks that hello_t is being redefined.

    \n

    Persistent Inputs

    \n

    In the C target, inputs are persistent. You can read an input even when there is no event present and the value of that input will be the most recently received value or an instance of the input type filled with zeros. For example:

    \n
    target C\nreactor Source {\n  output out: int\n  timer t(100 ms, 200 ms)\n  state count: int = 1\n  reaction(t) -> out {=\n    lf_set(out, self->count++);\n  =}\n}\nreactor Sink {\n  input in: int\n  timer t(0, 100 ms)\n  reaction(t) in {=\n    printf("Value of the input is %d at time %lld\\n", in->value, lf_time_logical_elapsed());\n  =}\n}\nmain reactor {\n  source = new Source()\n  sink = new Sink()\n  source.out -> sink.in\n}\n
    \n

    The Source reactor produces output 1 at 100ms and 2 at 300ms.\nThe Sink reactor reads every 100ms starting at 0.\nNotice that it uses the input in but is not triggered by it.\nThe result of running this program is:

    \n
    Value of the input is 0 at time 0\nValue of the input is 1 at time 100000000\nValue of the input is 1 at time 200000000\nValue of the input is 2 at time 300000000\nValue of the input is 2 at time 400000000\n...
    \n

    The first output is 0 (an int initialized with zero), and subsequently, each output is read twice.

    \n

    Fixed Length Array Inputs and Outputs

    \n

    When inputs and outputs are fixed-length arrays, the memory to contain the array is automatically provided as part of the reactor instance. You can write directly to it, and then just call lf_set_present to alert the system that the output is present. For example:

    \n
    reactor Source {\n  output out: int[3]\n  reaction(startup) -> out {=\n    out->value[0] = 0;\n    out->value[1] = 1;\n    out->value[2] = 2;\n    lf_set_present(out);\n  =}\n}\n
    \n

    In general, this will work for any data type that can be copied by a simple assignment operator (see below for how to handle more complex data types).

    \n

    Reading the array is equally simple:

    \n
    reactor Print(scale: int(1)) {  // The scale parameter is just for testing.\n  input in: int[3]\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < 3; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value[i]);\n    }\n   printf("]\\n");\n  =}\n}\n
    \n

    Variable Length Array Inputs and Outputs

    \n

    Above, the array size is fixed and must be known throughout the program. A more flexible mechanism leaves the array size unspecified in the types of the inputs and outputs and uses lf_set_array instead of lf_set to inform the system of the array length. For example,

    \n
    reactor Source {\n  output out: int[]\n  reaction(startup) -> out {=\n    // Dynamically allocate an output array of length 3.\n    int* array = (int*)malloc(3 * sizeof(int));\n    // Populate the array.\n    array[0] = 0;\n    array[1] = 1;\n    array[2] = 2;\n    // Set the output, specifying the array length.\n    lf_set_array(out, array, 3);\n  =}\n}\n
    \n

    The array length will be available at the receiving end, which may look like this:

    \n
    reactor Print {\n  input in: int[]\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < in->length; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value[i]);\n    }\n    printf("]\\n");\n  =}\n}\n
    \n

    Dynamically Allocated Data

    \n

    A much more flexible way to communicate complex data types is to set dynamically allocated memory on an output port. This can be done in a way that automatically handles freeing the memory when all users of the data are done with it. The reactor that allocates the memory cannot know when downstream reactors are done with the data, so Lingua Franca provides utilities for managing this using reference counting. You can specify a destructor on a port and pass a pointer to a dynamically allocated object as illustrated in the SetDestructor example.

    \n

    Suppose the data structure of interest, its constructor, destructor, and copy_constructor are defined as follows:

    \n
    preamble {=\n  typedef struct int_array_t {\n    int* data;\n    size_t length;\n  } int_array_t;\n\n  int_array_t* int_array_constructor(size_t length) {\n    int_array_t* result = (int_array_t*) malloc(sizeof(int_array_t));\n    result->data = (int*) calloc(length, sizeof(int));\n    result->length = length;\n    return result;\n  }\n\n  void int_array_destructor(void* array) {\n    free(((int_array_t*) array)->data);\n    free(array);\n  }\n\n  void* int_array_copy_constructor(void* array) {\n    int_array_t* source = (int_array_t*) array;\n    int_array_t* copy = (int_array_t*) malloc(sizeof(int_array_t));\n    copy->data = (int*) calloc(source->length, sizeof(int));\n    copy->length = source->length;\n    for (size_t i = 0; i < source->length; i++) {\n      copy->data[i] = source->data[i];\n    }\n    return (void*) copy;\n  }\n=}\n
    \n

    Then, the sender reactor would use lf_set_destructor to specify how the memory set on an output port should be freed:

    \n
    reactor Source {\n  output out:int_array_t*;\n  reaction(startup) -> out {=\n    lf_set_destructor(out, int_array_destructor);\n    lf_set_copy_constructor(out, int_array_copy_constructor);\n  }\n  reaction(startup) -> out {=\n    int_array_t* array =  int_array_constructor(2);\n    for (size_t i = 0; i < array->length; i++) {\n      array->data[i] = i;\n    }\n    lf_set(out, array);\n  =}\n}\n
    \n

    The first reaction specifies the destructor and copy constructor (the latter of which will be used if any downstream reactor has a mutable input or wishes to make a writable copy).

    \n

    IMPORTANT: The array constructed should be sent to only one output port using lf_set. If you need to send it to more than one output port or to use it as the payload of an action, you should use lf_set_token.

    \n

    FIXME: Show how to do this.

    \n

    A reactor receiving this array is straightforward. It just references the array elements as usual in C, as illustrated by this example:

    \n
    reactor Print() {\n  input in:int_array_t*;\n  reaction(in) {=\n    printf("Received: [");\n    for (int i = 0; i < in->value->length; i++) {\n      if (i > 0) printf(", ");\n      printf("%d", in->value->data[i]);\n    }\n    printf("]\\n");\n  =}\n}\n
    \n

    The deallocation of memory for the data will occur automatically after the last reactor that receives a pointer to the data has finished using it, using the destructor specified by lf_set_destructor or free if none specified.

    \n

    Occasionally, you will want an input or output type to be a pointer, but you don’t want the automatic memory allocation and deallocation. A simple example is a string type, which in C is char*. Consider the following (erroneous) reactor:

    \n
    reactor Erroneous {\n  output out:char*;\n  reaction(startup) -> out {=\n    lf_set(out, "Hello World");\n  =}\n}\n
    \n

    An output data type that ends with * signals to Lingua Franca that the message\nis dynamically allocated and must be freed downstream after all recipients are\ndone with it. But the \"Hello World\" string here is statically allocated, so an\nerror will occur when the last downstream reactor to use this message attempts\nto free the allocated memory. To avoid this for strings, you can use a special\nstring type as follows:

    \n
    reactor Fixed {\n  output out:string;\n  reaction(startup) -> out {=\n    lf_set(out, "Hello World");\n  =}\n}\n
    \n

    The string type is equivalent to char*, but since it doesn’t end with *, it does not signal to Lingua Franca that the type is dynamically allocated. Lingua Franca only handles allocation and deallocation for types that are specified literally with a final * in the type name. The same trick can be used for any type where you don’t want automatic allocation and deallocation. E.g., the SendsPointer example looks like this:

    \n
    reactor SendsPointer  {\n  preamble {=\n    typedef int* int_pointer;\n  =}\n  output out:int_pointer\n  reaction(startup) -> out {=\n    static int my_constant = 42;\n    lf_set(out, &my_constant;)\n  =}\n}\n
    \n

    The above technique can be used to abuse the reactor model of computation by communicating pointers to shared variables. This is generally a bad idea unless those shared variables are immutable. The result will likely be nondeterministic. Also, communicating pointers across machines that do not share memory will not work at all.

    \n

    Mutable Inputs

    \n

    Although it cannot be enforced in C, a receiving reactor should not modify the values provided by an input. Inputs are logically immutable because there may be several recipients. Any recipient that wishes to modify the input should make a copy of it. Fortunately, a utility is provided for this pattern. Consider the ArrayScale example, here modified to use the above int_array_t data type:

    \n
    reactor ArrayScale(scale:int(2)) {\n  mutable input in:int_array_t*;\n  output out:int_array_t*;\n  reaction(in) -> out {=\n    for(int i = 0; i < in->length; i++) {\n      in->value[i] *= self->scale;\n    }\n    lf_set_token(out, in->token);\n  =}\n}\n
    \n

    Here, the input is declared $mutable$, which means that any reaction is free to\nmodify the input. If this reactor is the only recipient of the array or the last\nrecipient of the array, then this will not make a copy of the array but rather use\nthe original array. Otherwise, it will use a copy. By default, memcpy is used to copy the data. However, the sender can also specify\na copy constructor to be used by calling lf_set_copy_constructor on the\noutput port, as explained below.

    \n

    Important: Notice that the above ArrayScale reactor modifies the array and then forwards it to its output port using the lf_set_token() macro. That macro further delegates to downstream reactors the responsibility for freeing dynamically allocated memory once all readers have completed their work. It will not work to just use lf_set, passing it the value.\nThis will result in a memory error, yielding a message like the following:

    \n
        malloc: *** error for object 0x600002674070: pointer being freed was not allocated
    \n

    If the above code were not to forward the array, then the dynamically allocated memory will be automatically freed when this reactor is done with it.

    \n

    Three of the above reactors can be combined into a pipeline as follows:

    \n
    main reactor ArrayScaleTest {\n  s = new Source();\n  c = new ArrayScale();\n  p = new Print();\n  s.out -> c.in;\n  c.out -> p.in;\n}\n
    \n

    In this composite, the array is allocated by ArrayPrint, modified by ArrayScale, and deallocated (freed) after Print has reacted. No copy is necessary because ArrayScale is the only recipient of the original array.

    \n

    Inputs and outputs can also be dynamically allocated structs. In fact, Lingua Franca’s C target will treat any input or output data type that ends with [] or * specially by providing utilities for allocating memory and modifying and forwarding. Deallocation of the allocated memory is automatic. The complete set of utilities is given below.

    \n

    String Types

    \n

    String types in C are char*. But, as explained above, types ending with * are interpreted specially to provide automatic memory management, which we generally don’t want with strings (a string that is a compile-time constant must not be freed). You could enclose the type as {= char* =}, but to avoid this awkwardness, the header files include a typedef that permits using string instead of char*. For example (from DelayString.lf):

    \n
    reactor DelayString(delay:time = 100 ms)) {\n  input in:string;\n  output out:string;\n  logical action a:string;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // The following copies the char*, not the string.\n    lf_schedule_copy(a, self->delay, &(in->value), 1);\n  =}\n}\n
    \n

    Macros For Setting Output Values

    \n

    In all of the following, <out> is the name of the output and <value> is the value to be sent.

    \n
    \n

    lf_set(<out>, <value>);

    \n
    \n

    Set the specified output (or input of a contained reactor) to the specified\nvalue using shallow copy. lf_set can be used with all supported data types\n(including type declarations that end with * or []).

    \n
    \n

    lf_set_token(<out>, <token>);

    \n
    \n

    This version is used to directly set the underlying reference-counted token in\noutputs with a type declaration ending with * (any pointer) or [] (any\narray). The <value> argument should be a struct of type token_t. It should\nbe rarely necessary to have the need to create your own (dynamically allocated)\ninstance of token_t.

    \n

    Consider the\nSetToken.lf\nexample:

    \n
    reactor Source {\n  output out:int*\n  logical action a:int\n  reaction(startup) -> a {=\n    lf_schedule_int(a, MSEC(200), 42);\n  =}\n  reaction(a) -> out {=\n    lf_set_token(out, a->token);\n  =}\n}\n
    \n

    Here, the first reaction schedules an integer-valued action to trigger after 200 milliseconds. As explained below, action payloads are carried by tokens. The second reaction grabs the token rather than the value using the syntax a->token (the name of the action followed by ->token). It then forwards the token to the output. The output data type is int* not int because the token carries a pointer to dynamically allocated memory that contains the value. All inputs and outputs with types ending in * or [] are carried by tokens.

    \n
    \n

    lf_set_destructor(<out>, <destructor>);

    \n
    \n

    Specify the destructor destructor used to deallocate any dynamic data set on the output port out.

    \n
    \n

    lf_set_copy_constructor(<out>, <copy_constructor>);

    \n
    \n

    Specify the copy_constructor used to copy construct any dynamic data set on the output port out if the receiving port is $mutable$.

    \n

    lf_set (and lf_set_token) will overwrite any output value previously set at the same logical time and will cause the final output value to be sent to all reactors connected to the output. They also set a local <out>->is_present variable to true. This can be used to subsequently test whether the output value has been set.

    \n
    \n
    \n

    In the body of a reaction in the C++ target, the value of an input is obtained using the syntax *name.get(), where name is the name of the input port. Similarly, outputs are set using a set() method on an output port. For examples, see Inputs and Outputs.

    \n

    Note that get() always returns a pointer to the actual value. Thus the pointer needs to be dereferenced with * to obtain the value. (See Sending and Receiving Large Data Types for an explanation of the exact mechanisms behind this pointer access).\nTo determine whether an input is present, name.is_present() can be used. Since get() returns a nullptr if no value is present, name.get() != nullptr can be used alternatively for checking presence.

    \n

    Sending and Receiving Large Data Types

    \n

    You can define your own data types in C++ or use types defined in a library and send and receive those. Consider the StructAsType example:

    \n
    public preamble {=\n  struct Hello {\n    std::string name;\n    int value;\n  };\n=}\nreactor StructAsType {\n  output out: Hello;\n  reaction(startup) -> out {=\n    Hello hello{"Earth, 42};\n    out.set(hello);\n  =}\n}\n
    \n

    The $public$ $preamble$ code defines a struct data type. In the reaction to $startup$, the reactor creates an instance of this struct on the stack (as a local variable named hello) and then copies that instance to the output using the set() method. For this reason, the C++ reactor runtime provides more sophisticated ways to allocate objects and send them via ports.

    \n

    The C++ library defines two types of smart pointers that the runtime uses internally to implement the exchange of data between ports. These are reactor::MutableValuePtr<T> and reactor::ImmutableValuePtr<T>. reactor::MutableValuePtr<T> is a wrapper around std::unique_ptr and provides read and write access to the value hold, while ensuring that the value has a unique owner. In contrast, reactor::ImmutableValuePtr<T> is a wrapper around std::shared_pointer and provides read only (const) access to the value it holds. This allows data to be shared between reactions of various reactors, while guarantee data consistency. Similar to std::make_unique and std::make_shared, the reactor library provides convenient function for creating mutable and immutable values pointers: reactor::make_mutable_value<T>(...) and reactor::make_immutable_value<T>(...).

    \n

    In fact this code from the example above:

    \n
        Hello hello{"Earth, 42"};\n    out.set(hello);\n
    \n

    implicitly invokes reactor::make_immutable_value<Hello>(hello) and could be rewritten as

    \n
        Hello hello{"Earth, 42"};\n    out.set(reactor::make_immutable_value<Hello>(hello));\n
    \n

    This will invoke the copy constructor of Hello, copying its content from the hello instance to the newly created reactor::ImmutableValuePtr<Hello>.

    \n

    Since copying large objects is inefficient, the move semantics of C++ can be used to move the ownership of object instead of copying it. This can be done in the following two ways. First, by directly creating a mutable or immutable value pointer, where a mutable pointer allows modification of the object after it has been created:

    \n
        auto hello = reactor::make_mutable_value<Hello>("Earth", 42);\n    hello->name = "Mars";\n    out.set(std::move(hello));\n
    \n

    An example of this can be found in StructPrint.lf. Not that after the call to std::move, hello is nullptr and the reaction cannot modify the object anymore. Alternatively, if no modification is requires, the object can be instantiated directly in the call to set() as follows:

    \n
        out.set({"Earth", 42});\n
    \n

    An example of this can be found in StructAsTypeDirect.

    \n

    Getting a value from an input port of type T via get() always returns an reactor::ImmutableValuePtr<T>. This ensures that the value cannot be modified by multiple reactors receiving the same value, as this could lead to an inconsistent state and nondeterminism in a multi-threaded execution. An immutable value pointer can be converted to a mutable pointer by calling get_mutable_copy. For instance, the ArrayScale reactor modifies elements of the array it receives before sending it to the next reactor:

    \n
    reactor Scale(scale:int = 2) {\n  input in:int[3];\n  output out:int[3];\n\n  reaction(in) -> out {=\n    auto array = in.get().get_mutable_copy();\n    for(int i = 0; i < array->size(); i++) {\n      (*array)[i] = (*array)[i] * scale;\n    }\n    out.set(std::move(array));\n  =}\n}\n
    \n

    Currently get_mutable_copy() always copies the contained value to safely create a mutable pointer. However, a future implementation could optimize this by checking if any other reaction is accessing the same value. If not, the value can simply be moved from the immutable pointer to a mutable one.

    \n
    \n
    \n

    In the body of a reaction in the Python target, the value of an input is\nobtained using the syntax name.value, where name is the name of the input\nport. To determine whether an input is present, use name.is_present. To\nproduce an output, use the syntax name.set(value). The value can be any\nvalid Python object. For simple examples, see Inputs and\nOutputs.

    \n

    Sending and Receiving Objects

    \n

    You can define your own data types in Python and send and receive those. Consider the StructAsType example:

    \n
    target Python {\n  files: include/hello.py\n}\npreamble {=\n  import hello\n=}\nreactor Source {\n  output out;\n  reaction(startup) -> out {=\n    temp = hello.hello("Earth", 42)\n    out.set(temp)\n  =}\n}\n
    \n

    The top-level preamble has imported the hello module, which contains the following class:

    \n
    class hello:\n    def __init__(self, name = "", value = 0):\n        self.name = name\n        self.value = value\n
    \n

    In the reaction to startup, the reactor has created an instance object of this class (as local variable named temp) and passed it downstream using the set method on output port out.

    \n

    The set method is defined as follows:

    \n
    \n

    <port>.set(<value>): Set the specified output port (or input of a contained\nreactor) to the specified value. This value can be any Python object\n(including None and objects of type Any). The value is\ncopied and therefore the variable carrying the value can be subsequently\nmodified without changing the output.

    \n
    \n

    A reactor receiving the class object message can subsequently access the object\nusing <port>.value:

    \n
    reactor Print(expected(42)) {\n  input _in;\n  reaction(_in) {=\n    print("Received: name = {:s}, value = {:d}\\n".format(_in.value.name,\n                                                         _in.value.value))\n  =}\n}\n
    \n

    Note: The hello module has been imported using a top-level preamble, therefore, the contents of the module are available to all reactors defined in the current Lingua Franca file (a similar situation arises if the hello class itself was in the top-level preamble).

    \n
    \n
    \n

    In the TypeScript target, all TypeScript types are generally acceptable for inputs and outputs with one notable exception:

    \n
      \n
    • undefined is not a valid type for an input, output, or action. This is because undefined is used to designate the absence of an input, output, or action during a reaction.
    • \n
    \n

    As with parameters and state variables, custom types (and classes) must be defined in the preamble before they may be used.

    \n

    To benefit from type checking, you should declare types for your reactor elements. If a type isn’t declared for an input, output, or action, it is assigned the reactor-ts type Present which is defined as

    \n
    export type Present = (number | string | boolean | symbol | object | null);\n
    \n

    In the body of a reaction in the TypeScript target, inputs are simply referred to by name. An input of type t is available within the body of a reaction as a local variable of type t | undefined. To determine whether an input is present, test the value of the input against undefined. An undefined input is not present.

    \n

    WARNING Be sure to use the === or !== operator and not == or != to test against undefined. In JavaScript/TypeScript the comparison undefined == null yields the value true. It may also be tempting to rely upon the falsy evaluation of undefined within an if statement, but this may introduce bugs. For example a reaction that tests the presence of input x with if (x) { ... } will not correctly identify potentially valid present values such as 0, false, or \"\".

    \n

    For example, the Determinism.lf test case includes the following reactor:

    \n
    reactor Destination {\n  input x: number\n  input y: number\n  reaction(x, y) {=\n    let sum = 0;\n    if (x !== undefined) {\n      sum += x;\n    }\n    if (y !== undefined) {\n      sum += y;\n    }\n    console.log("Received " + sum);\n    if (sum != 2) {\n      util.requestErrorStop("FAILURE: Expected 2.")\n    }\n  =}\n}\n
    \n

    The reaction refers to the inputs x and y by name and tests for their presence by testing x and y against undefined. If a reaction is triggered by just one input, then normally it is not necessary to test for its presence. It will always be present. However TypeScript’s type system is not smart enough to know such an input will never have type undefined if there’s no test against undefined within the reaction. An explicit type annotation (for example x = x as t; where t is the type of the input) may be necessary to avoid type errors from the compiler. In the above example, there are two triggers, so the reaction has no assurance that both will be present.

    \n

    Inputs declared in the uses part of the reaction do not trigger the reaction. Consider this modification of the above reaction:

    \n
    reaction(x) y {=\n  let sum = x as number;\n  if (y !== undefined) {\n    sum += y;\n  }\n  console.log("Received " + sum + ".");\n=}\n
    \n

    It is no longer necessary to test for the presence of x because that is the only trigger. The input y, however, may or may not be present at the logical time that this reaction is triggered. Hence, the code must test for its presence.

    \n

    The effects portion of the reaction specification can include outputs and actions. Actions will be described below. Like inputs, an output of type t is available within the body of a reaction as a local variable of type t | undefined. The local variable for each output is initialized to the output’s current value. Outputs are set by assigning a (non-undefined) value to its local variable (no changes will be made to an output if it has the value undefined at the end of a reaction). Whatever value an output’s local variable has at the end of the reaction will be set to that output. If an output’s local variable has the value undefined at the end of the reaction, that output will not be set and connected downstream inputs will be absent. For example, we can further modify the above example as follows:

    \n
    output z:number;\nreaction(x) y -> z {=\n  let sum = x as number;\n  if (y !== undefined) {\n    sum += y;\n  }\n  z = sum;\n=}\n
    \n

    If an output gets set more than once at any logical time, downstream reactors will see only the final value that is set. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

    \n

    An output may even be set in different reactions of the same reactor at the same logical time. In this case, one reaction may wish to test whether the previously invoked reaction has set the output. It can do that using a !== undefined test for that output. For example, the following reactor will always produce the output 42:

    \n
    reactor TestForPreviousOutput {\n  output out:number;\n  reaction(startup) -> out {=\n    if (Math.random() > 0.5) {\n      out = 21;\n    }\n  =}\n  reaction(startup) -> out {=\n    let previous_output = out;\n    if (previous_output) {\n      out = 2 * previous_output;\n    } else {\n      out = 42;\n    }\n  =}\n}\n
    \n

    The first reaction may or may not set the output to 21. The second reaction doubles the output if it has been previously produced and otherwise produces 42.

    \n

    Sending and Receiving Custom Types

    \n

    You can define your own data types in TypeScript and send and receive those. Consider the following example:

    \n
    reactor CustomType {\n  preamble {=\n    type custom = string | null;\n  =}\n  output out:custom;\n  reaction(startup) -> out {=\n    out = null;\n  =}\n}\n
    \n

    The $preamble$ code defines a custom union type of string and null.

    \n
    \n
    \n

    Inputs and outputs in the Rust target are accessed using the set and get methods of the ctx objects, as shown in Inputs and Outputs.

    \n
    \n

    Time

    \n
    \n

    In the C target, the value of a time instant or interval is an integer specifying a number of nanoseconds. An instant is the number of nanoseconds that have elapsed since January 1, 1970. An interval is the difference between two instants. When an LF program starts executing, logical time is (normally) set to the instant provided by the operating system. (On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    Time in the C target is a int64_t, which is a 64-bit signed number. Since a 64-bit number has a limited range, this measure of time instants will overflow in approximately the year 2262. For better code clarity, two types are defined in tag.h, instant_t and interval_t, which you can use for time instants and intervals respectively. These are both equivalent to int64_t, but using those types will insulate your code against changes and platform-specific customizations.

    \n

    Lingua Franca uses a superdense model of time. A reaction is invoked at a logical tag, a struct consisting of a time value (an instant_t, which is a int64_t) and a microstep value (a microstep_t, which is an uint32_t). The tag is guaranteed to not increase during the execution of a reaction. Outputs produced by a reaction have the same tag as the inputs, actions, or timers that trigger the reaction, and hence are logically simultaneous.

    \n

    The time structs and functions for working with time are defined in tag.h. The most useful functions are:

    \n
      \n
    • tag_t lf_tag(): Get the current tag at which this reaction has been invoked.
    • \n
    • int lf_tag_compare(tag_t, tag_t): Compare two tags, returning -1, 0, or 1 for less than, equal, and greater than.
    • \n
    • instant_t lf_time_logical(): Get the current logical time (the first part of the current tag).
    • \n
    • interval_t lf_time_logical_elapsed(): Get the logical time elapsed since program start.
    • \n
    \n

    There are also some useful functions for accessing physical time:

    \n
      \n
    • instant_t lf_time_physical(): Get the current physical time.
    • \n
    • instant_t lf_time_physical_elapsed(): Get the physical time elapsed since program start.
    • \n
    • instant_t lf_time_start(): Get the starting physical and logical time.
    • \n
    \n

    The last of these is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime example:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t logical = lf_time_logical();\n    printf("Logical time is %ld.\\n", logical);\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    Start execution at time Sun Oct 13 10:18:36 2019\nplus 353609000 nanoseconds.\nLogical time is 1570987116353609000.\nLogical time is 1570987117353609000.\nLogical time is 1570987118353609000.\n...
    \n

    The first two lines give the current time-of-day provided by the execution platform at the start of execution. This is used to initialize logical time. Subsequent values of logical time are printed out in their raw form, rather than the friendlier form in the first two lines. If you look closely, you will see that each number is one second larger than the previous number, where one second is 1000000000 nanoseconds.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    interval_t elapsed = lf_time_logical_elapsed();\n    printf("Elapsed logical time is %ld.\\n", elapsed);\n  =}\n}\n
    \n

    This will produce:

    \n
    Start execution at time Sun Oct 13 10:25:22 2019\nplus 833273000 nanoseconds.\nElapsed logical time is 0.\nElapsed logical time is 1000000000.\nElapsed logical time is 2000000000.\n...
    \n

    You can also get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t physical = lf_time_physical();\n    printf("Physical time is %ld.\\n", physical);\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Start execution at time Sun Oct 13 10:35:59 2019\nplus 984992000 nanoseconds.\nPhysical time is 1570988159986108000.\nPhysical time is 1570988160990219000.\nPhysical time is 1570988161990067000.\n...
    \n

    Finally, you can get elapsed physical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    instant_t elapsed_physical = lf_time_physical_elapsed();\n    printf("Elapsed physical time is %ld.\\n", elapsed_physical);\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Elapsed physical time is 657000.\nElapsed physical time is 1001856000.\nElapsed physical time is 2004761000.\n...
    \n

    Notice that these numbers are increasing by roughly one second each time. If you set the fast target parameter to true, then logical time will elapse much faster than physical time.

    \n

    Working with nanoseconds in C code can be tedious if you are interested in longer durations. For convenience, a set of macros are available to the C programmer to convert time units into the required nanoseconds. For example, you can specify 200 msec in C code as MSEC(200) or two weeks as WEEKS(2). The provided macros are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these. Examples are given in the next section.

    \n
    \n
    \n

    Timers are specified exactly as in the Time and Timers. When working with time in the C++ code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    The reactor-cpp library uses std::chrono for representing time. Specifically, the library defines two types for representing durations and timepoints: reactor::Duration and reactor::TimePoint. reactor::Duration is an alias for std::chrono::nanosecods. reactor::TimePoint is alias for std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>. As you can see from these definitions, the smallest time step that can be represented is one nanosecond. Note that reactor::TimePoint describes a specific point in time and is associated with a specific clock, whereas reactor::Duration defines a time interval between two time points.

    \n

    Lingua Franca uses a superdense model of logical time. A reaction is invoked at a logical tag. In the C++ library, a tag is represented by the class reactor::Tag. In essence, this class is a tuple of a reactor::TimePoint representing a specific point in logical time and a microstep value (of type reactor::mstep_t, which is an alias for unsigned long). reactor::Tag provides two methods for getting the time point or the microstep:

    \n
    const TimePoint& time_point() const;\nconst mstep_t& micro_step() const;\n
    \n

    The C++ code in reaction bodies has access to library functions that allow to retrieve the current logical or physical time:

    \n
      \n
    • TimePoint get_physical_time(): Get the current physical time.
    • \n
    • TimePoint get_logcial_time(): Get the current logical time.
    • \n
    • Duration get_elapsed_physical_time(): Get the physical time elapsed since program start.
    • \n
    • Duration get_elapsed_logical_time(): Get the logical time elapsed since program start.
    • \n
    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime example:

    \n
    main reactor {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    auto logical = get_logical_time();\n    std::cout << "Logical time is " << logical << std::endl;\n  =}\n}\n
    \n

    Note that the << operator is overloaded for both reactor::TimePoint and reactor::Duration and will print the time information accordingly.

    \n

    When executing the above program, you will see something like this:

    \n
    [INFO]  Starting the execution\nLogical time is 2021-05-19 14:06:09.496828396\nLogical time is 2021-05-19 14:06:10.496828396\nLogical time is 2021-05-19 14:06:11.496828396\nLogical time is 2021-05-19 14:06:11.496828396\n...
    \n

    If you look closely, you will see that each printed logical time is one second larger than the previous one.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    auto elapsed = get_elapsed_logical_time();\n    std::cout << "Elapsed logical time is " << elapsed << std::endl;\n    std::cout << "In seconds: " <<  std::chrono::duration_cast<std::chrono::seconds>(elapsed) << std::endl;\n  =}\n}\n
    \n

    Using std::chrono it is also possible to convert between time units and directly print the number of elapsed seconds as seen above. The resulting output of this program will be:

    \n
    [INFO]  Starting the execution\nElapsed logical time is 0 nsecs\nIn seconds: 0 secs\nElapsed logical time is 1000000000 nsecs\nIn seconds: 1 secs\nElapsed logical time is 2000000000 nsecs\nIn seconds: 2 secs\n...
    \n

    You can also get physical and elapsed physical time:

    \n
    main reactor {\n  timer t(0, 1 sec);\n\treaction(t) {=\n    auto logical = get_logical_time();\n    auto physical = get_physical_time();\n    auto elapsed = get_elapsed_physical_time();\n    std::cout << "Physical time is " << physical << std::endl;\n    std::cout << "Elapsed physical time is " << elapsed << std::endl;\n    std::cout << "Time lag is " << physical - logical << std::endl;\n  =}\n}\n
    \n

    Notice that the physical times are increasing by roughly one second in each reaction. The output also shows the lag between physical and logical time. If you set the fast target parameter to true, then physical time will elapse much faster than logical time. The above program will produce something like this:

    \n
    [INFO]  Starting the execution\nPhysical time is 2021-05-19 14:25:18.070523014\nElapsed physical time is 2601601 nsecs\nTime lag is 2598229 nsecs\nPhysical time is 2021-05-19 14:25:19.068038275\nElapsed physical time is 1000113888 nsecs\nTime lag is 113490 nsecs\n[INFO]  Physical time is Terminating the execution\n2021-05-19 14:25:20.068153026\nElapsed physical time is 2000228689 nsecs\nTime lag is 228241 nsecs
    \n

    For specifying time durations in code chrono provides convenient literal operators in std::chrono_literals. This namespace is automatically included for all reaction bodies. Thus, we can simply write:

    \n
    std::cout << 42us << std::endl;\nstd::cout << 1ms << std::endl;\nstd::cout << 3s << std::endl;\n
    \n

    which prints:

    \n
    42 usecs\n1 msecs\n3 secs
    \n
    \n
    \n

    Timers are specified exactly as in the Time and Timers. When working with time in the Python code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    In the Python target, similar to the C target, the value of a time instant or\ninterval is an integer specifying a number of nanoseconds. An instant is the\nnumber of nanoseconds that have elapsed since January 1, 1970. An interval is\nthe difference between two instants.

    \n

    The functions for working with time and tags are:

    \n
      \n
    • lf.tag() -> Tag: Returns a Tag instance of the current tag at which this reaction has been invoked.
    • \n
    • lf.tag_compare(Tag, Tag) -> int: Compare two Tag instances, returning -1, 0, or 1 for less than, equal, and greater than. Tags can also be compared using rich comparators (ex. <, >, ==), which returns True or False.
    • \n
    • lf.time.logical() -> int: Get the current logical time (the first part of the current tag).
    • \n
    • lf.time.logical_elapsed() -> int: Get the logical time elapsed since program start.
    • \n
    \n

    Tags can be initialized using Tag(time=some_number, microstep=some_other_number).

    \n

    There are also some useful functions for accessing physical time:

    \n
      \n
    • lf.time.physical() -> int: Get the current physical time.
    • \n
    • lf.time.physical_elapsed() -> int: Get the physical time elapsed since program start.
    • \n
    • lf.time.start() -> int: Get the starting physical and logical time.
    • \n
    \n

    The last of these is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the GetTime.lf example:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    logical = lf.time.logical()\n    print("Logical time is ", logical)\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nLogical time is  1604587862864237900\nLogical time is  1604587863864237900\nLogical time is  1604587864864237900\n...
    \n

    The first two lines give the current time-of-day provided by the execution platform at the start of execution. This is used to initialize logical time. Subsequent values of logical time are printed out in their raw form, rather than the friendlier form in the first two lines. If you look closely, you will see that each number is one second larger than the previous number, where one second is 1000000000 nanoseconds.

    \n

    You can also obtain the elapsed logical time since the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    elapsed = lf.time.logical_elapsed()\n    print("Elapsed logical time is ", elapsed)\n  =}\n}\n
    \n

    This will produce:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nElapsed logical time is  0\nElapsed logical time is  1000000000\nElapsed logical time is  2000000000\n...
    \n

    You can also get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    physical = lf.time.physical()\n    print("Physical time is ", physical)\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nPhysical time is  1604587862864343500\nPhysical time is  1604587863864401900\nPhysical time is  1604587864864395200\n...
    \n

    Finally, you can get elapsed physical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    elapsed_physical = lf.time.physical_elapsed()\n    print("Elapsed physical time is ", elapsed_physical)\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    ---- Start execution at time Thu Nov  5 08:51:02 2020\n---- plus 864237900 nanoseconds.\nElapsed physical time is  110200\nElapsed physical time is  1000185400\nElapsed physical time is  2000178600\n...
    \n

    Notice that these numbers are increasing by roughly one second each time. If you set the fast target parameter to true, then logical time will elapse much faster than physical time.

    \n

    Working with nanoseconds in the Python code can be tedious if you are interested in longer durations. For convenience, a set of functions are available to the Python programmer to convert time units into the required nanoseconds. For example, you can specify 200 msec in Python code as MSEC(200) or two weeks as WEEKS(2). The provided functions are NSEC, USEC (for microseconds), MSEC, SEC, MINUTE, HOUR, DAY, and WEEK. You may also use the plural of any of these. Examples are given in the next section.

    \n
    \n
    \n

    See Summary of Time Functions and Utility Function Reference for a quick API reference.

    \n

    Timers are specified exactly as in the Time and Timers section. When working with time in the TypeScript code body of a reaction, however, you will need to know a bit about its internal representation.

    \n

    A TimeValue is an class defined in the TypeScript target library file time.ts to represent a time instant or interval. For your convenience TimeValue and other classes from the time.ts library mentioned in these instructions are automatically imported into scope of your reactions. An instant is the number of nanoseconds that have elapsed since January 1, 1970. An interval is the difference between two instants. When an LF program starts executing, logical time is (normally) set to the instant provided by the operating system. (On some embedded platforms without real-time clocks, it will be set instead to zero.)

    \n

    Internally a TimeValue uses two numbers to represent the time. To prevent overflow (which would occur for time intervals spanning more than 0.29 years if a single JavaScript number, which has 2^53 bits of precision, were to be used), we use two numbers to store a time value. The first number denotes the number of whole seconds in the interval or instant; the second number denotes the remaining number of nanoseconds in the interval or instant. The first number represents the number of seconds, the second number represents the number of nanoseconds. These fields are not accessible to the programmer, instead TimeValues may be manipulated by an API with functions for addition, subtraction, and comparison.

    \n

    A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider:

    \n
    target TypeScript;\nmain reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let logical:TimeValue = util.getCurrentLogicalTime()\n    console.log("Logical time is " + logical + ".");\n  =}\n}\n
    \n

    When executed, you will get something like this:

    \n
    Logical time is (1584666585 secs; 805146880 nsecs).\nLogical time is (1584666586 secs; 805146880 nsecs).\nLogical time is (1584666587 secs; 805146880 nsecs).\n...
    \n

    Subsequent values of logical time are printed out in their raw form, of seconds and nanoseconds. If you look closely, you will see that each number is one second larger than the previous number.

    \n

    You can also obtain the elapsed logical time since the start of execution, rather than exact logical time:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let logical:TimeValue = util.getElapsedLogicalTime()\n    console.log("Logical time is " + logical + ".");\n  =}\n}\n
    \n

    This will produce:

    \n
    Logical time is (0 secs; 0 nsecs).\nLogical time is (1 secs; 0 nsecs).\nLogical time is (2 secs; 0 nsecs).\n...
    \n

    You can get physical time, which comes from your platform’s real-time clock:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let physical:TimeValue = util.getCurrentPhysicalTime()\n    console.log("Physical time is " + physical + ".");\n  =}\n}\n
    \n

    This will produce something like this:

    \n
    Physical time is (1584666801 secs; 644171008 nsecs).\nPhysical time is (1584666802 secs; 642269952 nsecs).\nPhysical time is (1584666803 secs; 642278912 nsecs).\n...
    \n

    Notice that these numbers are increasing by roughly one second each time.

    \n

    You can also get elapsed physical time from the start of execution:

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let physical:TimeValue = util.getElapsedPhysicalTime()\n    console.log("Physical time is " + physical + ".");\n  =}\n}\n
    \n

    This will produce something like:

    \n
    Physical time is (0 secs; 2260992 nsecs).\nPhysical time is (1 secs; 166912 nsecs).\nPhysical time is (2 secs; 136960 nsecs).\n...
    \n

    You can create a TimeValue yourself with the UnitBasedTimeValue class. UnitBasedTimeValue is a subclass of TimeValue and can be used wherever you could also use a TimeValue directly obtained from one of the util functions. A UnitBasedTimeValue is constructed with a whole number and a TimeUnit. A TimeUnit is an enum from the time.ts library with convenient labels for common time units. These are nsec, usec, msec, sec (or secs), minute (or minutes), hour (or hours), day (or days), and week (or weeks).

    \n

    This reactor has an example of a UnitBasedTimeValue.

    \n
    main reactor GetTime {\n  timer t(0, 1 sec);\n  reaction(t) {=\n    let myTimeValue:TimeValue = new UnitBasedTimeValue(200, TimeUnit.msec);\n    let logical:TimeValue = util.getCurrentLogicalTime()\n    console.log("My custom time value is " + myTimeValue + ".");\n  =}\n
    \n

    This will produce:

    \n
    My custom time value is 200 msec.\nMy custom time value is 200 msec.\nMy custom time value is 200 msec.\n...
    \n

    Tags

    \n

    The TypeScript target provides a utility to get the current Tag of a reaction. Recall that time in Lingua Franca is superdense and each TimeValue is paired with an integer “microstep” index to track the number of iterations at a particular TimeValue. A Tag is this combination of a TimeValue and a “microstep”. The time.ts library provides functions for adding, subtracting, and comparing Tags.

    \n

    You can get the current Tag in your reactions. This example illustrates tags with a Zero-Delay Action:

    \n
    target TypeScript;\nmain reactor GetTime {\n  timer t(0, 1 sec);\n  logical action a;\n  reaction(t) -> a {=\n    let superdense:Tag = util.getCurrentTag();\n    console.log("First iteration - the tag is: " + superdense + ".");\n    actions.a.schedule(0, null);\n  =}\n  reaction(a) {=\n    let superdense:Tag = util.getCurrentTag();\n    let timePart:TimeValue = superdense.time;\n    let microstepPart:number = superdense.microstep;\n    console.log("Second iteration - the time part of the tag is:  " + timePart + ".");\n    console.log("Second iteration - the microstep part of the tag is:  " + microstepPart + ".");\n  =}\n}\n
    \n

    This will produce:

    \n
    First iteration - the tag is: ((1584669987 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669987 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\nFirst iteration - the tag is: ((1584669988 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669988 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\nFirst iteration - the tag is: ((1584669989 secs; 740464896 nsecs), 0).\nSecond iteration - the time part of the tag is:  (1584669989 secs; 740464896 nsecs).\nSecond iteration - the microstep part of the tag is:  1.\n...
    \n

    The first reaction prints the “First iteration” part of the output at microstep 0. The second reaction occurs one microstep later (explained in Scheduling Delayed Reactions) and illustrates how to split a Tag into its constituent TimeValue and microstep.

    \n

    Summary of Time Functions

    \n

    See Time. These time functions are defined in the time.ts library of reactor-ts.

    \n

    UnitBasedTimeValue(value: number, unit:TimeUnit) Constructor for UnitBasedTimeValue, a programmer-friendly subclass of TimeValue. Use a number and a TimeUnit enum.

    \n
    enum TimeUnit {\n  nsec,\n  usec,\n  msec,\n  sec,\n  secs,\n  minute,\n  minutes,\n  hour,\n  hours,\n  day,\n  days,\n  week,\n  weeks,\n}\n
    \n

    TimeValue.add(other: TimeValue): TimeValue Adds this to other.

    \n

    TimeValue.subtract(other: TimeValue): TimeValue Subtracts other from this. A negative result is an error.

    \n

    TimeValue.difference(other: TimeValue): TimeValue Obtain absolute value of other minus this.

    \n

    TimeValue.isEqualTo(other: TimeValue): boolean Returns true if this and other represents the same TimeValue. Otherwise false.

    \n

    TimeValue.isZero(): boolean Returns true if this represents a 0 TimeValue.

    \n

    TimeValue.isEarlierThan(other: TimeValue): boolean Returns true if this < other. Otherwise false.

    \n

    Tag.isSmallerThan(other: Tag): boolean Returns true if this < other. Otherwise false.

    \n

    Tag.isSimultaneousWith(other: Tag): boolean Returns true if this and other represents the same Tag. Otherwise false.

    \n

    Tag.getLaterTag(delay: TimeValue): Tag Returns a tag with the time part of this TimeValue incremented by delay.

    \n

    Tag.getMicroStepLater(): Tag Returns a tag with the microstep part of this TimeValue incremented by 1.

    \n

    getTimeDifference(other: Tag): TimeValue Returns a TimeValue that represents the absolute (i.e., positive) time difference between this and other.

    \n
    \n
    \n

    FIXME: details needed here on time in Rust.

    \n
    \n

    Actions

    \n
    \n

    Actions are described in Actions. If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. This is particularly useful for physical actions that are externally triggered because it enables the action to convey information to the reactor. This could be, for example, the body of an incoming network message or a numerical reading from a sensor.

    \n

    Recall from Composing Reactors that the $after$ keyword on a connection between ports introduces a logical delay. This is actually implemented using a logical action. We illustrate how this is done using the DelayInt example:

    \n
    reactor Delay(delay: time = 100 ms) {\n  input in: int\n  output out: int\n  logical action a: int\n  reaction(a) -> out {=\n    if (a->has_value && a->is_present) lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // Use specialized form of schedule for integer payloads.\n    lf_schedule_int(a, self->delay, in->value);\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
      delay = new Delay();\n  source.out -> delay.in;\n  delay.in -> sink.out\n
    \n

    is equivalent to

    \n
        source.out -> sink.in after 100 ms\n
    \n

    (except that our Delay reactor will only work with data type int).

    \n

    Note: The reaction to a is given before the reaction to in above. This is important because if both inputs are present at the same tag, the first reaction must be executed before the second. Because of this reaction ordering, it is possible to create a program that has a feedback loop where the output of the Delay reactor propagates back to an input at the same tag. If the reactions were given in the opposite order, then such a program would result in a causality loop.

    \n

    In the Delay reactor, the action a is specified with a type int. The reaction to the input in declares as its effect the action a. This declaration makes it possible for the reaction to schedule a future triggering of a. The reaction uses one of several variants of the lf_schedule function, namely lf_schedule_int, a convenience function provided because integer payloads on actions are very common. We will see below, however, that payloads can have any data type.

    \n

    The first reaction declares that it is triggered by a and has effect out. To\nread the value, it uses the a->value variable. Because this reaction is first,\nthe out at any logical time can be produced before the input in is even\nknown to be present. Hence, this reactor can be used in a feedback loop, where\nout triggers a downstream reactor to send a message back to in of this same\nreactor. If the reactions were given in the opposite order, there would be a\ncausality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it as follows:

    \n
      reaction(a) -> out {=\n    if (a->has_value) {\n      lf_set(out, a->value);\n    }\n  =}\n
    \n

    It is possible to both be triggered by and schedule an action in the same\nreaction. For example, the\nfollowing CountSelf\nreactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay: time = 100 msec) {\n  output out: int\n  logical action a: int\n  reaction(startup) -> a, out {=\n    lf_set(out, 0);\n    lf_schedule_int(a, self->delay, 1);\n  =}\n  reaction(a) -> a, out {=\n    lf_set(out, a->value);\n    lf_schedule_int(a, self->delay, a->value + 1);\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    The C++ provides a simple interface for scheduling actions via a schedule() method. Actions are described in the Actions document. Consider the Schedule reactor:

    \n
    reactor Schedule {\n  input x: int\n  logical action a: void\n  reaction(x) -> a {=\n    a.schedule(200ms);\n  =}\n\n  reaction(a) {=\n    auto elapsed_time = get_elapsed_logical_time();\n    std::cout << "Action triggered at logical time " << elapsed_time.count()\n          << " after start" << std::endl;\n  =}\n}\n
    \n

    When this reactor receives an input x, it calls schedule() on the action a, specifying a logical time offset of 200 milliseconds. The action a will be triggered at a logical time 200 milliseconds after the arrival of input x. At that logical time, the second reaction will trigger and will use the get_elapsed_logical_time() function to determine how much logical time has elapsed since the start of execution.

    \n

    Notice that after the logical time offset of 200 msec, there may be another input x simultaneous with the action a. Because the reaction to a is given first, it will execute first. This becomes important when such a reactor is put into a feedback loop (see below).

    \n

    Physical actions work exactly as described in the Physical Actions section.

    \n

    Zero-Delay Actions

    \n

    If the specified delay in a schedule() is omitted or is zero, then the action a will be triggered one microstep later in superdense time (see Superdense Time). Hence, if the input x arrives at metric logical time t, and you call schedule() in one of the following ways:

    \n
    a.schedule();\na.schedule(0s);\na.schedule(reactor::Duration::zero());\n
    \n

    then when the reaction to a is triggered, the input x will be absent (it was present at the previous microstep). The reaction to x and the reaction to a occur at the same metric time t, but separated by one microstep, so these two reactions are not logically simultaneous.

    \n

    As discussed above the he metric time is visible to the programmer and can be obtained in a reaction using either get_elapsed_logical_time() or get_logical_time().

    \n

    As described in the Action document, action declarations can have a min_delay parameter. This modifies the timestamp further. Also, the action declaration may be physical rather than logical, in which case, the assigned timestamp will depend on the physical clock of the executing platform.

    \n

    Actions With Values

    \n

    If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. This is particularly useful for physical actions that are externally triggered because it enables the action to convey information to the reactor. This could be, for example, the body of an incoming network message or a numerical reading from a sensor.

    \n

    Recall from the Composing Reactors section that the after keyword on a connection between ports introduces a logical delay. This is actually implemented using a logical action. We illustrate how this is done using the DelayInt example:

    \n
    reactor Delay(delay: time = 100 ms) {\n  input in: int\n  output out: int\n  logical action d: int\n  reaction(d) -> out {=\n    if (d.is_present()) {\n      out.set(d.get());\n    }\n  =}\n  reaction(in) -> d {=\n    d.schedule(in.get(), delay);\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
    d = new Delay();\nsource.out -> d.in;\nd.in -> sink.out\n
    \n

    is equivalent to

    \n
    source.out -> sink.in after 100 ms\n
    \n

    (except that our Delay reactor will only work with data type int).

    \n

    Note: The reaction to d is given before the reaction to in above. This is important because if both inputs are present at the same tag, the first reaction must be executed before the second. Because of this reaction ordering, it is possible to create a program that has a feedback loop where the output of the Delay reactor propagates back to an input at the same tag. If the reactions were given in the opposite order, then such a program would result in a causality loop.

    \n

    The action d is specified with a type int. The reaction to the input in declares as its effect the action d. This declaration makes it possible for the reaction to schedule a future triggering of d. In the C++ target, actions use the same mechanism for passing data via value pointers as do ports. In the example above, the reactor::ImmutablValuePtr<int> derived by the call to in.get() is passed directly to schedule(). Similarly, the value can later be retrieved from the action with d.get() and passed to the output port.

    \n

    The first reaction declares that it is triggered by d and has effect out. Because this reaction is first, the out at any logical time can be produced before the input in is even known to be present. Hence, this reactor can be used in a feedback loop, where out triggers a downstream reactor to send a message back to in of this same reactor. If the reactions were given in the opposite order, there would be causality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it using is_present():

    \n
    reaction(d) -> out {=\n  if (d.is_present()) {\n    out.set(d.get());\n  }\n=}\n
    \n

    It is possible to both be triggered by and schedule an action the same reaction. For example, this reactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay:time(100 msec)) {\n  output out:int;\n  logical action a:int;\n  reaction(startup) -> a, out {=\n    out.set(0);\n    a.schedule_int(1, delay);\n  =}\n  reaction(a) -> a, out {=\n    out.set(a.get());\n    a.schedule_int(*a.get() + 1, delay);\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    Actions are described here. Actions can carry a\nvalue, a Python object that becomes available to any reaction triggered by\nthe action. This is particularly useful for physical actions that are externally\ntriggered because it enables the action to convey information to the reactor.\nThis could be, for example, the body of an incoming network message or a\nnumerical reading from a sensor. Note that actions do not have a data\ntype in the Python target, even when they carry a value.

    \n

    Recall from Composing Reactors that the\n$after$ keyword on a connection between ports introduces a logical delay. This\nis actually implemented using a logical action. We illustrate how this is done\nusing the\nDelay reactor in the DelayInt\nexample:

    \n
    reactor Delay(delay = 100 ms) {\n  input _in\n  output out\n  logical action a\n  reaction(a) -> out {=\n    if (a.value is not None) and a.is_present:\n      out.set(a.value)\n  =}\n  reaction(_in) -> a {=\n    a.schedule(self.delay, _in.value)\n  =}\n}\n
    \n

    Using this reactor as follows

    \n
        delay = new Delay()\n    <source_port_reference> -> delay._in\n    delay._in -> <destination_port_reference>\n
    \n

    is equivalent to

    \n
        <source_port_reference> -> <destination_port_reference> after 100 ms\n
    \n

    In the Delay reactor, the reaction to the input _in declares as its effect\nthe action a. This declaration makes it possible for the reaction to schedule\na future triggering of a using the\na.schedule()\nmethod.

    \n

    The first reaction declares that it is triggered by a and has effect out. To\nread the value, it uses the a.value syntax. Because this reaction is first,\nthe out at any logical time can be produced before the input _in is even\nknown to be present. Hence, this reactor can be used in a feedback loop, where\nout triggers a downstream reactor to send a message back to _in of this same\nreactor. If the reactions were given in the opposite order, there would be a\ncausality loop and compilation would fail.

    \n

    If you are not sure whether an action carries a value, you can test for it as follows:

    \n
      reaction(a) -> out {=\n    if (a.value is not None):\n      out.set(a.value)\n  =}\n
    \n

    It is possible to both be triggered by and schedule an action in the same\nreaction. For example, the\nfollowing CountSelf\nreactor will produce a counting sequence after it is triggered the first time:

    \n
    reactor CountSelf(delay = 100 ms) {\n  output out\n  logical action a\n  reaction(startup) -> a, out {=\n    out.set(0)\n    a.schedule(self.delay, 1)\n  =}\n  reaction(a) -> a, out {=\n    out.set(a.value)\n    a.schedule(self.delay, a.value + 1)\n  =}\n}\n
    \n

    Of course, to produce a counting sequence, it would be more efficient to use a state variable.

    \n
    \n
    \n

    Each action listed as an effect for a reaction is available as a schedulable object in the reaction body via the actions object. The TypeScript target provides a special actions object with a property for each schedulable action. Schedulable actions (of type t) have the object method:

    \n
    schedule: (extraDelay: TimeValue | 0, value?: T) => void;\n
    \n

    The first argument can either be the literal 0 (shorthand for 0 seconds) or a TimeValue/UnitBasedTimeValue. The second argument is the value for the action. Consider the following reactor:

    \n
    target TypeScript;\nreactor Schedule {\n  input x:number;\n  logical action a;\n  reaction(x) -> a {=\n    actions.a.schedule(new UnitBasedTimeValue(200, TimeUnit.msec), null);\n  =}\n  reaction(a) {=\n    let elapsedTime = util.getElapsedLogicalTime();\n    console.log("Action triggered at logical time " + elapsedTime + " after start.");\n  =}\n}\n
    \n

    When this reactor receives an input x, it calls schedule() on the action a, so it will be triggered at the logical time offset (200 msec) with a null value. The action a will be triggered at a logical time 200 milliseconds after the arrival of input x. This will trigger the second reaction, which will use the util.getElapsedLogicalTime() function to determine how much logical time has elapsed since the start of execution. The second argument to the schedule() function is a value, data that can be carried by the action, which is explained below. In the above example, there is no value.

    \n

    Zero-Delay Actions

    \n

    If the specified delay in a schedule() call is zero, then the action a will be triggered one microstep later in superdense time (see Superdense Time). Hence, if the input x arrives at metric logical time t, and you call schedule() as follows:

    \n
    actions.a.schedule(0);\n
    \n

    then when a reaction to a is triggered, the input x will be absent (it was present at the previous microstep). The reaction to x and the reaction to a occur at the same metric time t, but separated by one microstep, so these two reactions are not logically simultaneous. These reactions execute with different Tags.

    \n

    Actions With Values

    \n

    If an action is declared with a data type, then it can carry a value, a data value that becomes available to any reaction triggered by the action. The most common use of this is to implement a logical delay, where a value provided at an input is produced on an output with a larger logical timestamp. To accomplish that, it is much easier to use the after keyword on a connection between reactors. Nevertheless, in this section, we explain how to directly use actions with value. In fact, the after keyword is implemented as described below.

    \n

    If you are familiar with other targets (like C) you may notice it is much easier to schedule actions with values in TypeScript because of TypeScript/JavaScript’s garbage collected memory management. The following example implements a logical delay using an action with a value.

    \n
    reactor Delay(delay:time(100 ms)) {\n  input x:number;\n  output out:number;\n  logical action a:number;\n  reaction(x) -> a {=\n    actions.a.schedule(delay, x as number);\n  =}\n  reaction(a) -> out {=\n    if (a !== null){\n      out = a as number\n    }\n  =}\n}\n
    \n

    The action a is specified with a type number. The first reaction declares a as its effect. This declaration makes it possible for the reaction to schedule a future triggering of a. It’s necessary to explicitly annotate the type of x as a number in the schedule function because TypeScript doesn’t know the only trigger of a reaction must be present in that reaction.

    \n

    The second reaction declares that it is triggered by a and has effect out. When a reaction triggers or uses an action the value of that action is made available within the reaction as a local variable with the name of the action. This variable will take on the value of the action and it will have the value undefined if that action is absent because it was not scheduled for this reaction.

    \n

    The local variable cannot be used directly to schedule an action. As described above, an action a can only be scheduled in a reaction when it is 1) declared as an effect and 2) referenced through a property of the actions object. The reason for this implementation is that actions.a refers to the action, not its value, and it is possible to use both the action and the value in the same reaction. For example, the following reaction will produce a counting sequence after it is triggered the first time:

    \n
    reaction(a) -> out, a {=\n  if (a !== null) {\n    a = a as number;\n    out = a;\n    let newValue = a++;\n    actions.a.schedule(delay, newValue);\n  }\n=}\n
    \n
    \n
    \n

    Actions may carry values if they mention a data type, for instance:

    \n
    logical action act: u32;\n
    \n

    Within a reaction, you can schedule that action with a value like so

    \n
    ctx.schedule_with_v(act, Asap, 30);\n
    \n

    you can get the value from another reaction like so:

    \n
    if let Some(value) = ctx.get_action(act) {\n  // a value is present at this tag\n} else {\n  // value was not specified\n}\n
    \n

    If an action does not mention a data type, the type defaults to ().

    \n
    \n

    Schedule Functions

    \n
    \n

    Actions with values can be rather tricky to use because the value must usually be carried in dynamically allocated memory. It will not work for value to refer to a state variable of the reactor because that state variable will likely have changed value by the time the reactions to the action are invoked. Several variants of the lf_schedule function are provided to make it easier to pass values across time in varying circumstances.

    \n
    \n

    lf_schedule(<action>, <offset>);

    \n
    \n

    This is the simplest version as it carries no value. The action need not have a data type.

    \n
    \n

    lf_schedule_int(<action>, <offset>, <value>);

    \n
    \n

    This version carries an int value. The data type of the action is required to be int.

    \n
    \n

    lf_schedule_token(<action>, <offset>, <value>);

    \n
    \n

    This version carries a token, which has type token_t and points to the value, which can have any type. There is a create_token() function that can be used to create a token, but programmers will rarely need to use this. Instead, you can use lf_schedule_value() (see below), which will automatically create a token. Alternatively, for inputs with types ending in * or [], the value is wrapped in a token, and the token can be obtained using the syntax inputname->token in a reaction and then forwarded using lf_schedule_token() (see Dynamically Allocated Structs above). If the input is mutable, the reaction can then even modify the value pointed to by the token and/or use lf_schedule_token() to send the token to a future logical time. For example, the DelayPointer reactor realizes a logical delay for any data type carried by a token:

    \n
    reactor DelayPointer(delay:time(100 ms)) {\n  input in:void*;\n  output out:void*;\n  logical action a:void*;\n  reaction(a) -> out {=\n    // Using lf_set_token delegates responsibility for\n    // freeing the allocated memory downstream.\n    lf_set_token(out, a->token);\n  =}\n  reaction(in) -> a {=\n    // Schedule the actual token from the input rather than\n    // a new token with a copy of the input value.\n    lf_schedule_token(a, self->delay, in->token);\n  =}\n}\n
    \n
    \n

    lf_schedule_value(<action>, <offset>, <value>, <length>);

    \n
    \n

    This version is used to send into the future a value that has been dynamically allocated using malloc. It will be automatically freed when it is no longer needed. The value argument is a pointer to the memory containing the value. The length argument should be 1 if it is a not an array and the array length otherwise. This length will be needed downstream to interpret the data correctly. See ScheduleValue.lf.

    \n
    \n

    lf_schedule_copy(<action>, <offset>, <value>, <length>);

    \n
    \n

    This version is for sending a copy of some data pointed to by the <value> argument. The data is assumed to be a scalar or array of type matching the <action> type. The <length> argument should be 1 if it is a not an array and the array length otherwise. This length will be needed downstream to interpret the data correctly.

    \n

    Occasionally, an action payload may not be dynamically allocated nor freed. For example, it could be a pointer to a statically allocated string. If you know this to be the case, the DelayString reactor will realize a logical time delay on such a string:

    \n
    reactor DelayString(delay:time(100 msec)) {\n  input in:string;\n  output out:string;\n  logical action a:string;\n  reaction(a) -> out {=\n    lf_set(out, a->value);\n  =}\n  reaction(in) -> a {=\n    // The following copies the char*, not the string.\n    lf_schedule_copy(a, self->delay, &(in->value), 1);\n  =}\n}\n
    \n

    The data type string is an alias for char*, but Lingua Franca does not know this, so it creates a token that contains a copy of the pointer to the string rather than a copy of the string itself.

    \n
    \n
    \n

    FIXME: Give a list of schedule() functions with descriptions.

    \n
    \n
    \n

    The Python reactor target provides a .schedule() method to trigger an action at a\nfuture logical time. The .schedule() method also optionally allows for a value\nto be sent into the future. For example, take the\nScheduleValue.lf:

    \n
    main reactor ScheduleValue {\n  logical action a;\n  reaction(startup) -> a {=\n    value = "Hello"\n    a.schedule(0, value)\n  =}\n  reaction(a) {=\n    print("Received: ", a.value)\n    if a.value != "Hello":\n      sys.stderr.write("FAILURE: Should have received 'Hello'\\n")\n      exit(1)\n  =}\n}\n
    \n

    In this example, the logical action a is scheduled one\nmicrostep in the future with a string value\ncontaining \"Hello\".

    \n
    \n
    \n

    FIXME: List them here

    \n
    \n
    \n

    Within a reaction, actions may be scheduled using the schedule function:

    \n
    // schedule without additional delay\nctx.schedule(act, Asap);\n// schedule with an additional delay\nctx.schedule(act, after!(20 ms));\n// that's shorthand for\nctx.schedule(act, After(Duration.of_millis(20)));\n
    \n
    \n

    Stopping Execution

    \n
    \n

    A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method request_stop(), which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which request_stop() was called. For example, if the current tag is (2 seconds, 0), the last (stop) tag will be (2 seconds, 1). In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

    \n
    \n

    The timeout target property will take precedence over this function. For example, if a program has a timeout of 2 seconds and request_stop() is called at the (2 seconds, 0) tag, the last tag will still be (2 seconds, 0).

    \n
    \n
    \n
    \n

    A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method lf.request_stop(), which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which lf.request_stop() was called. For example, if the current tag is (2 seconds, 0), the last (stop) tag will be (2 seconds, 1). In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

    \n
    \n

    The timeout target property will take precedence over this function. For example, if a program has a timeout of 2 seconds and request_stop() is called at the (2 seconds, 0) tag, the last tag will still be (2 seconds, 0).

    \n
    \n
    \n
    \n

    A reaction may request that the execution stops after all events with the current timestamp have been processed by calling environment()->sync_shutdown(). There is also a method environment()->async_shutdown()\nwhich may be invoked from outside an reaction, like an external thread.

    \n
    \n
    \n

    A reaction may request that the execution stop by calling the function util.requestShutdown() which takes no arguments. Execution will not stop immediately when this function is called; all events with the current tag will finish processing and execution will continue for one more microstep to give shutdown triggers a chance to execute. After this additional step, execution will terminate.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n

    Log and Debug Information

    \n
    \n

    A suite of useful functions is provided in util.h for producing messages to be made visible when the generated program is run. Of course, you can always use printf, but this is not a good choice for logging or debug information, and it is not a good choice when output needs to be redirected to a window or some other user interface (see for example the sensor simulator). Also, in federated execution, these functions identify which federate is producing the message. The functions are listed below. The arguments for all of these are identical to printf with the exception that a trailing newline is automatically added and therefore need not be included in the format string.

    \n
      \n
    • \n

      LF_PRINT_DEBUG(format, ...): Use this for verbose messages that are only needed during debugging. Nothing is printed unless the target parameter logging is set to debug. THe overhead is minimized when nothing is to be printed.

      \n
    • \n
    • \n

      LF_PRINT_LOG(format, ...): Use this for messages that are useful logs of the execution. Nothing is printed unless the target parameter logging is set to log or debug. This is a macro so that overhead is minimized when nothing is to be printed.

      \n
    • \n
    • \n

      lf_print(format, ...): Use this for messages that should normally be printed but may need to be redirected to a user interface such as a window or terminal (see register_print_function below). These messages can be suppressed by setting the logging target property to warn or error.

      \n
    • \n
    • \n

      lf_print_warning(format, ...): Use this for warning messages. These messages can be suppressed by setting the logging target property to error.

      \n
    • \n
    • \n

      lf_print_error(format, ...): Use this for error messages. These messages are not suppressed by any logging target property.

      \n
    • \n
    • \n

      lf_print_error_and_exit(format, ...): Use this for catastrophic errors.

      \n
    • \n
    \n

    In addition, a utility function is provided to register a function to redirect printed outputs:

    \n
      \n
    • lf_register_print_function(function): Register a function that will be used instead of printf to print messages generated by any of the above functions. The function should accept the same arguments as printf.
    • \n
    \n
    \n
    \n

    The reactor-cpp library provides logging utilities in logging.hh for producing messages to be made visible when the generated program is run. Of course std::cout or printf can be used for the same purpose, but the logging mechanism provided by reactor-cpp is thread-safe ensuring that messages produced in parallel reactions are not interleaved with each other and provides common way for turning messages of a certain severity on and off.

    \n

    In particular, reactor-cpp provides the following logging interfaces:

    \n
      \n
    • reactor::log::Debug(): for verbose debug messages
    • \n
    • reactor::log::Info(): for info messages of general interest, info is the default severity level
    • \n
    • reactor::log::Warning(): for warning messages
    • \n
    • reactor::log::Error(): for errors
    • \n
    \n

    These utilities can be used analogues to std::cout. For instance:

    \n
    reactor::log::Info() << "Hello World! It is " << get_physical_time();\n
    \n

    Note that unlike std::cout the new line delimiter is automatically added to the end of the message.

    \n

    Which type of messages are actually produced by the compiled program can be controlled with the log-level target property.

    \n
    \n
    \n

    The Python supports the logging target specification. This will cause the runtime to produce more or less information about the execution. However, user-facing functions for different logging levels are not yet implemented (see issue #619).

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    The executable reacts to the environment variable RUST_LOG, which sets the logging level of the application. Possible values are\noff, error, warn, info, debug, trace

    \n

    Error and warning logs are on by default. Enabling a level enables all greater levels (i.e., RUST_LOG=info also enables warn and error, but not trace or debug).

    \n

    Logging can also be turned on with the --log-level CLI option, if the application features a CLI.

    \n

    Note that the logging target property is ignored, as its levels do not match the Rust standard levels we use (those of the log crate).

    \n

    Note that when building with a release profile (i.e., target property build-type is not Debug), all log statements with level debug and trace are removed from the executable, and cannot be turned on at runtime. A warning is produced by the executable if you try to use these levels explicitly.

    \n
    \n

    Libraries Available to Programmers

    \n
    \n

    Libraries Available in All Programs

    \n

    Reactions in C can use a number of pre-defined functions, macros, and constants without having to explicitly include any header files:

    \n
      \n
    • \n

      Time and tags (tag.h):

      \n
        \n
      • Specifying time value, such as MSEC and FOREVER
      • \n
      • Time data types, such as tag_t and instant_t
      • \n
      • Obtaining tag and time information, e.g. lf_time_logical and lf_time_physical
      • \n
      \n
    • \n
    • \n

      Ports

      \n
        \n
      • Writing to output ports, such as lf_set and lf_set_token (set.h)
      • \n
      • Iterating over sparse multiports, such as lf_multiport_iterator and lf_multiport_next (port.h)
      • \n
      \n
    • \n
    • \n

      Scheduling actions

      \n
        \n
      • Schedule future events, such as lf_schedule and lf_schedule_value (api.h)
      • \n
      \n
    • \n
    • \n

      File Access

      \n
        \n
      • LF_SOURCE_DIRECTORY: A C string giving the full path to the directory containing the .lf file of the program.
      • \n
      • LF_PACKAGE_DIRECTORY: A C string giving the full path to the directory that is the root of the project or package (normally, the directory above the src directory).
      • \n
      • LF_FILE_SEPARATOR: A C string giving the file separator for the platform containing the .lf file (”/” for Unix-like systems, ”\\” for Windows).
      • \n
      \n
    • \n
    \n

    These are useful when your application needs to open and read additional files. For example, the following C code can be used to open a file in a subdirectory called dir of the directory that contains the .lf file:

    \n
        const char* path = LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "dir" LF_FILE_SEPARATOR "filename"\n    FILE* fp = fopen(path, "rb");
    \n
      \n
    • \n

      Miscellaneous

      \n
        \n
      • Changing modes in modal models, lf_set_mode (set.h)
      • \n
      • Checking deadlines, lf_check_deadline (api.h)
      • \n
      • Defining and recording tracepoints, such as register_user_trace_event and tracepoint (trace.h)
      • \n
      • Printing utilities, such as lf_print and lf_print_error (util.h)
      • \n
      • Logging utilities, such as LF_PRINT_LOG and LF_PRINT_DEBUG (util.h)
      • \n
      \n
    • \n
    \n

    Standard C Libraries

    \n

    The generated C code automatically includes the following standard C libraries (see also the C standard library header files):

    \n
      \n
    • limits.h (Defines INT_MIN, INT_MAX, etc.)
    • \n
    • stdbool.h (Defines bool datatype and true and false constants)
    • \n
    • stddef.h (Defines size_t, NULL, etc.)
    • \n
    • stdint.h (Defines int64_t, int32_t, etc.)
    • \n
    • stdlib.h (Defines exit, getenv, atoi, etc.)
    • \n
    \n

    Hence, programmers are free to use functions from these libraries without explicitly providing a #include statement. Nevertheless, providing one is harmless and may be good form. In particular, future releases may not include these header files

    \n

    Available Libraries Requiring #include

    \n

    More sophisticated library functions require a #include statement in a $preamble$.\nSpecifically, platform.h includes the following:

    \n
      \n
    • Sleep functions such as lf_sleep
    • \n
    • Mutual exclusion such as lf_critial_section_enter and lf_critical_section_exit
    • \n
    • Threading functions such as lf_thread_create
    • \n
    \n

    The threading functions are only available for platforms that support multithreading.

    \n

    Available Libraries Requiring #include, a files entry, and a cmake-include

    \n

    A few utility libraries are provided, but require considerably more setup.\nThese also help to illustrate how to incorporate your own libraries.

    \n\n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    FIXME: Details needed here.

    \n
    \n
    \n

    Scheduler Target Property

    \n

    The scheduler target property is used to select the scheduler used by the C runtime. This scheduler determines the exact order in which reactions are processed, as long as the order complies with the deterministic semantics of Lingua Franca. It also assigns reactions to user-level threads and can thereby influence the assignment of reactions to processors.

    \n

    Because the C runtime scheduler operates at a higher level of abstraction than the OS, none of the scheduling policies that we currently support allow preemption; furthermore, they do not control migration of threads between processors.

    \n

    Another limitation of these schedulers is that they are constrained to process the reaction graph breadth-first. We define the level of a reaction r to be the length of the longest chain of causally dependent reactions that are all (causally) upstream of r. Current LF schedulers process one level of reactions at a time, but this constraint is more restrictive than necessary to implement Lingua Franca’s semantics and is notable only for its effect on execution times.

    \n

    The following schedulers are available:

    \n
      \n
    • NP (non-preemptive): This scheduler is the default scheduler. It ignores deadlines.
    • \n
    • GEDF_NP (global earliest-deadline-first, non-preemptive): When the semantics of Lingua Franca allows for concurrent execution of two or more ready reactions with the same level at a particular tag, this scheduler will prioritize the reaction with the earliest deadline to run first. Reactions with no explicit deadline implicitly have an infinitely late deadline.
    • \n
    • adaptive: This experimental scheduler behaves similarly to the NP scheduler, with the additional limitation that it is designed for applications that have potentially wide variability in physical execution times. It performs experiments to measure execution times at runtime to determine the degree of exploitable parallelism in various parts of the program. This lets it automate judgments which are made more naively by the other schedulers and which are typically made by the programmer in general-purpose languages.
    • \n
    \n
    \n

    Target Implementation Details

    \n
    \n

    Included Libraries

    \n

    Definitions for the following do not need to be explicitly included because the code generator exposes them in the user namespace automatically:

    \n
      \n
    • Functions and macros used to set ports and iterate over multiports
    • \n
    • Functions and macros used to schedule actions
    • \n
    • Functions and macros used to set a reactor’s mode
    • \n
    • Functions and macros used to create trace points
    • \n
    • Logging utility functions
    • \n
    • Typedefs relating to time and logical time, including tag_t, instant_t, interval_t, and microstep_t
    • \n
    • API functions for obtaining timing information about the current program execution, including the current physical and logical time
    • \n
    \n

    Some standard C libraries are exposed to the user through reactor.h, including stddef.h,\nstdio.h, and stdlib.h. In addition, math.h gets automatically included. However, users who wish to avoid breaking changes between releases should\nconsider including these libraries explicitly instead of relying on their being exposed by the\nruntime.

    \n

    Users who wish to include functionality that has a platform-specific implementation may choose to\nexplicitly include platform.h, which provides a uniform interface for various concurrency\nprimitives and sleep functions.

    \n

    Multithreaded Implementation

    \n

    By default, the C runtime system uses multiple worker threads in order to take advantage of multicore execution. The number of worker threads will match the number of cores on the machine unless the workers argument is given in the target statement or the --workers command-line argument is given.

    \n

    Upon initialization, the main thread will create the specified number of worker threads.\nExecution proceeds in a manner similar to the single threaded implementation\nexcept that the worker threads concurrently draw reactions from the reaction queue.\nThe execution algorithm ensures that no reaction executes until all reactions that it depends on have executed or it has been determined that they will not execute at the current tag.

    \n

    Single Threaded Implementation

    \n

    By giving the single-threaded (target option)[/docs/handbook/target-declaration#single-threaded] or the --single-threaded (command-line argument)[/docs/handbook/target-declaration#command-line-arguments], the generated program will execute the program using only a single thread. This option is most useful for creating programs to run on bare-metal microprocessors that have no threading support. On such platforms, mutual exclusion is typically realized by disabling interrupts.

    \n

    The execution strategy is to have two queues of pending accessor invocations, one that is sorted by\ntag (the event queue) and one that is sorted by priority (the reaction queue).\nExecution proceeds as follows:

    \n
      \n
    1. \n

      At initialization, an event for each timer is put on the event queue and logical time is initialized to the current time, represented as the number of nanoseconds elapsed since January 1, 1970.

      \n
    2. \n
    3. \n

      At each logical time, pull all events from event queue that have the same earliest tag, find the reactions that these events trigger, and put them on the reaction queue. If there are no events on the event queue, then exit the program (unless the --keepalive true (command-line argument)[/docs/handbook/target-declaration#command-line-arguments] is given).

      \n
    4. \n
    5. \n

      Wait until physical time matches or exceeds that earliest timestamp (unless the --fast true (command-line argument)[/docs/handbook/target-declaration#command-line-arguments] is given). Then advance logical time to match that earliest timestamp.

      \n
    6. \n
    7. \n

      Execute reactions in order of priority from the reaction queue. These reactions may produce outputs, which results in more events getting put on the reaction queue. Those reactions are assured of having lower priority than the reaction that is executing. If a reaction calls lf_schedule(), an event will be put on the event queue, not the reaction queue.

      \n
    8. \n
    9. \n

      When the reaction queue is empty, go to 2.

      \n
    10. \n
    \n
    \n
    \n

    Unlike the C target, the Cpp target implements more of the analysis and setup of a Lingua Franca in the runtime system. The runtime system is define in the reactor-cpp repository on GitHub. See that repo for details.

    \n
    \n
    \n

    The Python target is built on top of the C runtime to enable maximum efficiency where possible. It uses the single-threaded C runtime by default but will switch to the multi-threaded C runtime if a physical action is detected. The threading target property can be used to override this behavior.

    \n

    Running lfc on a XXX.lf program that uses the Python target specification on a\nLinux machine will create the following files (other operating systems will have\na slightly different structure and/or files):

    \n
    ├── src\n│   └── XXX.lf\n└── src-gen\n    └── XXX\n        ###### Files related to the Python C extension module for XXX ######\n        ├── build               # Temporary files for setuptools\n        ├── core                # Core C runtime files\n        ├── ctarget.c           # C target API implementations\n        ├── ctarget.h           # C target API definitions\n        ├── LinguaFrancaXXX*.so # The Python C extension module for XXX\n        ├── pythontarget.c      # Python target API implementations\n        ├── pythontarget.h      # Python target API definitions\n        ├── setup.py            # Setup file used to build the Python C extension\n        ├── XXX.c               # Source code of the Python C extension\n        ###### Files containing the Python code ######\n        └── XXX.py              # Python file containing reactors and reaction code\n
    \n

    There are two major components in the src-gen/XXX directory that together enable the execution of a Python target application:

    \n
      \n
    • An XXX.py file containing the user code (e.g., reactor definitions and reactions).
    • \n
    • The source code for a Python C extension module called LinguaFrancaXXX containing the C runtime, as well as hooks to execute the user-defined reactions.
    • \n
    \n

    The interactions between the src-gen/XXX/XXX.py file and the LinguaFrancaXXX module are explained below.

    \n

    The XXX.py file containing user code

    \n

    The XXX.py file contains all the reactor definitions in the form of Python classes. The contents of a reactor are converted as follows:

    \n
      \n
    • Each Reaction in a reactor definition will be converted to a class method.
    • \n
    • Each Parameter will be converted to a class property to make it read-only.
    • \n
    • Each State variable will be converted to an instance variable.
    • \n
    • Each trigger and effect will be converted to an object passed as a method function argument to reaction methods, allowing the body of the reaction to access them.
    • \n
    • Each reactor Preamble will be put in the class definition verbatim.
    • \n
    \n

    Finally, each reactor class instantiation will be converted to a Python object class instantiation.

    \n

    For example, imagine the following program:

    \n
    # src/XXX.lf\ntarget Python;\nreactor Foo(bar(0)) {\n  preamble {=\n    import random\n  =}\n  state baz\n  input _in\n  logical action act\n  reaction(_in, act) {=\n    # Body of the reaction\n    self.random.seed() # Note the use of self\n  =}\n}\nmain reactor {\n  foo = new Foo()\n}\n
    \n

    Th reactor Foo and its instance, foo, will be converted to

    \n
    # src-gen/XXX/XXX.py\n...\n# Python class for reactor Foo\nclass _Foo:\n\n    # From the preamble, verbatim:\n    import random\n    def __init__(self, **kwargs):\n        #Define parameters and their default values\n        self._bar = 0\n        # Handle parameters that are set in instantiation\n        self.__dict__.update(kwargs)\n\n        # Define state variables\n        self.baz = None\n\n    @property\n    def bar(self):\n        return self._bar\n\n    def reaction_function_0(self , _in, act):\n        # Body of the reaction\n        self.random.seed() # Note the use of self\n        return 0\n\n\n# Instantiate classes\nxxx_lf = [None] * 1\nxxx_foo_lf = [None] * 1\n# Start initializing XXX of class XXX\nfor xxx_i in range(1):\n    bank_index = xxx_i\n    xxx_lf[0] = _XXX(\n        _bank_index = 0,\n    )\n    # Start initializing XXX.foo of class Foo\n    for xxx_foo_i in range(1):\n        bank_index = xxx_foo_i\n        xxx_foo_lf[0] = _Foo(\n            _bank_index = 0,\n            _bar=0,\n        )\n...\n
    \n

    The generated LinguaFrancaXXX Python module (a C extension module)

    \n

    The rest of the files in src-gen/XXX form a Python C extension\nmodule\ncalled LinguaFrancaXXX that can be built by executing python3 setup.py build_ext --inplace in the src-gen/XXX/ folder. In this case, Python will\nread the instructions in the src-gen/XXX/setup.py file and build a\nLinguaFrancaXXX module in src-gen/XXX/. The --inplace flag puts the\ncompiled extension (the LinguaFrancaXXX*.so in the example above) in the\nsrc-gen directory alongside the XXX.py file.

    \n

    As mentioned before, the LinguaFrancaXXX module is separate from\nsrc-gen/XXX/XXX.py but interacts with it. Next, we explain this interaction.

    \n

    Interactions between XXX.py and LinguaFrancaXXX

    \n

    The LinguaFrancaXXX module is imported in src-gen/XXX/XXX.py:

    \n
    from LinguaFrancaXXX import *\n
    \n

    This is done to enable the main function in src-gen/XXX/XXX.py to make a call to the start() function, which is part of the generated (and installed) LinguaFrancaXXX module. This function will start the main event handling loop of the C runtime.

    \n

    From then on, LinguaFrancaXXX will call reactions that are defined in src-gen/XXX/XXX.py when needed.

    \n

    The LinguaFrancaBase package

    \n

    LinguaFrancaBase is a package that contains several helper methods and definitions that are necessary for the Python target to work. This module is installable via python3 -m pip install LinguaFrancaBase but is automatically installed if needed during the installation of LinguaFrancaXXX. The source code of this package can be found on GitHub.

    \n

    This package’s modules are imported in the XXX.py program:

    \n
    from LinguaFrancaBase.constants import * #Useful constants\nfrom LinguaFrancaBase.functions import * #Useful helper functions\nfrom LinguaFrancaBase.classes import * #Useful classes\n
    \n

    Already imported Python modules

    \n

    The following packages are already imported and thus do not need to be re-imported by the user:

    \n
    import os\nimport sys\nimport copy\n
    \n
    \n
    \n

    When a TypeScript reactor is compiled, the generated code is placed inside a project directory. This is because there are two steps of compilation. First, the Lingua Franca compiler generates a TypeScript project from the TypeScript reactor code. Second, the Lingua Franca compiler runs a TypeScript compiler on the generated TypeScript project to produce executable JavaScript. This is illustrated below:

    \n
    Lingua Franca (.lf) ==> TypeScript (.ts) ==> JavaScript (.js)\n
    \n

    Assuming the directory containing our Lingua Franca file Foo.lf is named TS, the compiler will generate the following:

    \n
      \n
    1. TS/package.json
    2. \n
    3. TS/node_modules
    4. \n
    5. TS/Foo/tsconfig.json
    6. \n
    7. TS/Foo/babel.config.js
    8. \n
    9. TS/Foo/src/
    10. \n
    11. TS/Foo/dist/
    12. \n
    \n

    Items 1, 3, and 4 are configuration files for the generated project. Item 2 is a node_modules directory with contents specified by item 1. Item 5 is the directory for generated TypeScript code. Item 6 is the directory for compiled JavaScript code. In addition to the generated code for your Lingua Franca program, items 5 and 6 include libraries from the reactor-ts submodule.

    \n

    The Lingua Franca compiler automatically invokes other programs as it compiles a Lingua Franca (.lf) file to a Node.js executable JavaScript (.js) file. The files package.json, babel.config.js, and tsconfig.json are used to configure the behavior of those other programs. Whenever you compile a .lf file for the first time, the Lingua Franca compiler will copy default versions of these configuration files into the new project so the other programs can run. The Lingua Franca compiler will only copy a default configuration file into a project if that file is not already present in the generated project. This means you, the reactor programmer, may safely modify these configuration files to control the finer points of compilation. Beware, other generated files in the project’s src and dist directories may be overwritten by the compiler.

    \n

    package.json

    \n

    Node.js uses a package.json file to describe metadata relevant to a Node project. This includes a list of project dependencies (i.e. modules) used by the project. When the Lingua Franca compiler copies a default package.json file into a Lingua Franca project that doesn’t already have a package.json, the compiler runs the command npm install to create a node_modules directory. The default package.json only lists dependencies for the reactor-ts submodule. Follow these instructions to modify package.json if you want to use other Node modules in your reactors.

    \n

    tsconfig.json

    \n

    After generating a TypeScript program from a .lf file, the Lingua Franca compiler uses the TypeScript compiler tsc to run a type check. The behavior of tsc is configured by the tsconfig.json file. You probably won’t need to modify tsconfig.json, but you can if you know what you’re doing.

    \n

    babel.config.js

    \n

    If the tsc type check was successful, the Lingua Franca compiler uses babel to compile the generated TypeScript code into JavaScript. (This blog post articulates the advantages of using babel over tsc to generate JavaScript.) There are many different flavors of JavaScript and the babel.config.js file specifies exactly what babel should generate. This is the file to edit if you want the Lingua Franca compiler to produce a different version of JavaScript as its final output.

    \n

    Debugging Type Errors

    \n

    Let’s take the minimal reactor example, and intentionally break it by adding a type error into the reaction.

    \n
    target TypeScript;\nmain reactor ReactionTypeError {\n    timer t;\n    reaction(t) {=\n        let foo:number = "THIS IS NOT A NUMBER";\n        console.log("Hello World.");\n    =}\n}\n
    \n

    This reactor will not compile, and should you attempt to compile it you will get an output from the compiler which looks something like this:

    \n
    --- Standard output from command:\nsrc/ReactionTypeError.ts(23,25): error TS2322: Type '"THIS IS NOT A NUMBER"' is not assignable to type 'number'.\n\n--- End of standard output.
    \n

    In particular the output

    \n
    src/ReactionTypeError.ts(23,25): error TS2322: Type '"THIS IS NOT A NUMBER"' is not assignable to type 'number'.
    \n

    identifies the problem: surprisingly, the string \"THIS IS NOT A NUMBER\" is not a number. However the line information (23,25) is a little confusing because it points to the location of the type error in the generated .ts file ReactionTypeError/src/ReactionTypeError.ts not in the original .lf file ReactionTypeError.lf. The .ts files produced by the TypeScript code generator are quite readable if you are familiar with the reactor-ts submodule, but even if you aren’t familiar it is not too difficult to track down the problem. Just open ReactionTypeError/src/ReactionTypeError.ts in your favorite text editor (we recommend Visual Studio for its excellent TypeScript integration) and look at line 23.

    \n
    14        this.addReaction(\n15            new Triggers(this.t),\n16            new Args(this.t),\n17            function (this, __t: Readable<Tag>) {\n18                // =============== START react prologue\n19                const util = this.util;\n20                let t = __t.get();\n21                // =============== END react prologue\n22                try {\n23                    let foo:number = "THIS IS NOT A NUMBER";\n24                    console.log("Hello World.");\n25                } finally {\n26                    // =============== START react epilogue\n27\n28                    // =============== END react epilogue\n29                }\n30            }\n31        );\n
    \n

    There (inside the try block) we can find the problematic reaction code. Reaction code is copied verbatim into generated .ts files.

    \n

    It can be a bit harder to interpret type errors outside of reaction code, but most type error messages are still relatively clear. For example if you attempt to connect a reactor output to an incompatibly typed input like:

    \n
    target TypeScript;\nmain reactor ConnectionError {\n    s = new Sender();\n    r = new Receiver();\n    s.foo -> r.bar;\n}\nreactor Sender {\n    output foo:number;\n}\nreactor Receiver {\n    input bar:string;\n}\n
    \n

    you should get an error like

    \n
    --- Standard output from command:\nsrc/InputTypeError.ts(36,23): error TS2345: Argument of type 'OutPort<number>' is not assignable to parameter of type 'Port<string>'.\n  Types of property 'value' are incompatible.\n    Type 'number | undefined' is not assignable to type 'string | undefined'.\n      Type 'number' is not assignable to type 'string | undefined'.\n\n--- End of standard output.
    \n

    The key message being Argument of type 'OutPort<number>' is not assignable to parameter of type 'Port<string>'.

    \n

    One last tip: if you attempt to reference a port, action, timer etc. named foo that isn’t declared in the triggers, uses, or effects declaration of the reaction, you will get the error Cannot find name 'foo' in the reaction body.

    \n

    Utility Function Reference

    \n

    These utility functions may be called within a TypeScript reaction:

    \n

    util.requestShutdown(): void Ends execution after one microstep. See Stopping Execution.

    \n

    util.getCurrentTag(): Tag Gets the current (logical) tag. See Tags.

    \n

    util.getCurrentLogicalTime(): TimeValue Gets the current logical TimeValue. See Time.

    \n

    util.getCurrentPhysicalTime(): TimeValue Gets the current physical TimeValue. See Time.

    \n

    util.getElapsedLogicalTime(): TimeValue Gets the elapsed logical TimeValue from execution start. See Time.

    \n

    util.getElapsedPhysicalTime(): TimeValue Gets the elapsed physical TimeValue from execution start. See Time.

    \n

    util.success(): void Invokes the reactor-ts App’s default success callback. FIXME: Currently doesn’t do anything in Lingua Franca.

    \n

    util.failure(): void Invokes the reactor-ts App’s default failure callback. Throws an error.

    \n

    Building Reactor-ts Documentation

    \n

    To build and view proper documentation for time.ts (and other reactor-ts libraries), install typedoc and run

    \n
    typedoc --out docs src\n
    \n

    from the root of the reactor-ts. You probably already have the reactor-ts submodule at

    \n
    lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/
    \n

    You should see an output like.

    \n
    Using TypeScript 3.8.3 from /usr/local/lib/node_modules/typescript/lib\nRendering [========================================] 100%\n\nDocumentation generated at /Users/<username>/git/lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/docs
    \n

    Open that path in a browser with /index.html appended to the end like

    \n
    /Users/<username>/git/lingua-franca/xtext/org.icyphy.linguafranca/src/lib/TS/reactor-ts/docs/index.html
    \n

    to navigate the docs.

    \n
    \n
    \n

    Target Properties

    \n

    Target properties may be mentioned like so:

    \n
    target Rust {\n    // enables single-file project layout\n    single-file-project: false,\n    // timeout for the execution. The program will shutdown at most after the specified duration.\n    timeout: 3 sec,\n\n    cargo-features: ["cli"]\n}\n
    \n

    See Target Declaration for the full list of supported target properties.

    \n

    The Executable

    \n

    The executable name is the name of the main reactor transformed to snake_case: main reactor RustProgram will generate rust_program. See Command-Line Arguments for details.

    \n

    File layout

    \n

    The Rust code generator generates a Cargo project with a classical layout:

    \n
    ├── Cargo.lock\n├── Cargo.toml\n├── src\n│   ├── main.rs\n│   └── reactors\n│       ├── mod.rs\n|       ├── ...\n|\n└── target\n    ├── ...
    \n

    The module structure is as follows:

    \n
      \n
    • the crate has a module reactors
    • \n
    • each LF reactor has its own submodule of reactors. For instance, Minimal.lf will generate minimal.rs. The name is transformed to snake_case.
    • \n
    \n

    This means that to refer to the contents of another reactor module, e.g. that of Other.lf, you have to write super::other::Foo. This is relevant to access preamble items.

    \n

    Single-file layout

    \n

    The Rust target supports an alternative file layout, where all reactors are generated into the main.rs file, making the project fit in a single file (excluding Cargo.toml). The module structure is unchanged: the file still contains a mod reactors { ... } within which each reactor has its mod foo { ... }. You can thus change the layout without having to update any LF code.

    \n

    Set the target property single-file-project: true to use this layout.

    \n

    Note: this alternative layout is provided for the purposes of making self-contained benchmark files. Generating actual runnable benchmarks from an LF file may be explored in the future.

    \n

    Specifying dependencies

    \n

    The Rust code generator leverages Cargo to allow LF programs to profit from Rust’s large package ecosystem. The code generator may also link support files written in pure Rust into the generated crate. Target properties are used to achieve all this.

    \n

    Adding cargo dependencies

    \n

    The cargo-dependencies target property may be used to specify dependencies on crates coming from crates.io. Here’s an example:

    \n
    target Rust {\n   cargo-dependencies: {\n      termcolor: "0.8"\n   }\n};\n
    \n

    The value of the cargo-dependencies property is a map of crate identifiers to a dependency-spec. An informal example follows:

    \n
    cargo-dependencies: {\n   // Name-of-the-crate: "version"\n   rand: "0.8",\n   // Equivalent to using an explicit map:\n   rand: {\n     version: "0.8"\n   },\n   // The map allows specifying more details\n   rand: {\n     // A path to a local unpublished crate.\n     // Note 'path' is mutually exclusive with 'git'.\n     path: "/home/me/Git/local-rand-clone"\n   },\n   rand: {\n     // A URL to a git repo\n     git: "https://github.com/me/rand",\n     // Specify an explicit Git revision number\n     rev: "abcdef1234"\n   },\n   rand: {\n     version: "0.8",\n     // you can specify cargo features\n     features: ["some-cargo-feature",]\n   }\n}\n
    \n

    When a dependency-spec is specified as an object, its key-value pairs correspond directly to those of a Cargo dependency specification. For instance for the following dependency spec:

    \n
       rand: {\n     version: "0.8",\n     // you can specify cargo features\n     features: ["some-cargo-feature",]\n   }\n
    \n

    we add the following to the generated Cargo.toml:

    \n
    [dependencies.rand]\nversion = "0.8"\nfeatures = ["some-cargo-feature"]\n
    \n

    Not all keys are necessarily supported though, e.g. the registry key is not supported (yet).

    \n

    Configuring the runtime

    \n

    The runtime crate can be configured just like other crates, using the cargo-dependencies target property, e.g.:

    \n
    cargo-dependencies: {\n   reactor_rt: {\n     features: ["parallel-runtime"]\n   }\n}\n
    \n

    The dependency is always included, with defaults picked by LFC. The location information (path/git/version key) is optional.\nSee reactor_rt for the supported features.

    \n

    Linking support files

    \n

    You can link-in additional rust modules using the rust-include target property:

    \n
    target Rust {\n  rust-include: ["foo.rs"]\n};\n
    \n

    The property is a list of paths (relative to the directory containing the .lf file). Each path should either point to a Rust file (.rs), or a directory that contains a mod.rs file. Each of those will be copied to the src directory of the generated Cargo project, and linked in to the main.rs file.

    \n

    To refer to the included module, you can use e.g. crate::foo if your module is named foo.

    \n

    Generation scheme

    \n

    Each reactor generates its own struct which contains state variables. For instance,

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    LFGenerated Rust
    \n
    reactor SomeReactor {\n  state field: u32(0)\n}\n
    \n
    \n
    struct SomeReactor {\n  field: u32\n}\n
    \n
    \n

    In the following we refer to that struct as the state struct.

    \n

    Reactions

    \n

    Reactions are each generated in a separate method of the reactor struct. Reaction names are unspecified and may be mangled to prevent explicit calling. The parameters of that method are

    \n
      \n
    • &mut self: the state struct described above,
    • \n
    • ctx: &mut ReactionCtx: the context object for the reaction execution,
    • \n
    • For each dependency, a parameter is generated.\n
        \n
      • If the dependency is a component of this reactor, the name of the parameter is just the name of the component
      • \n
      • If the dependency is a port of a child reactor, the name of the parameter is <name of the child instance>__<name of the port>, e.g. child__out for child.out.
      • \n
      • The type of the parameter depends on the kind of dependency and of component:
      • \n
      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      ComponentUse/trigger dependencyEffect dependency
      \n\n\n

      Port of type T

      \n
      \n

      &ReadablePort<T>

      \n
      \n

      WritablePort<T>

      \n
      \n

      Logical action of type T

      \n
      \n

      &LogicalAction<T>

      \n
      \n

      &mut LogicalAction<T>

      \n
      \n

      Physical action of type T

      \n
      \n

      &PhysicalActionRef<T>

      \n
      \n

      &mut PhysicalActionRef<T>

      \n
      Timer\n

      &Timer

      \n
      \n

      n/a

      \n
      \n

      Port bank of type T

      \n
      \n

      &ReadablePortBank<T>

      \n
      \n

      WritablePortBank<T>

      \n
      \n

      Undeclared dependencies, and dependencies on timers and startup or shutdown, do not generate a parameter.

      \n

      The ReactionCtx object is a mediator to manipulate all those dependency objects. It has methods to set ports, schedule actions, retrieve the current logical time, etc.

      \n

      For instance:

      \n
      reactor Source {\n    output out: i32;\n    reaction(startup) -> out {=\n        ctx.set(out, 76600)\n    =}\n}\n
      \n

      In this example, the context object ctx is used to set a port to a value. The port is in scope as out.

      \n
      \n

      ⚠ TODO when the runtime crate is public link to the docs, they should be the most exhaustive documentation.

      \n
      \n
    \n````","headings":[{"value":"Overview","depth":2},{"value":"Requirements","depth":2},{"value":"Limitations","depth":2},{"value":"The Target Specification","depth":2},{"value":"Parameters and State Variables","depth":2},{"value":"Array Values for Parameters","depth":3},{"value":"Array Values for States","depth":3},{"value":"States and Parameters with Struct Values","depth":3},{"value":"Array-Valued Parameters","depth":3},{"value":"State Variables","depth":3},{"value":"Array Expressions for State Variables and Parameters","depth":3},{"value":"Assigning Arbitrary Initial Expressions to State Variables and Parameters","depth":3},{"value":"Array or Object Parameters","depth":3},{"value":"Inputs and Outputs","depth":2},{"value":"Sending and Receiving Data","depth":3},{"value":"Persistent Inputs","depth":3},{"value":"Fixed Length Array Inputs and Outputs","depth":3},{"value":"Variable Length Array Inputs and Outputs","depth":3},{"value":"Dynamically Allocated Data","depth":3},{"value":"Mutable Inputs","depth":3},{"value":"String Types","depth":3},{"value":"Macros For Setting Output Values","depth":3},{"value":"Sending and Receiving Large Data Types","depth":3},{"value":"Sending and Receiving Objects","depth":3},{"value":"Sending and Receiving Custom Types","depth":3},{"value":"Time","depth":2},{"value":"Tags","depth":3},{"value":"Summary of Time Functions","depth":3},{"value":"Actions","depth":2},{"value":"Zero-Delay Actions","depth":3},{"value":"Actions With Values","depth":3},{"value":"Zero-Delay Actions","depth":3},{"value":"Actions With Values","depth":3},{"value":"Schedule Functions","depth":3},{"value":"Stopping Execution","depth":2},{"value":"Log and Debug Information","depth":2},{"value":"Libraries Available to Programmers","depth":2},{"value":"Libraries Available in All Programs","depth":4},{"value":"Standard C Libraries","depth":4},{"value":"Available Libraries Requiring #include","depth":4},{"value":"Available Libraries Requiring #include, a files entry, and a cmake-include","depth":4},{"value":"Scheduler Target Property","depth":2},{"value":"Target Implementation Details","depth":2},{"value":"Included Libraries","depth":3},{"value":"Multithreaded Implementation","depth":3},{"value":"Single Threaded Implementation","depth":3},{"value":"The XXX.py file containing user code","depth":3},{"value":"The generated LinguaFrancaXXX Python module (a C extension module)","depth":3},{"value":"Interactions between XXX.py and LinguaFrancaXXX","depth":3},{"value":"The LinguaFrancaBase package","depth":3},{"value":"Already imported Python modules","depth":3},{"value":"package.json","depth":3},{"value":"tsconfig.json","depth":3},{"value":"babel.config.js","depth":3},{"value":"Debugging Type Errors","depth":3},{"value":"Utility Function Reference","depth":3},{"value":"Building Reactor-ts Documentation","depth":3},{"value":"Target Properties","depth":3},{"value":"The Executable","depth":3},{"value":"File layout","depth":3},{"value":"Single-file layout","depth":4},{"value":"Specifying dependencies","depth":3},{"value":"Adding cargo dependencies","depth":4},{"value":"Configuring the runtime","depth":4},{"value":"Linking support files","depth":4},{"value":"Generation scheme","depth":3},{"value":"Reactions","depth":4}],"frontmatter":{"permalink":"/docs/handbook/target-language-details","title":"Target Language Details","oneline":"Detailed reference for each target langauge.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Expressions","oneline":"Expressions in Lingua Franca.","permalink":"/docs/handbook/expressions"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Target Declaration","oneline":"The target declaration and its parameters in Lingua Franca.","permalink":"/docs/handbook/target-declaration"}}}},"pageContext":{"id":"3-target-language-details","slug":"/docs/handbook/target-language-details","repoPath":"/packages/documentation/copy/en/reference/Target Language Details.md","previousID":"24e3b5ae-ac22-5a79-956a-4258d40ae77c","nextID":"de456861-0847-5726-aba2-da3ba779bd9d","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/termination/page-data.json b/page-data/docs/handbook/termination/page-data.json index 599397a6d..c8f239b70 100644 --- a/page-data/docs/handbook/termination/page-data.json +++ b/page-data/docs/handbook/termination/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/termination","result":{"data":{"markdownRemark":{"id":"6f15c9ee-d99f-5526-99a6-3dd6bce05820","excerpt":"Shutdown Reactions There are several mechanisms for terminating a Lingua Franca in an orderly fashion.\nAll of these mechanisms result in a final tag at which…","html":"

    Shutdown Reactions

    \n

    There are several mechanisms for terminating a Lingua Franca in an orderly fashion.\nAll of these mechanisms result in a final tag at which any reaction that declares $shutdown$ as a trigger will be invoked (recall that a tag is a tuple (logical time, microstep)). Other reactions may also be invoked at this final tag, and the order in which reactions are invoked will be constrained by the normal precedence rules.

    \n

    If a reaction triggered by $shutdown$ produces outputs, then downstream reactors will also be invoked at the final tag. If the reaction schedules any actions by calling schedule(), those will be ignored. In fact, any event after the final tag will be ignored. After the completion of the final tag, the program will exit.

    \n

    There are four ways to terminate a program:

    \n
      \n
    • Timeout: The program specifies the last logical time at which reactions should be triggered.
    • \n
    • Starvation: At the conclusion of some tag, there are no events in the event queue at future tags.
    • \n
    • Stop request: Some reaction requests that the program terminate.
    • \n
    • External signal: Program is terminated externally using operating services like control-C or kill.
    • \n
    \n

    We address each of these in turn.

    \n

    Timeout

    \n

    The target property timeout specifies the last logical time at which reactions should be triggered. The last invocation of reactions will be at tag (timeout, 0).

    \n

    There is a significant subtlety when using physical connections, which are connections using the syntax ~>. Such connections specify that the tag at the receiving end will be based on the physical time at which the message is received. If the tag assigned at the receiving end is greater than the final tag, then the message is lost. Hence, messages sent near the timeout time are likely to be lost!

    \n

    Starvation

    \n

    If a Lingua Franca program has no physical actions, and if at any time during execution there are no future events waiting to be processed, then there is no possibility for any more reactions to occur and the program will exit. This situation is called starvation. If there is a timer anywhere in the program with a period, then this condition never occurs.

    \n

    One subtlety is that reactions triggered by $shutdown$ will be invoked one microstep later than the last tag at which there was an event. They cannot be invoked at the same tag because it is only after that last tag has completed that the runtime system can be sure that there are no future events. It would not be correct to trigger the $shutdown$ reactions at that point because it would be impossible to respect the required reaction ordering.

    \n
    \n

    Starvation termination is not currently implemented for federated execution. You will need to use one of the other mechanisms to terminate a federated program.

    \n
    \n

    Stop Request

    \n

    If a reaction calls the built-in request_stop() function, then it is requesting that the program cease execution as soon as possible. This cessation will normally occur in the next microstep. The current tag will be completed as normal. Then the tag will be advanced by one microstep, and reactions triggered by $shutdown$ will be executed, along with any other reactions with triggers at that tag, with all reactions executed in precedence order.

    \n
    \n

    In a federated execution, things are more complicated. In general, it is not possible to cease execution in the next microstep because this would mean that every federate has a communication channel to every other with delay equal to one microstep. This does not create a causality loop, but it means that all federates have to advance time in lockstep, which creates a global barrier synchronization that would likely kill performance. It would also make decentralized coordination impossible because the safe-to-process (STP) threshold for all federates would diverge to infinity.

    \n

    For centralized coordination, when a reaction in a federate calls request_stop(), the federate sends a STOP_REQUEST message to the RTI with its current timestamp t as a payload and completes execution of any other reactions triggered at the current tag. It then blocks, waiting for a STOP_GRANTED message with a timestamp payload s. If s > t, then it sets timeout = s and continues executing, using the timeout mechanism (see above) to stop. If s = t, then schedules the shutdown phase to occur one microstep later, as in the unfederated case.

    \n

    When the RTI receives a STOP_REQUEST message from a federate, it forwards it to all other federates and waits for a reply from all. Each reply will have a timestamp payload. The RTI chooses s, the largest of these timestamps, and sends a STOP_GRANTED message to all federates with payload s.

    \n

    When a federate receives a STOP_REQUEST message, it replies with its current logical time t, completes its current tag (if one is progress), and blocks, waiting for a STOP_GRANTED message from the RTI. When it gets the reply with payload s, if s > t, then it sets timeout = s and continues executing, using the timeout mechanism (see above) to stop. If s = t, then it schedules the shutdown phase to occur one microstep later, as in the unfederated case.

    \n
    \n

    External Signal

    \n

    A control-C or other kill signal to a running Lingua Franca program will cause execution to stop immediately.

    \n
    \n

    For federated programs, each federate and the RTI catches external signals to shut down in an orderly way.

    \n

    When a federate gets such an external signal (e.g. control-C), it sends a RESIGN message to the RTI and an EOF (end of file) on each socket connection to another federate. It then closes all sockets and shuts down. The RTI and all other federates should continue running until some other termination condition occurs.

    \n

    When the RTI gets such an external signal (e.g. control-C), it broadcasts a STOP_REQUEST message to all federates, waits for their replies (with a timeout in case the federate or the network has failed), chooses the maximum timestamp s on the replies, broadcasts a STOP_GRANTED message to all federates with payload s, and waits for LOGICAL_TIME_COMPLETE messages as above.

    \n
    ","headings":[{"value":"Shutdown Reactions","depth":2},{"value":"Timeout","depth":2},{"value":"Starvation","depth":2},{"value":"Stop Request","depth":2},{"value":"External Signal","depth":2}],"frontmatter":{"permalink":"/docs/handbook/termination","title":"Termination","oneline":"Terminating a Lingua Franca execution.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","permalink":"/docs/handbook/distributed-execution"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"id":"1-termination","slug":"/docs/handbook/termination","repoPath":"/packages/documentation/copy/en/topics/Termination.md","previousID":"79d9c9b2-eee4-5652-9541-c483de60119e","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/termination","result":{"data":{"markdownRemark":{"id":"6f15c9ee-d99f-5526-99a6-3dd6bce05820","excerpt":"Shutdown Reactions There are several mechanisms for terminating a Lingua Franca in an orderly fashion.\nAll of these mechanisms result in a final tag at which…","html":"

    Shutdown Reactions

    \n

    There are several mechanisms for terminating a Lingua Franca in an orderly fashion.\nAll of these mechanisms result in a final tag at which any reaction that declares $shutdown$ as a trigger will be invoked (recall that a tag is a tuple (logical time, microstep)). Other reactions may also be invoked at this final tag, and the order in which reactions are invoked will be constrained by the normal precedence rules.

    \n

    If a reaction triggered by $shutdown$ produces outputs, then downstream reactors will also be invoked at the final tag. If the reaction schedules any actions by calling schedule(), those will be ignored. In fact, any event after the final tag will be ignored. After the completion of the final tag, the program will exit.

    \n

    There are four ways to terminate a program:

    \n
      \n
    • Timeout: The program specifies the last logical time at which reactions should be triggered.
    • \n
    • Starvation: At the conclusion of some tag, there are no events in the event queue at future tags.
    • \n
    • Stop request: Some reaction requests that the program terminate.
    • \n
    • External signal: Program is terminated externally using operating services like control-C or kill.
    • \n
    \n

    We address each of these in turn.

    \n

    Timeout

    \n

    The target property timeout specifies the last logical time at which reactions should be triggered. The last invocation of reactions will be at tag (timeout, 0).

    \n

    There is a significant subtlety when using physical connections, which are connections using the syntax ~>. Such connections specify that the tag at the receiving end will be based on the physical time at which the message is received. If the tag assigned at the receiving end is greater than the final tag, then the message is lost. Hence, messages sent near the timeout time are likely to be lost!

    \n

    Starvation

    \n

    If a Lingua Franca program has no physical actions, and if at any time during execution there are no future events waiting to be processed, then there is no possibility for any more reactions to occur and the program will exit. This situation is called starvation. If there is a timer anywhere in the program with a period, then this condition never occurs.

    \n

    One subtlety is that reactions triggered by $shutdown$ will be invoked one microstep later than the last tag at which there was an event. They cannot be invoked at the same tag because it is only after that last tag has completed that the runtime system can be sure that there are no future events. It would not be correct to trigger the $shutdown$ reactions at that point because it would be impossible to respect the required reaction ordering.

    \n
    \n

    Starvation termination is not currently implemented for federated execution. You will need to use one of the other mechanisms to terminate a federated program.

    \n
    \n

    Stop Request

    \n

    If a reaction calls the built-in request_stop() function, then it is requesting that the program cease execution as soon as possible. This cessation will normally occur in the next microstep. The current tag will be completed as normal. Then the tag will be advanced by one microstep, and reactions triggered by $shutdown$ will be executed, along with any other reactions with triggers at that tag, with all reactions executed in precedence order.

    \n
    \n

    In a federated execution, things are more complicated. In general, it is not possible to cease execution in the next microstep because this would mean that every federate has a communication channel to every other with delay equal to one microstep. This does not create a causality loop, but it means that all federates have to advance time in lockstep, which creates a global barrier synchronization that would likely kill performance. It would also make decentralized coordination impossible because the safe-to-process (STP) threshold for all federates would diverge to infinity.

    \n

    For centralized coordination, when a reaction in a federate calls request_stop(), the federate sends a STOP_REQUEST message to the RTI with its current timestamp t as a payload and completes execution of any other reactions triggered at the current tag. It then blocks, waiting for a STOP_GRANTED message with a timestamp payload s. If s > t, then it sets timeout = s and continues executing, using the timeout mechanism (see above) to stop. If s = t, then schedules the shutdown phase to occur one microstep later, as in the unfederated case.

    \n

    When the RTI receives a STOP_REQUEST message from a federate, it forwards it to all other federates and waits for a reply from all. Each reply will have a timestamp payload. The RTI chooses s, the largest of these timestamps, and sends a STOP_GRANTED message to all federates with payload s.

    \n

    When a federate receives a STOP_REQUEST message, it replies with its current logical time t, completes its current tag (if one is progress), and blocks, waiting for a STOP_GRANTED message from the RTI. When it gets the reply with payload s, if s > t, then it sets timeout = s and continues executing, using the timeout mechanism (see above) to stop. If s = t, then it schedules the shutdown phase to occur one microstep later, as in the unfederated case.

    \n
    \n

    External Signal

    \n

    A control-C or other kill signal to a running Lingua Franca program will cause execution to stop immediately.

    \n
    \n

    For federated programs, each federate and the RTI catches external signals to shut down in an orderly way.

    \n

    When a federate gets such an external signal (e.g. control-C), it sends a RESIGN message to the RTI and an EOF (end of file) on each socket connection to another federate. It then closes all sockets and shuts down. The RTI and all other federates should continue running until some other termination condition occurs.

    \n

    When the RTI gets such an external signal (e.g. control-C), it broadcasts a STOP_REQUEST message to all federates, waits for their replies (with a timeout in case the federate or the network has failed), chooses the maximum timestamp s on the replies, broadcasts a STOP_GRANTED message to all federates with payload s, and waits for LOGICAL_TIME_COMPLETE messages as above.

    \n
    ","headings":[{"value":"Shutdown Reactions","depth":2},{"value":"Timeout","depth":2},{"value":"Starvation","depth":2},{"value":"Stop Request","depth":2},{"value":"External Signal","depth":2}],"frontmatter":{"permalink":"/docs/handbook/termination","title":"Termination","oneline":"Terminating a Lingua Franca execution.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Distributed Execution","oneline":"Distributed Execution (preliminary)","permalink":"/docs/handbook/distributed-execution"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"id":"1-termination","slug":"/docs/handbook/termination","repoPath":"/packages/documentation/copy/en/topics/Termination.md","previousID":"79d9c9b2-eee4-5652-9541-c483de60119e","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/time-and-timers/page-data.json b/page-data/docs/handbook/time-and-timers/page-data.json index 83528e0c2..e1d18a2f1 100644 --- a/page-data/docs/handbook/time-and-timers/page-data.json +++ b/page-data/docs/handbook/time-and-timers/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/time-and-timers","result":{"data":{"markdownRemark":{"id":"051bc602-4435-5af0-930a-f471f905219e","excerpt":"$page-showing-target$ Logical Time A key property of Lingua Franca is logical time. All events occur at an instant in logical time. By default, the runtime…","html":"

    $page-showing-target$

    \n

    Logical Time

    \n

    A key property of Lingua Franca is logical time. All events occur at an instant in logical time. By default, the runtime system does its best to align logical time with physical time, which is some measurement of time on the execution platform. The lag is defined to be physical time minus logical time, and the goal of the runtime system is maintain a small non-negative lag.

    \n

    The lag is allowed to go negative only if the fast target property or the --fast command-line argument is set to true. In that case, the program will execute as fast as possible with no regard to physical time.

    \n
    \n

    In Lingua Franca, $time$ is a data type.\nA parameter, state variable, port, or action may have type $time$.\nIn the C target, time values internally have type instant_t or interval_t,\nboth of which are (usually) equivalent to the C type long long.\nIn the C++ target, time values internally have the type std::chrono::nanoseconds. For details, see the Target Language Details.\nIn the Rust target, time values internally have type FIXME.

    \n
    \n

    Time Values

    \n

    A time value is given with units (unless the value is 0, in which case the units can be omitted).\nThe allowable units are:

    \n
      \n
    • For nanoseconds: ns, nsec, or nsecs
    • \n
    • For microseconds: us, usec, or usecs
    • \n
    • For milliseconds: ms, msec, or msecs
    • \n
    • For seconds: s, sec, secs, second, or seconds
    • \n
    • For minutes: min, minute, mins, or minutes
    • \n
    • For hours: h, hour, or hours
    • \n
    • For days: d, day, or days
    • \n
    • For weeks: week or weeks
    • \n
    \n

    The following example illustrates using time values for parameters and state variables:

    \n

    $start(SlowingClock)$

    \n
    target C\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state interval: time = start\n  logical action a\n  reaction(startup) -> a {=\n    lf_schedule(a, self->start);\n  =}\n  reaction(a) -> a {=\n    instant_t elapsed_logical_time = lf_time_logical_elapsed();\n    printf("Logical time since start: %lld nsec.\\n",\n        elapsed_logical_time\n    );\n    self->interval += self->incr;\n    lf_schedule(a, self->interval);\n  =}\n}\n
    \n
    target Cpp\nmain reactor SlowingClock(start: time(100 ms), incr: time(100 ms)) {\n  state interval: time(start)\n  logical action a\n  reaction(startup) -> a {=\n    a.schedule(start);\n  =}\n  reaction(a) -> a {=\n    auto elapsed_logical_time = get_elapsed_logical_time();\n    std::cout << "Logical time since start: " << elapsed_logical_time << " nsec" << std::endl;\n    interval += incr;\n    a.schedule(interval);\n  =}\n}\n
    \n
    target Python\nmain reactor SlowingClock(start = 100 ms, incr = 100 ms) {\n  state interval = start\n  logical action a\n  reaction(startup) -> a {=\n    a.schedule(self.start)\n  =}\n  reaction(a) -> a {=\n    elapsed_logical_time = lf.time.logical_elapsed()\n    print(\n        f"Logical time since start: {elapsed_logical_time} nsec."\n    )\n    self.interval += self.incr\n    a.schedule(self.interval)\n  =}\n}\n
    \n
    target TypeScript\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state interval: time = start\n  logical action a\n  reaction(startup) -> a {=\n    actions.a.schedule(start, null);\n  =}\n  reaction(a) -> a {=\n    console.log(`Logical time since start: ${util.getElapsedLogicalTime()}`)\n    interval = interval.add(incr)\n    actions.a.schedule(interval, null)\n  =}\n}\n
    \n
    target Rust\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state start = start\n  state incr = incr\n  state interval: time = start\n  state expected_time: time()\n  logical action a\n  reaction(startup) -> a {=\n    ctx.schedule(a, After(self.start));\n  =}\n  reaction(a) -> a {=\n    println!(\n        "Logical time since start: {} nsec.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n    self.interval += self.incr;\n    ctx.schedule(a, After(self.interval));\n    self.expected_time += self.interval;\n  =}\n}\n
    \n

    $end(SlowingClock)$

    \n

    This has two time parameters, start and incr, each with default value 100 ms and type $time$. This parameter is used to initialize the interval state variable, which also stores a time. The $logical$ $action$ a, explained in Actions, is used to schedule events to occur at time start after program startup and then at intervals that are increased each time by incr. The result of executing this program will look like this:

    \n
    Logical time since start: 100000000 nsec.\nLogical time since start: 300000000 nsec.\nLogical time since start: 600000000 nsec.\nLogical time since start: 1000000000 nsec.\n...
    \n

    Timers

    \n

    The simplest use of logical time in Lingua Franca is to invoke a reaction periodically. This is done by first declaring a $timer$ using this syntax:

    \n
      timer <name>(<offset>, <period>)\n
    \n

    The <period>, which is optional, specifies the time interval between timer events. The <offset>, which is also optional, specifies the (logical) time interval between when the program starts executing and the first timer event. If no period is given, then the timer event occurs only once. If neither an offset nor a period is specified, then one timer event occurs at program start, simultaneous with the $startup$ event.

    \n

    The period and offset are given by a number and a units, for example, 10 ms. See the expressions documentation for allowable units. Consider the following example:

    \n

    $start(Timer)$

    \n
    target C\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    printf("Logical time is %lld.\\n", lf_time_logical());\n  =}\n}\n
    \n
    target Cpp\nmain reactor Timer {\n  timer t(0, 1 s)\n  reaction(t) {=\n    std::cout << "Logical time is: " << get_logical_time() << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(f"Logical time is {lf.time.logical()}.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    console.log(`Logical time is ${util.getCurrentLogicalTime()}.`)\n  =}\n}\n
    \n
    target Rust\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    println!(\n        "Logical time is {}.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Timer)$

    \n

    This specifies a timer named t that will first trigger at the start of execution and then repeatedly trigger at intervals of one second. Notice that the time units can be left off if the value is zero.

    \n

    This target provides a built-in function for retrieving the logical time at which the reaction is invoked,\nget_logical_time()\nFIXME\nlf.time.logical()\nutil.getCurrentLogicalTime()\nFIXME.\nOn most platforms (with the exception of some embedded platforms), the returned value is a 64-bit number representing the number of nanoseconds that have elapsed since January 1, 1970. Executing the above displays something like the following:

    \n
    Logical time is 1648402121312985000.\nLogical time is 1648402122312985000.\nLogical time is 1648402123312985000.\n...
    \n

    The output lines appear at one second intervals unless the fast option has been specified.

    \n

    Elapsed Time

    \n

    The times above are a bit hard to read, so, for convenience, each target provides a built-in function to retrieve the elapsed time. For example:

    \n

    $start(TimeElapsed)$

    \n
    target C\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    printf(\n        "Elapsed logical time is %lld.\\n",\n        lf_time_logical_elapsed()\n    );\n  =}\n}\n
    \n
    target Cpp\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    std::cout << "Elapsed logical time is " << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    print(\n        f"Elapsed logical time is {lf.time.logical_elapsed()}."\n    )\n  =}\n}\n
    \n
    target TypeScript\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    console.log(`Elapsed logical time is ${util.getElapsedLogicalTime()}`)\n  =}\n}\n
    \n
    target Rust\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    println!(\n        "Elapsed logical time is {}.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(TimeElapsed)$

    \n

    See the Target Language Details for the full set of functions provided for accessing time values.

    \n

    Executing this program will produce something like this:

    \n
    Elapsed logical time is 0.\nElapsed logical time is 1000000000.\nElapsed logical time is 2000000000.\n...
    \n

    Comparing Logical and Physical Times

    \n

    The following program compares logical and physical times:

    \n

    $start(TimeLag)$

    \n
    target C\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    interval_t t = lf_time_logical_elapsed();\n    interval_t T = lf_time_physical_elapsed();\n    printf(\n        "Elapsed logical time: %lld, physical time: %lld, lag: %lld\\n",\n        t, T, T-t\n    );\n  =}\n}\n
    \n
    target Cpp\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    auto logical_time = get_elapsed_logical_time();\n    auto physical_time = get_elapsed_physical_time();\n    std::cout << "Elapsed logical time: " << logical_time\n        << " physical time: " << physical_time\n        << " lag: " << physical_time - logical_time <<  std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    t = lf.time.logical_elapsed()\n    T = lf.time.physical_elapsed()\n    print(\n        f"Elapsed logical time: {t}, physical time: {T}, lag: {T-t}"\n    )\n  =}\n}\n
    \n
    target TypeScript\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    const t = util.getElapsedLogicalTime()\n    const T = util.getElapsedPhysicalTime()\n    console.log(`Elapsed logical time: ${t}, physical time: ${T}, lag: ${T.subtract(t)}`)\n  =}\n}\n
    \n
    target Rust\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    let t = ctx.get_elapsed_logical_time();\n    let T = ctx.get_elapsed_physical_time();\n    println!(\n      "Elapsed logical time: {}, physical time: {}, lag: {}",\n      t.as_nanos(),\n      T.as_nanos(),\n      (T-t).as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(TimeLag)$

    \n

    Execution will show something like this:

    \n
    Elapsed logical time: 0, physical time: 855000, lag: 855000\nElapsed logical time: 1000000000, physical time: 1004714000, lag: 4714000\nElapsed logical time: 2000000000, physical time: 2004663000, lag: 4663000\nElapsed logical time: 3000000000, physical time: 3000210000, lag: 210000\n...
    \n

    In this case, the lag varies from a few hundred microseconds to a small number of milliseconds. The amount of lag will depend on the execution platform.

    \n

    Simultaneity and Instantaneity

    \n

    If two timers have the same offset and period, then their events are logically simultaneous. No observer will be able to see that one timer has triggered and the other has not.

    \n

    A reaction is always invoked at a well-defined logical time, and logical time does not advance during its execution. Any output produced by the reaction will be logically simultaneous with the input. In other words, reactions are logically instantaneous (for an exception, see Logical Execution Time). Physical time, however, does elapse during execution of a reaction.

    \n

    Timeout

    \n

    By default, a Lingua Franca program will terminate when there are no more events to process. If there is a timer with a non-zero period, then there will always be more events to process, so the default execution will be unbounded. To specify a finite execution horizon, you can either specify a timeout target property or a --timeout command-line option. For example, the following timeout property will cause the above timer with a period of one second to terminate after 11 events:

    \n
    target C {\n  timeout: 10 s\n}\n
    \n
    target Cpp {\n  timeout: 10 s\n}\n
    \n
    target Python {\n  timeout: 10 s\n}\n
    \n
    target TypeScript {\n  timeout: 10 s\n}\n
    \n
    target Rust {\n  timeout: 10 s\n}\n
    \n

    Startup and Shutdown

    \n

    To cause a reaction to be invoked at the start of execution, a special startup trigger is provided:

    \n
    reactor Foo {\n  reaction(startup) {=\n    ... perform initialization ...\n  =}\n}\n
    \n

    The startup trigger is equivalent to a timer with no offset or period.

    \n

    To cause a reaction to be invoked at the end of execution, a special shutdown trigger is provided. Consider the following reactor, commonly used to build regression tests:

    \n

    $start(TestCount)$

    \n
    target C\nreactor TestCount(start: int = 0, stride: int = 1, num_inputs: int = 1) {\n  state count: int = start\n  state inputs_received: int = 0\n  input x: int\n  reaction(x) {=\n    printf("Received %d.\\n", x->value);\n    if (x->value != self->count) {\n      printf("ERROR: Expected %d.\\n", self->count);\n      exit(1);\n    }\n    self->count += self->stride;\n    self->inputs_received++;\n  =}\n  reaction(shutdown) {=\n    printf("Shutdown invoked.\\n");\n    if (self->inputs_received != self->num_inputs) {\n      printf("ERROR: Expected to receive %d inputs, but got %d.\\n",\n          self->num_inputs,\n          self->inputs_received\n      );\n      exit(2);\n    }\n  =}\n}\n
    \n
    target Cpp\nreactor TestCount(start: int = 0, stride: int = 1, num_inputs: int = 1) {\n  state count: int = start\n  state inputs_received: int = 0\n  input x: int\n  reaction(x) {=\n    auto value = *x.get();\n    std::cout << "Received " <<  value << std::endl;\n    if (value != count) {\n      std::cerr << "ERROR: Expected: "<< count << std::endl;\n      exit(1);\n    }\n    count += stride;\n    inputs_received++;\n  =}\n  reaction(shutdown) {=\n    std::cout << "Shutdown invoked." << std::endl;\n    if (inputs_received != num_inputs) {\n      std::cerr << "ERROR: Expected to receive " << num_inputs\n          << " inputs, but got " << inputs_received << std::endl;\n      exit(2);\n    }\n  =}\n}\n
    \n
    target Python\nreactor TestCount(start=0, stride=1, num_inputs=1) {\n  state count = start\n  state inputs_received = 0\n  input x\n  reaction(x) {=\n    print(f"Received {x.value}.")\n    if x.value != self.count:\n      sys.stderr.write(f"ERROR: Expected {self.count}.\\n")\n      exit(1)\n    self.count += self.stride\n    self.inputs_received += 1\n  =}\n  reaction(shutdown) {=\n    print("Shutdown invoked.")\n    if self.inputs_received != self.num_inputs:\n      sys.stderr.write(\n          f"ERROR: Expected to receive {self.num_inputs} inputs, but got {self.inputs_received}.\\n"\n      )\n      exit(2)\n  =}\n}\n
    \n
    target TypeScript\nreactor TestCount(start: number = 0, stride: number = 1, numInputs: number = 1) {\n  state count: number = start\n  state inputsReceived: number = 0\n  input x: number\n  reaction(x) {=\n    console.log(`Received ${x}`)\n    if (x != count) {\n      console.error(`ERROR: Expected ${count}.`)\n      process.exit(1)\n    }\n    count += stride;\n    inputsReceived++\n  =}\n  reaction(shutdown) {=\n    console.log("Shutdown invoked.")\n    if (inputsReceived != numInputs) {\n      console.error(`ERROR: Expected to receive ${numInputs}, but got ${inputsReceived}.`)\n      process.exit(2)\n    }\n  =}\n}\n
    \n
    target Rust\nreactor TestCount(start: u32 = 0, stride: u32 = 1, num_inputs: u32 = 1) {\n  state stride = stride\n  state num_inputs = num_inputs\n  state count: u32 = start\n  state inputs_received: u32 = 0\n  input x: u32\n  reaction(x) {=\n    let x = ctx.get(x).unwrap();\n    println!("Received {}.", x);\n    if x != self.count {\n      println!("ERROR: Expected {}.", self.count);\n      std::process::exit(1);\n    }\n    self.count += self.stride;\n    self.inputs_received += 1;\n  =}\n  reaction(shutdown) {=\n    println!("Shutdown invoked.");\n    if self.inputs_received != self.num_inputs {\n      println!(\n          "ERROR: Expected to receive {} inputs, but got {}.",\n          self.num_inputs,\n          self.inputs_received\n      );\n      std::process::exit(2);\n    }\n  =}\n}\n
    \n

    $end(TestCount)$

    \n

    This reactor tests its inputs against expected values, which are expected to start with the value given by the start parameter and increase by stride with each successive input. It expects to receive a total of num_inputs input events. It checks the total number of inputs received in its $shutdown$ reaction.

    \n

    The shutdown trigger typically occurs at microstep 0, but may occur at a larger microstep. See Superdense Time and Termination.

    ","headings":[{"value":"Logical Time","depth":2},{"value":"Time Values","depth":2},{"value":"Timers","depth":2},{"value":"Elapsed Time","depth":2},{"value":"Comparing Logical and Physical Times","depth":2},{"value":"Simultaneity and Instantaneity","depth":2},{"value":"Timeout","depth":2},{"value":"Startup and Shutdown","depth":2}],"frontmatter":{"permalink":"/docs/handbook/time-and-timers","title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","permalink":"/docs/handbook/parameters-and-state-variables"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","permalink":"/docs/handbook/composing-reactors"}}}},"pageContext":{"id":"1-time-and-timers","slug":"/docs/handbook/time-and-timers","repoPath":"/packages/documentation/copy/en/topics/Time and Timers.md","previousID":"20781702-b6a5-5a16-b4d8-b4c45cd76fa3","nextID":"834f9d0d-f7c6-5732-8c60-bad1954701f7","lang":"en","modifiedTime":"2023-10-19T14:14:27.305Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/time-and-timers","result":{"data":{"markdownRemark":{"id":"051bc602-4435-5af0-930a-f471f905219e","excerpt":"$page-showing-target$ Logical Time A key property of Lingua Franca is logical time. All events occur at an instant in logical time. By default, the runtime…","html":"

    $page-showing-target$

    \n

    Logical Time

    \n

    A key property of Lingua Franca is logical time. All events occur at an instant in logical time. By default, the runtime system does its best to align logical time with physical time, which is some measurement of time on the execution platform. The lag is defined to be physical time minus logical time, and the goal of the runtime system is maintain a small non-negative lag.

    \n

    The lag is allowed to go negative only if the fast target property or the --fast command-line argument is set to true. In that case, the program will execute as fast as possible with no regard to physical time.

    \n
    \n

    In Lingua Franca, $time$ is a data type.\nA parameter, state variable, port, or action may have type $time$.\nIn the C target, time values internally have type instant_t or interval_t,\nboth of which are (usually) equivalent to the C type long long.\nIn the C++ target, time values internally have the type std::chrono::nanoseconds. For details, see the Target Language Details.\nIn the Rust target, time values internally have type FIXME.

    \n
    \n

    Time Values

    \n

    A time value is given with units (unless the value is 0, in which case the units can be omitted).\nThe allowable units are:

    \n
      \n
    • For nanoseconds: ns, nsec, or nsecs
    • \n
    • For microseconds: us, usec, or usecs
    • \n
    • For milliseconds: ms, msec, or msecs
    • \n
    • For seconds: s, sec, secs, second, or seconds
    • \n
    • For minutes: min, minute, mins, or minutes
    • \n
    • For hours: h, hour, or hours
    • \n
    • For days: d, day, or days
    • \n
    • For weeks: week or weeks
    • \n
    \n

    The following example illustrates using time values for parameters and state variables:

    \n

    $start(SlowingClock)$

    \n
    target C\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state interval: time = start\n  logical action a\n  reaction(startup) -> a {=\n    lf_schedule(a, self->start);\n  =}\n  reaction(a) -> a {=\n    instant_t elapsed_logical_time = lf_time_logical_elapsed();\n    printf("Logical time since start: %lld nsec.\\n",\n        elapsed_logical_time\n    );\n    self->interval += self->incr;\n    lf_schedule(a, self->interval);\n  =}\n}\n
    \n
    target Cpp\nmain reactor SlowingClock(start: time(100 ms), incr: time(100 ms)) {\n  state interval: time(start)\n  logical action a\n  reaction(startup) -> a {=\n    a.schedule(start);\n  =}\n  reaction(a) -> a {=\n    auto elapsed_logical_time = get_elapsed_logical_time();\n    std::cout << "Logical time since start: " << elapsed_logical_time << " nsec" << std::endl;\n    interval += incr;\n    a.schedule(interval);\n  =}\n}\n
    \n
    target Python\nmain reactor SlowingClock(start = 100 ms, incr = 100 ms) {\n  state interval = start\n  logical action a\n  reaction(startup) -> a {=\n    a.schedule(self.start)\n  =}\n  reaction(a) -> a {=\n    elapsed_logical_time = lf.time.logical_elapsed()\n    print(\n        f"Logical time since start: {elapsed_logical_time} nsec."\n    )\n    self.interval += self.incr\n    a.schedule(self.interval)\n  =}\n}\n
    \n
    target TypeScript\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state interval: time = start\n  logical action a\n  reaction(startup) -> a {=\n    actions.a.schedule(start, null);\n  =}\n  reaction(a) -> a {=\n    console.log(`Logical time since start: ${util.getElapsedLogicalTime()}`)\n    interval = interval.add(incr)\n    actions.a.schedule(interval, null)\n  =}\n}\n
    \n
    target Rust\nmain reactor SlowingClock(start: time = 100 ms, incr: time = 100 ms) {\n  state start = start\n  state incr = incr\n  state interval: time = start\n  state expected_time: time()\n  logical action a\n  reaction(startup) -> a {=\n    ctx.schedule(a, After(self.start));\n  =}\n  reaction(a) -> a {=\n    println!(\n        "Logical time since start: {} nsec.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n    self.interval += self.incr;\n    ctx.schedule(a, After(self.interval));\n    self.expected_time += self.interval;\n  =}\n}\n
    \n

    $end(SlowingClock)$

    \n

    This has two time parameters, start and incr, each with default value 100 ms and type $time$. This parameter is used to initialize the interval state variable, which also stores a time. The $logical$ $action$ a, explained in Actions, is used to schedule events to occur at time start after program startup and then at intervals that are increased each time by incr. The result of executing this program will look like this:

    \n
    Logical time since start: 100000000 nsec.\nLogical time since start: 300000000 nsec.\nLogical time since start: 600000000 nsec.\nLogical time since start: 1000000000 nsec.\n...
    \n

    Timers

    \n

    The simplest use of logical time in Lingua Franca is to invoke a reaction periodically. This is done by first declaring a $timer$ using this syntax:

    \n
      timer <name>(<offset>, <period>)\n
    \n

    The <period>, which is optional, specifies the time interval between timer events. The <offset>, which is also optional, specifies the (logical) time interval between when the program starts executing and the first timer event. If no period is given, then the timer event occurs only once. If neither an offset nor a period is specified, then one timer event occurs at program start, simultaneous with the $startup$ event.

    \n

    The period and offset are given by a number and a units, for example, 10 ms. See the expressions documentation for allowable units. Consider the following example:

    \n

    $start(Timer)$

    \n
    target C\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    printf("Logical time is %lld.\\n", lf_time_logical());\n  =}\n}\n
    \n
    target Cpp\nmain reactor Timer {\n  timer t(0, 1 s)\n  reaction(t) {=\n    std::cout << "Logical time is: " << get_logical_time() << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    print(f"Logical time is {lf.time.logical()}.")\n  =}\n}\n
    \n
    target TypeScript\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    console.log(`Logical time is ${util.getCurrentLogicalTime()}.`)\n  =}\n}\n
    \n
    target Rust\nmain reactor Timer {\n  timer t(0, 1 sec)\n  reaction(t) {=\n    println!(\n        "Logical time is {}.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(Timer)$

    \n

    This specifies a timer named t that will first trigger at the start of execution and then repeatedly trigger at intervals of one second. Notice that the time units can be left off if the value is zero.

    \n

    This target provides a built-in function for retrieving the logical time at which the reaction is invoked,\nget_logical_time()\nFIXME\nlf.time.logical()\nutil.getCurrentLogicalTime()\nFIXME.\nOn most platforms (with the exception of some embedded platforms), the returned value is a 64-bit number representing the number of nanoseconds that have elapsed since January 1, 1970. Executing the above displays something like the following:

    \n
    Logical time is 1648402121312985000.\nLogical time is 1648402122312985000.\nLogical time is 1648402123312985000.\n...
    \n

    The output lines appear at one second intervals unless the fast option has been specified.

    \n

    Elapsed Time

    \n

    The times above are a bit hard to read, so, for convenience, each target provides a built-in function to retrieve the elapsed time. For example:

    \n

    $start(TimeElapsed)$

    \n
    target C\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    printf(\n        "Elapsed logical time is %lld.\\n",\n        lf_time_logical_elapsed()\n    );\n  =}\n}\n
    \n
    target Cpp\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    std::cout << "Elapsed logical time is " << get_elapsed_logical_time() << std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    print(\n        f"Elapsed logical time is {lf.time.logical_elapsed()}."\n    )\n  =}\n}\n
    \n
    target TypeScript\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    console.log(`Elapsed logical time is ${util.getElapsedLogicalTime()}`)\n  =}\n}\n
    \n
    target Rust\nmain reactor TimeElapsed {\n  timer t(0, 1 s)\n  reaction(t) {=\n    println!(\n        "Elapsed logical time is {}.",\n        ctx.get_elapsed_logical_time().as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(TimeElapsed)$

    \n

    See the Target Language Details for the full set of functions provided for accessing time values.

    \n

    Executing this program will produce something like this:

    \n
    Elapsed logical time is 0.\nElapsed logical time is 1000000000.\nElapsed logical time is 2000000000.\n...
    \n

    Comparing Logical and Physical Times

    \n

    The following program compares logical and physical times:

    \n

    $start(TimeLag)$

    \n
    target C\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    interval_t t = lf_time_logical_elapsed();\n    interval_t T = lf_time_physical_elapsed();\n    printf(\n        "Elapsed logical time: %lld, physical time: %lld, lag: %lld\\n",\n        t, T, T-t\n    );\n  =}\n}\n
    \n
    target Cpp\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    auto logical_time = get_elapsed_logical_time();\n    auto physical_time = get_elapsed_physical_time();\n    std::cout << "Elapsed logical time: " << logical_time\n        << " physical time: " << physical_time\n        << " lag: " << physical_time - logical_time <<  std::endl;\n  =}\n}\n
    \n
    target Python\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    t = lf.time.logical_elapsed()\n    T = lf.time.physical_elapsed()\n    print(\n        f"Elapsed logical time: {t}, physical time: {T}, lag: {T-t}"\n    )\n  =}\n}\n
    \n
    target TypeScript\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    const t = util.getElapsedLogicalTime()\n    const T = util.getElapsedPhysicalTime()\n    console.log(`Elapsed logical time: ${t}, physical time: ${T}, lag: ${T.subtract(t)}`)\n  =}\n}\n
    \n
    target Rust\nmain reactor TimeLag {\n  timer t(0, 1 s)\n  reaction(t) {=\n    let t = ctx.get_elapsed_logical_time();\n    let T = ctx.get_elapsed_physical_time();\n    println!(\n      "Elapsed logical time: {}, physical time: {}, lag: {}",\n      t.as_nanos(),\n      T.as_nanos(),\n      (T-t).as_nanos(),\n    );\n  =}\n}\n
    \n

    $end(TimeLag)$

    \n

    Execution will show something like this:

    \n
    Elapsed logical time: 0, physical time: 855000, lag: 855000\nElapsed logical time: 1000000000, physical time: 1004714000, lag: 4714000\nElapsed logical time: 2000000000, physical time: 2004663000, lag: 4663000\nElapsed logical time: 3000000000, physical time: 3000210000, lag: 210000\n...
    \n

    In this case, the lag varies from a few hundred microseconds to a small number of milliseconds. The amount of lag will depend on the execution platform.

    \n

    Simultaneity and Instantaneity

    \n

    If two timers have the same offset and period, then their events are logically simultaneous. No observer will be able to see that one timer has triggered and the other has not.

    \n

    A reaction is always invoked at a well-defined logical time, and logical time does not advance during its execution. Any output produced by the reaction will be logically simultaneous with the input. In other words, reactions are logically instantaneous (for an exception, see Logical Execution Time). Physical time, however, does elapse during execution of a reaction.

    \n

    Timeout

    \n

    By default, a Lingua Franca program will terminate when there are no more events to process. If there is a timer with a non-zero period, then there will always be more events to process, so the default execution will be unbounded. To specify a finite execution horizon, you can either specify a timeout target property or a --timeout command-line option. For example, the following timeout property will cause the above timer with a period of one second to terminate after 11 events:

    \n
    target C {\n  timeout: 10 s\n}\n
    \n
    target Cpp {\n  timeout: 10 s\n}\n
    \n
    target Python {\n  timeout: 10 s\n}\n
    \n
    target TypeScript {\n  timeout: 10 s\n}\n
    \n
    target Rust {\n  timeout: 10 s\n}\n
    \n

    Startup and Shutdown

    \n

    To cause a reaction to be invoked at the start of execution, a special startup trigger is provided:

    \n
    reactor Foo {\n  reaction(startup) {=\n    ... perform initialization ...\n  =}\n}\n
    \n

    The startup trigger is equivalent to a timer with no offset or period.

    \n

    To cause a reaction to be invoked at the end of execution, a special shutdown trigger is provided. Consider the following reactor, commonly used to build regression tests:

    \n

    $start(TestCount)$

    \n
    target C\nreactor TestCount(start: int = 0, stride: int = 1, num_inputs: int = 1) {\n  state count: int = start\n  state inputs_received: int = 0\n  input x: int\n  reaction(x) {=\n    printf("Received %d.\\n", x->value);\n    if (x->value != self->count) {\n      printf("ERROR: Expected %d.\\n", self->count);\n      exit(1);\n    }\n    self->count += self->stride;\n    self->inputs_received++;\n  =}\n  reaction(shutdown) {=\n    printf("Shutdown invoked.\\n");\n    if (self->inputs_received != self->num_inputs) {\n      printf("ERROR: Expected to receive %d inputs, but got %d.\\n",\n          self->num_inputs,\n          self->inputs_received\n      );\n      exit(2);\n    }\n  =}\n}\n
    \n
    target Cpp\nreactor TestCount(start: int = 0, stride: int = 1, num_inputs: int = 1) {\n  state count: int = start\n  state inputs_received: int = 0\n  input x: int\n  reaction(x) {=\n    auto value = *x.get();\n    std::cout << "Received " <<  value << std::endl;\n    if (value != count) {\n      std::cerr << "ERROR: Expected: "<< count << std::endl;\n      exit(1);\n    }\n    count += stride;\n    inputs_received++;\n  =}\n  reaction(shutdown) {=\n    std::cout << "Shutdown invoked." << std::endl;\n    if (inputs_received != num_inputs) {\n      std::cerr << "ERROR: Expected to receive " << num_inputs\n          << " inputs, but got " << inputs_received << std::endl;\n      exit(2);\n    }\n  =}\n}\n
    \n
    target Python\nreactor TestCount(start=0, stride=1, num_inputs=1) {\n  state count = start\n  state inputs_received = 0\n  input x\n  reaction(x) {=\n    print(f"Received {x.value}.")\n    if x.value != self.count:\n      sys.stderr.write(f"ERROR: Expected {self.count}.\\n")\n      exit(1)\n    self.count += self.stride\n    self.inputs_received += 1\n  =}\n  reaction(shutdown) {=\n    print("Shutdown invoked.")\n    if self.inputs_received != self.num_inputs:\n      sys.stderr.write(\n          f"ERROR: Expected to receive {self.num_inputs} inputs, but got {self.inputs_received}.\\n"\n      )\n      exit(2)\n  =}\n}\n
    \n
    target TypeScript\nreactor TestCount(start: number = 0, stride: number = 1, numInputs: number = 1) {\n  state count: number = start\n  state inputsReceived: number = 0\n  input x: number\n  reaction(x) {=\n    console.log(`Received ${x}`)\n    if (x != count) {\n      console.error(`ERROR: Expected ${count}.`)\n      process.exit(1)\n    }\n    count += stride;\n    inputsReceived++\n  =}\n  reaction(shutdown) {=\n    console.log("Shutdown invoked.")\n    if (inputsReceived != numInputs) {\n      console.error(`ERROR: Expected to receive ${numInputs}, but got ${inputsReceived}.`)\n      process.exit(2)\n    }\n  =}\n}\n
    \n
    target Rust\nreactor TestCount(start: u32 = 0, stride: u32 = 1, num_inputs: u32 = 1) {\n  state stride = stride\n  state num_inputs = num_inputs\n  state count: u32 = start\n  state inputs_received: u32 = 0\n  input x: u32\n  reaction(x) {=\n    let x = ctx.get(x).unwrap();\n    println!("Received {}.", x);\n    if x != self.count {\n      println!("ERROR: Expected {}.", self.count);\n      std::process::exit(1);\n    }\n    self.count += self.stride;\n    self.inputs_received += 1;\n  =}\n  reaction(shutdown) {=\n    println!("Shutdown invoked.");\n    if self.inputs_received != self.num_inputs {\n      println!(\n          "ERROR: Expected to receive {} inputs, but got {}.",\n          self.num_inputs,\n          self.inputs_received\n      );\n      std::process::exit(2);\n    }\n  =}\n}\n
    \n

    $end(TestCount)$

    \n

    This reactor tests its inputs against expected values, which are expected to start with the value given by the start parameter and increase by stride with each successive input. It expects to receive a total of num_inputs input events. It checks the total number of inputs received in its $shutdown$ reaction.

    \n

    The shutdown trigger typically occurs at microstep 0, but may occur at a larger microstep. See Superdense Time and Termination.

    ","headings":[{"value":"Logical Time","depth":2},{"value":"Time Values","depth":2},{"value":"Timers","depth":2},{"value":"Elapsed Time","depth":2},{"value":"Comparing Logical and Physical Times","depth":2},{"value":"Simultaneity and Instantaneity","depth":2},{"value":"Timeout","depth":2},{"value":"Startup and Shutdown","depth":2}],"frontmatter":{"permalink":"/docs/handbook/time-and-timers","title":"Time and Timers","oneline":"Time and timers in Lingua Franca.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Parameters and State Variables","oneline":"Parameters and state variables in Lingua Franca.","permalink":"/docs/handbook/parameters-and-state-variables"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Composing Reactors","oneline":"Composing reactors in Lingua Franca.","permalink":"/docs/handbook/composing-reactors"}}}},"pageContext":{"id":"1-time-and-timers","slug":"/docs/handbook/time-and-timers","repoPath":"/packages/documentation/copy/en/topics/Time and Timers.md","previousID":"20781702-b6a5-5a16-b4d8-b4c45cd76fa3","nextID":"834f9d0d-f7c6-5732-8c60-bad1954701f7","lang":"en","modifiedTime":"2023-10-19T17:25:56.906Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/timing-analysis/page-data.json b/page-data/docs/handbook/timing-analysis/page-data.json index 1b3df4782..d42fbf4c7 100644 --- a/page-data/docs/handbook/timing-analysis/page-data.json +++ b/page-data/docs/handbook/timing-analysis/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/timing-analysis","result":{"data":{"markdownRemark":{"id":"791b4a7b-0b3a-528e-a7be-2cb254c96f50","excerpt":"Examples Precision-Timed Actuation (discussion Dec 2018) Given a time unit c, H3 reacts sporadically >= 100c (e.g., 10, 120, 230, …) H4 reacts periodically with…","html":"

    Examples

    \n

    Precision-Timed Actuation (discussion Dec 2018)

    \n

    Given a time unit c,

    \n
      \n
    • H3 reacts sporadically >= 100c (e.g., 10, 120, 230, …)
    • \n
    • H4 reacts periodically with period 50c (e.g., 0, 50, 100, …)
    • \n
    • Delay adds 100c to the timestamp of each incoming event
    • \n
    • Actuate shall start executing H5 before r.t. clock exceeds time stamp of incoming events
    • \n
    \n
    +--------+\n|        |          +--------+     +-------+     +---------+\n|   H3   +----------> H1     |     |       |     |         |\n|        |          |        +-----> Delay +-----> Actuate |\n+--------+    +-----> H2     |     |  100  |     |   (H5)  |\n              |     +--------+     +-------+     +---------+\n              |\n+--------+    |\n|        |    |\n|   H4   +----+\n|        |\n+--------+
    \n

    We can construct a dependency graph:

    \n
    H3 ---> H1 ---> H2 ---> H5\n            |\nH4 ---------+
    \n

    A feasible schedule requires that:

    \n
      \n
    • WCET(H3) + WCET(H1) + WCET(H2) <= 100c
    • \n
    • WCET(H4) + WCET(H1) + WCET(H2) <= 100c
    • \n
    \n

    Preemption Example

    \n
              T = 1s          C = 300ms\n       +---------+      +----------+\n       |         |      |   Corr   |\n       |   GPS   +------> r1       +----+\n       |         |      |          |    |\n       +---------+      +----------+    |     +----------------+         D = 100ms\n                                        |     |                |        +--------+\n         T = 100ms                      +-----> r2             |        |        |\n       +---------+                            |       Ctrl     +-------->  Act.  |\n       |         |           +----------------> r3             |        |        |\n       |  IMU    +-----------+                |                |        +--------+\n       |         |                            +----------------+\n       +---------+
    \n

    This example needs the following:

    \n
      \n
    • r3 needs to preempt r1.
    • \n
    • The event from GPS needs a delay of 300ms between Corr and Ctrl, so Ctrl never sees an older event.
    • \n
    \n

    If we want to avoid preemption, as this hurts WCET analysis:

    \n
      \n
    • Split reactor Corr. into three (or more) reactors and add a delay of 100 ms after each one.
    • \n
    \n

    For both solutions, the scheduler needs a “safe to process” analysis for reaction r3 to execute while r1 is\nstill executing for an older time-stamped event.

    \n

    Preemption can be avoided when there are enough cores (or hardware threads in PRET) available to execute r1 and r3 concurrently.

    ","headings":[{"value":"Examples","depth":1},{"value":"Precision-Timed Actuation (discussion Dec 2018)","depth":2},{"value":"Preemption Example","depth":2}],"frontmatter":{"permalink":"/docs/handbook/timing-analysis","title":"Timing Analysis","oneline":"Timing Analysis.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/timing-analysis","repoPath":"/packages/documentation/copy/en/less-developed/Timing Analysis.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/timing-analysis","result":{"data":{"markdownRemark":{"id":"791b4a7b-0b3a-528e-a7be-2cb254c96f50","excerpt":"Examples Precision-Timed Actuation (discussion Dec 2018) Given a time unit c, H3 reacts sporadically >= 100c (e.g., 10, 120, 230, …) H4 reacts periodically with…","html":"

    Examples

    \n

    Precision-Timed Actuation (discussion Dec 2018)

    \n

    Given a time unit c,

    \n
      \n
    • H3 reacts sporadically >= 100c (e.g., 10, 120, 230, …)
    • \n
    • H4 reacts periodically with period 50c (e.g., 0, 50, 100, …)
    • \n
    • Delay adds 100c to the timestamp of each incoming event
    • \n
    • Actuate shall start executing H5 before r.t. clock exceeds time stamp of incoming events
    • \n
    \n
    +--------+\n|        |          +--------+     +-------+     +---------+\n|   H3   +----------> H1     |     |       |     |         |\n|        |          |        +-----> Delay +-----> Actuate |\n+--------+    +-----> H2     |     |  100  |     |   (H5)  |\n              |     +--------+     +-------+     +---------+\n              |\n+--------+    |\n|        |    |\n|   H4   +----+\n|        |\n+--------+
    \n

    We can construct a dependency graph:

    \n
    H3 ---> H1 ---> H2 ---> H5\n            |\nH4 ---------+
    \n

    A feasible schedule requires that:

    \n
      \n
    • WCET(H3) + WCET(H1) + WCET(H2) <= 100c
    • \n
    • WCET(H4) + WCET(H1) + WCET(H2) <= 100c
    • \n
    \n

    Preemption Example

    \n
              T = 1s          C = 300ms\n       +---------+      +----------+\n       |         |      |   Corr   |\n       |   GPS   +------> r1       +----+\n       |         |      |          |    |\n       +---------+      +----------+    |     +----------------+         D = 100ms\n                                        |     |                |        +--------+\n         T = 100ms                      +-----> r2             |        |        |\n       +---------+                            |       Ctrl     +-------->  Act.  |\n       |         |           +----------------> r3             |        |        |\n       |  IMU    +-----------+                |                |        +--------+\n       |         |                            +----------------+\n       +---------+
    \n

    This example needs the following:

    \n
      \n
    • r3 needs to preempt r1.
    • \n
    • The event from GPS needs a delay of 300ms between Corr and Ctrl, so Ctrl never sees an older event.
    • \n
    \n

    If we want to avoid preemption, as this hurts WCET analysis:

    \n
      \n
    • Split reactor Corr. into three (or more) reactors and add a delay of 100 ms after each one.
    • \n
    \n

    For both solutions, the scheduler needs a “safe to process” analysis for reaction r3 to execute while r1 is\nstill executing for an older time-stamped event.

    \n

    Preemption can be avoided when there are enough cores (or hardware threads in PRET) available to execute r1 and r3 concurrently.

    ","headings":[{"value":"Examples","depth":1},{"value":"Precision-Timed Actuation (discussion Dec 2018)","depth":2},{"value":"Preemption Example","depth":2}],"frontmatter":{"permalink":"/docs/handbook/timing-analysis","title":"Timing Analysis","oneline":"Timing Analysis.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/timing-analysis","repoPath":"/packages/documentation/copy/en/less-developed/Timing Analysis.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/tools/page-data.json b/page-data/docs/handbook/tools/page-data.json index eeae2d662..efaa9e2a1 100644 --- a/page-data/docs/handbook/tools/page-data.json +++ b/page-data/docs/handbook/tools/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/tools","result":{"data":{"markdownRemark":{"id":"61cd9ca1-e842-5a49-9f75-b0310f8cec4e","excerpt":"IDE integration The idea is to build a language server to facilitate the integration with a variety of editors/IDEs. See Language Server Protocol (LSP) for more…","html":"

    IDE integration

    \n

    The idea is to build a language server to facilitate the integration with a variety of editors/IDEs. See Language Server Protocol (LSP) for more information.

    \n
                     +-------------------------------------+\n+--------+       |  +----------+         +----------+  |\n|        +-------|-->    LF    +--------->  Target  |  |\n| Editor |  src  |  | Compiler | gen src | Compiler |  |\n|        <-------|--+          <---------+          |  |\n+--------+  err  |  +----------+ gen err +----------+  |\n                 |          Language Server            |\n                 +-------------------------------------+
    \n

    If the LF compiler encounters any syntax errors, it will report them to the editor (the language client). If the LF code compiles, the output will be sent to the target compiler. If the target compiler reports any errors, these, too, will be reported to the editor via the language server. The tricky part is to match target language errors to LF source locations; the language server will have to do some bookkeeping.

    ","headings":[{"value":"IDE integration","depth":1}],"frontmatter":{"permalink":"/docs/handbook/tools","title":"Tools","oneline":"LF Tools.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/tools","repoPath":"/packages/documentation/copy/en/less-developed/Tools.md","lang":"en","modifiedTime":"2023-10-19T14:14:27.301Z"}},"staticQueryHashes":[]} \ No newline at end of file +{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/tools","result":{"data":{"markdownRemark":{"id":"61cd9ca1-e842-5a49-9f75-b0310f8cec4e","excerpt":"IDE integration The idea is to build a language server to facilitate the integration with a variety of editors/IDEs. See Language Server Protocol (LSP) for more…","html":"

    IDE integration

    \n

    The idea is to build a language server to facilitate the integration with a variety of editors/IDEs. See Language Server Protocol (LSP) for more information.

    \n
                     +-------------------------------------+\n+--------+       |  +----------+         +----------+  |\n|        +-------|-->    LF    +--------->  Target  |  |\n| Editor |  src  |  | Compiler | gen src | Compiler |  |\n|        <-------|--+          <---------+          |  |\n+--------+  err  |  +----------+ gen err +----------+  |\n                 |          Language Server            |\n                 +-------------------------------------+
    \n

    If the LF compiler encounters any syntax errors, it will report them to the editor (the language client). If the LF code compiles, the output will be sent to the target compiler. If the target compiler reports any errors, these, too, will be reported to the editor via the language server. The tricky part is to match target language errors to LF source locations; the language server will have to do some bookkeeping.

    ","headings":[{"value":"IDE integration","depth":1}],"frontmatter":{"permalink":"/docs/handbook/tools","title":"Tools","oneline":"LF Tools.","preamble":""}},"prev":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}},"next":{"childMarkdownRemark":{"frontmatter":{"title":"Contributing","oneline":"Contribute to Lingua Franca.","permalink":"/docs/handbook/contributing"}}}},"pageContext":{"slug":"/docs/handbook/tools","repoPath":"/packages/documentation/copy/en/less-developed/Tools.md","lang":"en","modifiedTime":"2023-10-19T17:25:56.902Z"}},"staticQueryHashes":[]} \ No newline at end of file diff --git a/page-data/docs/handbook/tracing/page-data.json b/page-data/docs/handbook/tracing/page-data.json index b19c894dd..b5a80430e 100644 --- a/page-data/docs/handbook/tracing/page-data.json +++ b/page-data/docs/handbook/tracing/page-data.json @@ -1 +1 @@ -{"componentChunkName":"component---src-templates-documentation-tsx","path":"/docs/handbook/tracing","result":{"data":{"markdownRemark":{"id":"7f0a051c-f9a3-5d83-b042-6408ba55e8a0","excerpt":"Tracing is a powerful tool when it comes to analysis and debugging of applications. Unfortunately, most tracing tools that are readily available are designed…","html":"

    Tracing is a powerful tool when it comes to analysis and debugging of applications. Unfortunately, most tracing tools that are readily available are designed specifically for analyzing processes, threads and system calls. Specialized tools are required to enable analysis that is tailored to an alternative model of computation such as Reactors. The tools should be capable of understanding the fundamental concepts of the model, such as the distinction between logical and physical time, as well as structural units such as reactors and reactions. This page gives an overview of the currently supported trace mechanism, as well as an outline of alternative tools that could be useful in the future.

    \n

    Tracing is different from logging. Logging produces human-readable output in textual form and incurs significant overhead. Tracing produces binary data that must be further processed to be useful and is designed to have minimal impact on the execution time of a program.

    \n

    Tracing is currently supported in the C, Python, and C++ targets. The mechanism used in C and Python is different from that used in C++. Tracing in C++ requires third-party tools that may only be available in Linux. Tracing in C and Python does not require any third-party tools.

    \n
    \n

    Tracing in C++

    \n

    Tracing in the C++ target of Lingua Franca is based on three third-party tools. LTTng is a Linux tool used to instrument the Lingua Franca program and to record traces in the CTF, which minimizes the overhead of instrumentation. Chrome (or Chromium) has a build in trace viewer that is used to visualize the recorded trace data in a reactor-specific way. Since the Chrome trace-viewer cannot read CTF traces directly, we use Babeltrace2 to convert the recorded CTF trace to a JSON file that the Google trace viewer can load.

    \n

    Usage

    \n

    Some helper scripts that we will use below, can be found in the reactor-cpp repository.

    \n
      \n
    1. \n

      Build and install the user space tools of LTTng (lttng-ust) as described here. On Arch, there is a community package available pacman -Sy lttng-ust. On Ubuntu, you need to install lttng-tools, lttng-modules-dkms, and liblttng-ust-dev

      \n
    2. \n
    3. \n

      Build and install Babeltrace2 and its python bindings as described here. In most cases, the following steps should work:

      \n
        \n
      1. git clone --branch v2.0.4 git@github.com:efficios/babeltrace.git
      2. \n
      3. cd babeltrace
      4. \n
      5. ./bootstrap
      6. \n
      7. ./configure --prefix=/path/to/preferred/install/location --enable-python-bindings --disable-debug-info --disable-man-pages
      8. \n
      9. make install
      10. \n
      \n
    4. \n
    5. \n

      Make sure babeltrace is available on your path:

      \n
        \n
      1. export PYTHONPATH=${PYTHONPATH}:/path/to/preferred/install/location/lib/python3.8/site-packages
      2. \n
      3. export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/path/to/preferred/install/location/lib
      4. \n
      \n
    6. \n
    7. \n

      Modify the target declaration of your Lingua Franca program to enable tracing:

      \n
    8. \n
    \n
    target Cpp {\n    tracing: true\n};\n
    \n
      \n
    1. Build the Lingua Franca program. The current build process is not very robust and does not automatically rebuild the reactor-cpp framework with tracing support if an earlier build exist. Be sure to remove all build artifacts rm -r bin build include lib share src-gen before triggering a new build. Alternatively, if you compile with lfc, you can add -c to the command to clean before building.
    2. \n
    3. Start a LTTng user space session by simply running the start_tracing.sh script. This will print the directory in which the recorded traces will be placed.
    4. \n
    5. Run your instrumented Lingua Franca application.
    6. \n
    7. Stop the LTTng session using stop_tracing.sh.
    8. \n
    9. Convert the recorded CTF trace to a JSON file using ctf_to_json.py <lttng-session-dir>. <lttng-session-dir> is the output directory reported by start_tracing.sh. By default, this produces a file trace.json. Optionally, the default output file can be overridden using -o or --output.
    10. \n
    11. Open Chrome (or Chromium) and go to about://tracing. Load the previously generated JSON file to visualize it.
    12. \n
    \n

    The Trace View

    \n

    \"Screenshot_20200512_165849\"

    \n

    The trace visualization consists of two parts. The upper part (labeled Execution), shows the physical time at which reactions are executed by the scheduler or by its worker threads. The lower parts show the reactors of the program. For each reactor, all scheduled actions (red markers) and all triggered reactions (blue markers) are visualized by their logical time. All elements in the trace view can be clicked on to display more detailed information.

    \n

    Supporting Tracing in Other Targets

    \n

    The same mechanism as described above can be used to trace Lingua Franca applications in other target languages. The key is to instrument the target runtime in order to produce CTF traces. LTTng comes with support for multiple languages. As a fallback solution, C can be used to define the trace points which then can be used from the target language through a foreign function interface. It should also be considered, to use an alternative library in the target language that is capable of producing CTF traces. The only requirement is that the generated CTF events have a similar structure, as it is currently used in the C++ target. See trace.hh in reactor-cpp to get an overview of the available trace points.

    \n

    Trace Viewers

    \n

    This section gives a brief overview of trace viewers that could be applicable for tracing Lingua Franca applications.

    \n

    Google Trace Viewer

    \n

    The Google Trace Viewer is the only viewer currently supported. Since it reads JSON files, it is easy to use and a conversion script can easily tailor the trace data such that it is correctly displayed by the viewer. Documentation of the JSON trace format can be found here. There is also a list of available color codes. The approach of using LTTng for tracing, a converter and Google Trace Viewer can also be used to sample and visualize data live. This is shown in the Scalapus project.

    \n

    Trace Compass

    \n

    Trace Compass is based on Eclipse and has native support for CTF traces. Due to the Eclipse support, Trace Compass is a natural candidate for integration with the Lingua Franca IDE. However, Trace Compass is tailored for the visualization of kernel traces or running user processes. Out of the box, it cannot make sense of reactor traces. There are various ways to customize Trace Compass in order to properly display the data, but they are difficult to use and/or not well documented.

    \n
      \n
    1. Custom trace analysis and views can be defined in an XML format. This probably works well for smaller tasks, but programming a mildly complex analysis in XML without proper error checking and debugging appears ridiculous.
    2. \n
    3. Custom analysis and views can also be programmed in scripting languages such as Python or Java Script. However, the tools seem not to be ready for production. I was not able to get the Python support working. Using Java Script the tools worked for simple tasks, but I was not able to come to a satisfactory results due to lack of or incomplete documentation and low customizability.
    4. \n
    5. Writing a Trace Compass Plugin. This seems to be the most promising approach, but probably requires a considerable amount of work. Writing such a plugin and integrating it with the Lingua Franca IDE could be a nice student project though.
    6. \n
    \n

    Vampir

    \n

    Vampir is another powerful tracing tool that is mainly developed at TU Dresden. It targets mostly HPC applications, and I am not sure if it can be adjusted to display specific information other than details of processes and threads.

    \n
    \n
    \n

    Tracing in C and Python

    \n

    The C and Python tracing mechanism depends only on the availability of the pthread library. Like C++ tracing, tracing is enabled by a target parameter:

    \n
    target C {\n    tracing: true\n};\n
    \n

    Once it is enabled, when the compiled program, say Foo.lf, is executed, a trace file is created, Foo.lft (the extension is for “Lingua Franca trace”). If you wish to customize the root name of the trace file, you can specify the following target property instead:

    \n
    target C {\n    tracing: {trace-file-name: "Bar"}\n};\n
    \n

    This will result in the trace file being named Bar.lft, regardless of the name of the .lf file.

    \n

    The trace file is a binary file. It is not human readable. There are utilities for reading it:

    \n
      \n
    • trace_to_csv: This program creates a text file with one line per traced event in comma-separated list format.
    • \n
    • trace_to_chrome: This program creates a text file in JSON format that is suitable for reading into the same Google Trace Viewer, which runs in Chrome, as used above in C++ tracing.
    • \n
    • trace_to_influxdb : This program will send the traced event to a running InfluxDB database server.
    • \n
    • fedsd: This program creates a timed sequence diagram showing the interactions between components of a federated program (see Tracing Federated Programs below).
    • \n
    \n

    These four programs are located in reactor-c at lingua-franca/core/src/main/resources/lib/c/reactor-c/util/tracing. Running sudo make install in that directory will put executables into usr/local/bin.

    \n

    Consider for example the ThreadedThreaded.lf test, which executes a number of heavyweight computations in parallel on multiple cores. If you enable tracing as shown above and run the program, a ThreadedTheread.lft file will appear. Running

    \n
       trace_to_csv ThreadedThreaded\n
    \n

    will create a file called ThreadedThreaded.csv that looks like this:

    \n
    Event, Reactor, Reaction, Worker, Elapsed Logical Time, Microstep, Elapsed Physical Time, Trigger, Extra Delay\nSchedule called, a, 0, 0, 0, 0, 704000, a.t, 0\nSchedule called, a, 0, 0, 0, 0, 704000, a.t, 200000000\nSchedule called, a, 0, 0, 200000000, 0, 177916000, a.t, 200000000\n...\nReaction starts, a, 0, 1, 0, 0, 765000, NO TRIGGER, 0\nReaction ends, a, 0, 1, 0, 0, 765000, NO TRIGGER, 0\nReaction starts, t[0], 0, 1, 0, 0, 793000, NO TRIGGER, 0\nReaction ends, t[0], 0, 1, 0, 0, 177520000, NO TRIGGER, 0\nReaction starts, t[3], 0, 1, 200000000, 0, 177955000, NO TRIGGER, 0\nReaction ends, t[3], 0, 1, 200000000, 0, 348602000, NO TRIGGER, 0
    \n

    The first line defines each of the columns. For example, the second line records a call to lf_schedule() for reactor named a, with no associated reaction, in worker thread 0, at (elapsed) logical time 0 with microstep 0. The call occurred at (elapsed) physical time\n704 microseconds and is scheduling the trigger named a.t (a timer) with extra delay 0. This file can be imported into any spreadsheet program and sorted and analyzed.

    \n

    The trace_to_csv utility will also create a summary file called ThreadedThreaded_summary.csv that looks like this after importing in Excel:

    \n

    \n \n \n

    \n

    If you call

    \n
       trace_to_chrome ThreadedThreaded\n
    \n

    then a ThreadedThreaded.json file is created. To visualize the data, point your Chrome browser to chrome://tracing/. Click on the Load button and select the .json file that you just created. The result should look something like this:

    \n

    \n \n \n

    \n

    The tan-colored regions whose labels start with “A” and “W” represent time spent advancing logical time and waiting for activity on the reaction queue, respectively. When logical time advances, unless you have specified the -fast option, one of the worker threads blocks execution until physical time catches up with logical time. The remaining worker threads block waiting for reactions that are ready to execute appear on the reaction queue.

    \n

    The JSON trace format can be found here. There is also a list of available color codes.

    \n

    User-Defined Tracepoints

    \n

    Users can add their own tracepoints in order to provide low-overhead recording of events and events with values that occur during the execution of reactions. To do this, the first step is to register the trace event in a startup reaction as follows:

    \n
        reaction(startup) {=\n        if (!register_user_trace_event("Description of event")) {\n            fprintf(stderr, "ERROR: Failed to register trace event.\\n");\n            exit(1);\n        }\n    =}\n
    \n

    The description of the event is an arbitrary string, but the string must be unique. All events with the same description will be collected together in any display of events.

    \n

    To then actually record an event, in a reaction, call tracepoint_user_event, passing it the same string. E.g.,

    \n
    \treaction(in) -> out {=\n\t    ...\n\t    tracepoint_user_event("Description of event");\n\t    ...\n\t=}\n
    \n

    You can also pass a value to the trace. The type of the value is long long, so it can be a time value or an int. For example,

    \n
    \treaction(in) -> out {=\n\t    ...\n\t    tracepoint_user_value("Description of event", 42);\n\t    ...\n\t=}\n
    \n

    An example of a Chrome display of a run of the Tracing regression test is here:

    \n

    \n \n \n

    \n

    In this image, “Number of Destination invocations” is an event description to\nwhich values 1 through 10 were passed. This results in the shaded value plot\nshown first. The other four rows are just pure events (with no value). They are\nshown by (extremely) thin lines positioned at the physical time of the\noccurrence of the event. Dragging the mouse over those thin lines shows further\ndetails about the event in the window below.

    \n

    Tracing Federated Programs

    \n

    When the tracing target parameter is set to true in a federated program, then each federate plus the RTI will generate a binary trace file. The utility fedsd generates an HTML file containing an SVG graphic that shows the messages exchanged between components over time. Like the other utilities, fedsd is defined in lingua_franca/util/tracing and installed using make install.

    \n

    Consider the following LF program:

    \n\"Feedback\n

    Setting tracing: true in this program and running it produces four .lft files. Running fedsd on those files:

    \n
       fedsd
    \n

    results in converting the files to .csv files and then generating a trace_svg.html file. Opening that file reveals a trace, the beginning of which looks like this:

    \n

    \n \n \n

    \n

    If you call

    \n
       trace_to_chrome ThreadedThreaded\n
    \n

    then a ThreadedThreaded.json file is created. To visualize the data, point your Chrome browser to chrome://tracing/. Click on the Load button and select the .json file that you just created. The result should look something like this:

    \n

    \n \n \n

    \n

    The tan-colored regions whose labels start with “A” and “W” represent time spent advancing logical time and waiting for activity on the reaction queue, respectively. When logical time advances, unless you have specified the -fast option, one of the worker threads blocks execution until physical time catches up with logical time. The remaining worker threads block waiting for reactions that are ready to execute appear on the reaction queue.

    \n

    The JSON trace format can be found here. There is also a list of available color codes.

    \n

    User-Defined Tracepoints

    \n

    Users can add their own tracepoints in order to provide low-overhead recording of events and events with values that occur during the execution of reactions. To do this, the first step is to register the trace event in a startup reaction as follows:

    \n
        reaction(startup) {=\n        if (!register_user_trace_event("Description of event")) {\n            fprintf(stderr, "ERROR: Failed to register trace event.\\n");\n            exit(1);\n        }\n    =}\n
    \n

    The description of the event is an arbitrary string, but the string must be unique. All events with the same description will be collected together in any display of events.

    \n

    To then actually record an event, in a reaction, call tracepoint_user_event, passing it the same string. E.g.,

    \n
    \treaction(in) -> out {=\n\t    ...\n\t    tracepoint_user_event("Description of event");\n\t    ...\n\t=}\n
    \n

    You can also pass a value to the trace. The type of the value is long long, so it can be a time value or an int. For example,

    \n
    \treaction(in) -> out {=\n\t    ...\n\t    tracepoint_user_value("Description of event", 42);\n\t    ...\n\t=}\n
    \n

    An example of a Chrome display of a run of the Tracing regression test is here:

    \n

    \n \n \n

    \n

    In this image, “Number of Destination invocations” is an event description to\nwhich values 1 through 10 were passed. This results in the shaded value plot\nshown first. The other four rows are just pure events (with no value). They are\nshown by (extremely) thin lines positioned at the physical time of the\noccurrence of the event. Dragging the mouse over those thin lines shows further\ndetails about the event in the window below.

    \n

    Tracing Federated Programs

    \n

    When the tracing target parameter is set to true in a federated program, then each federate plus the RTI will generate a binary trace file. The utility fedsd generates an HTML file containing an SVG graphic that shows the messages exchanged between components over time. Like the other utilities, fedsd is defined in lingua_franca/util/tracing and installed using make install.

    \n

    Consider the following LF program:

    \n\"Feedback\n

    Setting tracing: true in this program and running it produces four .lft files. Running fedsd on those files:

    \n
       fedsd
    \n

    results in converting the files to .csv files and then generating a trace_svg.html file. Opening that file reveals a trace, the beginning of which looks like this:

    \n

    \n

    Skip to main content

    Note: Lingua Franca is an evolving language, and the older papers below may use a syntax that does not match the current syntax. Nevertheless, these papers are useful for understanding the principles.

    Published Papers

    In reverse chronological order:

    Presentations

    Press Coverage

    Learning Resources

    \ No newline at end of file +
    Skip to main content

    Note: Lingua Franca is an evolving language, and the older papers below may use a syntax that does not match the current syntax. Nevertheless, these papers are useful for understanding the principles.

    Published Papers

    In reverse chronological order:

    Presentations

    Press Coverage

    Learning Resources

    \ No newline at end of file diff --git a/sitemap/sitemap-0.xml b/sitemap/sitemap-0.xml index 74525bfd1..db806d9cb 100644 --- a/sitemap/sitemap-0.xml +++ b/sitemap/sitemap-0.xml @@ -1 +1 @@ -https://www.lf-lang.org/docs/handbook/contributingdaily0.7https://www.lf-lang.org/docs/handbook/eclipse-oomphdaily0.7https://www.lf-lang.org/docs/handbook/intellijdaily0.7https://www.lf-lang.org/docs/handbook/developer-setupdaily0.7https://www.lf-lang.org/docs/handbook/regression-testsdaily0.7https://www.lf-lang.org/docs/handbook/running-benchmarksdaily0.7https://www.lf-lang.org/docs/handbook/website-developmentdaily0.7https://www.lf-lang.org/docs/handbook/arduinodaily0.7https://www.lf-lang.org/docs/handbook/zephyrdaily0.7https://www.lf-lang.org/docs/handbook/proof-importdaily0.7https://www.lf-lang.org/docs/handbook/logical-execution-timedaily0.7https://www.lf-lang.org/docs/handbook/related-workdaily0.7https://www.lf-lang.org/docs/handbook/timing-analysisdaily0.7https://www.lf-lang.org/docs/handbook/toolsdaily0.7https://www.lf-lang.org/docs/handbook/language-specificationdaily0.7https://www.lf-lang.org/docs/handbook/generic-types-interfaces-inheritancedaily0.7https://www.lf-lang.org/docs/handbook/import-systemdaily0.7https://www.lf-lang.org/docs/handbook/reactors-on-patmosdaily0.7https://www.lf-lang.org/docs/handbook/featuresdaily0.7https://www.lf-lang.org/docs/handbook/containerized-executiondaily0.7https://www.lf-lang.org/docs/handbook/expressionsdaily0.7https://www.lf-lang.org/docs/handbook/securitydaily0.7https://www.lf-lang.org/docs/handbook/target-declarationdaily0.7https://www.lf-lang.org/docs/handbook/tracingdaily0.7https://www.lf-lang.org/docs/handbook/code-extensiondaily0.7https://www.lf-lang.org/docs/handbook/command-line-toolsdaily0.7https://www.lf-lang.org/docs/handbook/epoch-idedaily0.7https://www.lf-lang.org/docs/handbook/troubleshootingdaily0.7https://www.lf-lang.org/docs/handbook/a-first-reactordaily0.7https://www.lf-lang.org/docs/handbook/actionsdaily0.7https://www.lf-lang.org/docs/handbook/causality-loopsdaily0.7https://www.lf-lang.org/docs/handbook/composing-reactorsdaily0.7https://www.lf-lang.org/docs/handbook/deadlinesdaily0.7https://www.lf-lang.org/docs/handbook/distributed-executiondaily0.7https://www.lf-lang.org/docs/handbook/extending-reactorsdaily0.7https://www.lf-lang.org/docs/handbook/genericsdaily0.7https://www.lf-lang.org/docs/handbook/inputs-and-outputsdaily0.7https://www.lf-lang.org/docs/handbook/methodsdaily0.7https://www.lf-lang.org/docs/handbook/modal-modelsdaily0.7https://www.lf-lang.org/docs/handbook/multiports-and-banksdaily0.7https://www.lf-lang.org/docs/handbook/overviewdaily0.7https://www.lf-lang.org/docs/handbook/parameters-and-state-variablesdaily0.7https://www.lf-lang.org/docs/handbook/preamblesdaily0.7https://www.lf-lang.org/docs/handbook/reaction-declarationsdaily0.7https://www.lf-lang.org/docs/handbook/reactionsdaily0.7https://www.lf-lang.org/docs/handbook/superdense-timedaily0.7https://www.lf-lang.org/docs/handbook/terminationdaily0.7https://www.lf-lang.org/docs/handbook/time-and-timersdaily0.7https://www.lf-lang.org/docs/handbook/tutorial-videodaily0.7https://www.lf-lang.org/docs/handbook/target-language-detailsdaily0.7https://www.lf-lang.org/communitydaily0.7https://www.lf-lang.org/downloaddaily0.7https://www.lf-lang.org/emptydaily0.7https://www.lf-lang.org/daily0.7https://www.lf-lang.org/publications-and-presentationsdaily0.7https://www.lf-lang.org/docs/daily0.7https://www.lf-lang.org/docs/handbook/daily0.7 \ No newline at end of file +https://www.lf-lang.org/docs/handbook/contributingdaily0.7https://www.lf-lang.org/docs/handbook/eclipse-oomphdaily0.7https://www.lf-lang.org/docs/handbook/intellijdaily0.7https://www.lf-lang.org/docs/handbook/regression-testsdaily0.7https://www.lf-lang.org/docs/handbook/running-benchmarksdaily0.7https://www.lf-lang.org/docs/handbook/website-developmentdaily0.7https://www.lf-lang.org/docs/handbook/arduinodaily0.7https://www.lf-lang.org/docs/handbook/proof-importdaily0.7https://www.lf-lang.org/docs/handbook/logical-execution-timedaily0.7https://www.lf-lang.org/docs/handbook/developer-setupdaily0.7https://www.lf-lang.org/docs/handbook/zephyrdaily0.7https://www.lf-lang.org/docs/handbook/related-workdaily0.7https://www.lf-lang.org/docs/handbook/timing-analysisdaily0.7https://www.lf-lang.org/docs/handbook/toolsdaily0.7https://www.lf-lang.org/docs/handbook/language-specificationdaily0.7https://www.lf-lang.org/docs/handbook/generic-types-interfaces-inheritancedaily0.7https://www.lf-lang.org/docs/handbook/import-systemdaily0.7https://www.lf-lang.org/docs/handbook/reactors-on-patmosdaily0.7https://www.lf-lang.org/docs/handbook/featuresdaily0.7https://www.lf-lang.org/docs/handbook/containerized-executiondaily0.7https://www.lf-lang.org/docs/handbook/expressionsdaily0.7https://www.lf-lang.org/docs/handbook/securitydaily0.7https://www.lf-lang.org/docs/handbook/tracingdaily0.7https://www.lf-lang.org/docs/handbook/target-declarationdaily0.7https://www.lf-lang.org/docs/handbook/code-extensiondaily0.7https://www.lf-lang.org/docs/handbook/a-first-reactordaily0.7https://www.lf-lang.org/docs/handbook/actionsdaily0.7https://www.lf-lang.org/docs/handbook/causality-loopsdaily0.7https://www.lf-lang.org/docs/handbook/composing-reactorsdaily0.7https://www.lf-lang.org/docs/handbook/deadlinesdaily0.7https://www.lf-lang.org/docs/handbook/distributed-executiondaily0.7https://www.lf-lang.org/docs/handbook/extending-reactorsdaily0.7https://www.lf-lang.org/docs/handbook/genericsdaily0.7https://www.lf-lang.org/docs/handbook/inputs-and-outputsdaily0.7https://www.lf-lang.org/docs/handbook/methodsdaily0.7https://www.lf-lang.org/docs/handbook/modal-modelsdaily0.7https://www.lf-lang.org/docs/handbook/command-line-toolsdaily0.7https://www.lf-lang.org/docs/handbook/epoch-idedaily0.7https://www.lf-lang.org/docs/handbook/troubleshootingdaily0.7https://www.lf-lang.org/docs/handbook/multiports-and-banksdaily0.7https://www.lf-lang.org/docs/handbook/overviewdaily0.7https://www.lf-lang.org/docs/handbook/parameters-and-state-variablesdaily0.7https://www.lf-lang.org/docs/handbook/preamblesdaily0.7https://www.lf-lang.org/docs/handbook/reaction-declarationsdaily0.7https://www.lf-lang.org/docs/handbook/reactionsdaily0.7https://www.lf-lang.org/docs/handbook/superdense-timedaily0.7https://www.lf-lang.org/docs/handbook/terminationdaily0.7https://www.lf-lang.org/docs/handbook/time-and-timersdaily0.7https://www.lf-lang.org/docs/handbook/tutorial-videodaily0.7https://www.lf-lang.org/docs/handbook/target-language-detailsdaily0.7https://www.lf-lang.org/communitydaily0.7https://www.lf-lang.org/downloaddaily0.7https://www.lf-lang.org/emptydaily0.7https://www.lf-lang.org/daily0.7https://www.lf-lang.org/publications-and-presentationsdaily0.7https://www.lf-lang.org/docs/daily0.7https://www.lf-lang.org/docs/handbook/daily0.7 \ No newline at end of file diff --git a/webpack-runtime-6b3dd33249beab12ce1c.js b/webpack-runtime-587676fc722d4109d293.js similarity index 96% rename from webpack-runtime-6b3dd33249beab12ce1c.js rename to webpack-runtime-587676fc722d4109d293.js index 35dae3efe..ff0e0e10e 100644 --- a/webpack-runtime-6b3dd33249beab12ce1c.js +++ b/webpack-runtime-587676fc722d4109d293.js @@ -1,2 +1,2 @@ -!function(){"use strict";var e,t,n,r,o,a={},i={};function c(e){var t=i[e];if(void 0!==t)return t.exports;var n=i[e]={exports:{}};return a[e](n,n.exports,c),n.exports}c.m=a,e=[],c.O=function(t,n,r,o){if(!n){var a=1/0;for(f=0;f=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[u])}))?n.splice(u--,1):(i=!1,o0&&e[f-1][2]>o;f--)e[f]=e[f-1];e[f]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return{56:"component---src-templates-pages-index-tsx",175:"component---src-templates-pages-docs-handbook-index-tsx",208:"90c0253ea39b81a5e49d137436e825ead0ef33a7",248:"component---src-templates-pages-download-tsx",306:"component---src-templates-pages-empty-tsx",351:"commons",517:"component---src-templates-documentation-tsx",532:"styles",533:"component---src-templates-pages-community-tsx",542:"component---src-templates-pages-publications-and-presentations-tsx",616:"component---src-templates-pages-docs-index-tsx"}[e]+"-"+{56:"0a7639f51e33385eb8f9",175:"4aa0b654197d1d5fae53",208:"7299e993a3830cab1f9e",248:"581d01e80dae64748c16",306:"167cd7a4c2900b63ac14",351:"530c9aa67c6d41969a85",517:"69662a933b3589c31818",532:"9fa542d257a161f411e7",533:"311a2a5f18f0af45971a",542:"f09ec94dd1ed2f74a804",616:"b914ffbd927f75fc8fee"}[e]+".js"},c.miniCssF=function(e){return"styles.8b00b2cda1c9b4bc348f.css"},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t={},n="lingua-franca:",c.l=function(e,r,o,a){if(t[e])t[e].push(r);else{var i,u;if(void 0!==o)for(var s=document.getElementsByTagName("script"),f=0;f=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[u])}))?n.splice(u--,1):(i=!1,o0&&e[f-1][2]>o;f--)e[f]=e[f-1];e[f]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return{56:"component---src-templates-pages-index-tsx",175:"component---src-templates-pages-docs-handbook-index-tsx",208:"90c0253ea39b81a5e49d137436e825ead0ef33a7",248:"component---src-templates-pages-download-tsx",306:"component---src-templates-pages-empty-tsx",351:"commons",517:"component---src-templates-documentation-tsx",532:"styles",533:"component---src-templates-pages-community-tsx",542:"component---src-templates-pages-publications-and-presentations-tsx",616:"component---src-templates-pages-docs-index-tsx"}[e]+"-"+{56:"0a7639f51e33385eb8f9",175:"4aa0b654197d1d5fae53",208:"7299e993a3830cab1f9e",248:"0fc7218df25b40189519",306:"167cd7a4c2900b63ac14",351:"b82d559d459fdf48f9b7",517:"69662a933b3589c31818",532:"9fa542d257a161f411e7",533:"311a2a5f18f0af45971a",542:"f09ec94dd1ed2f74a804",616:"b914ffbd927f75fc8fee"}[e]+".js"},c.miniCssF=function(e){return"styles.8b00b2cda1c9b4bc348f.css"},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t={},n="lingua-franca:",c.l=function(e,r,o,a){if(t[e])t[e].push(r);else{var i,u;if(void 0!==o)for(var s=document.getElementsByTagName("script"),f=0;f 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"lingua-franca:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = function(url, done, key, chunkId) {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 120;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = function(prev, event) {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach(function(fn) { return fn(event); });\n\t\tif(prev) return prev(event);\n\t}\n\t;\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","var createStylesheet = function(chunkId, fullhref, resolve, reject) {\n\tvar linkTag = document.createElement(\"link\");\n\n\tlinkTag.rel = \"stylesheet\";\n\tlinkTag.type = \"text/css\";\n\tvar onLinkComplete = function(event) {\n\t\t// avoid mem leaks.\n\t\tlinkTag.onerror = linkTag.onload = null;\n\t\tif (event.type === 'load') {\n\t\t\tresolve();\n\t\t} else {\n\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\tvar realHref = event && event.target && event.target.href || fullhref;\n\t\t\tvar err = new Error(\"Loading CSS chunk \" + chunkId + \" failed.\\n(\" + realHref + \")\");\n\t\t\terr.code = \"CSS_CHUNK_LOAD_FAILED\";\n\t\t\terr.type = errorType;\n\t\t\terr.request = realHref;\n\t\t\tlinkTag.parentNode.removeChild(linkTag)\n\t\t\treject(err);\n\t\t}\n\t}\n\tlinkTag.onerror = linkTag.onload = onLinkComplete;\n\tlinkTag.href = fullhref;\n\n\tdocument.head.appendChild(linkTag);\n\treturn linkTag;\n};\nvar findStylesheet = function(href, fullhref) {\n\tvar existingLinkTags = document.getElementsByTagName(\"link\");\n\tfor(var i = 0; i < existingLinkTags.length; i++) {\n\t\tvar tag = existingLinkTags[i];\n\t\tvar dataHref = tag.getAttribute(\"data-href\") || tag.getAttribute(\"href\");\n\t\tif(tag.rel === \"stylesheet\" && (dataHref === href || dataHref === fullhref)) return tag;\n\t}\n\tvar existingStyleTags = document.getElementsByTagName(\"style\");\n\tfor(var i = 0; i < existingStyleTags.length; i++) {\n\t\tvar tag = existingStyleTags[i];\n\t\tvar dataHref = tag.getAttribute(\"data-href\");\n\t\tif(dataHref === href || dataHref === fullhref) return tag;\n\t}\n};\nvar loadStylesheet = function(chunkId) {\n\treturn new Promise(function(resolve, reject) {\n\t\tvar href = __webpack_require__.miniCssF(chunkId);\n\t\tvar fullhref = __webpack_require__.p + href;\n\t\tif(findStylesheet(href, fullhref)) return resolve();\n\t\tcreateStylesheet(chunkId, fullhref, resolve, reject);\n\t});\n}\n// object to store loaded CSS chunks\nvar installedCssChunks = {\n\t658: 0\n};\n\n__webpack_require__.f.miniCss = function(chunkId, promises) {\n\tvar cssChunks = {\"532\":1};\n\tif(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);\n\telse if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {\n\t\tpromises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(function() {\n\t\t\tinstalledCssChunks[chunkId] = 0;\n\t\t}, function(e) {\n\t\t\tdelete installedCssChunks[chunkId];\n\t\t\tthrow e;\n\t\t}));\n\t}\n};\n\n// no hmr","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = function(chunkId) {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + {\"56\":\"component---src-templates-pages-index-tsx\",\"175\":\"component---src-templates-pages-docs-handbook-index-tsx\",\"208\":\"90c0253ea39b81a5e49d137436e825ead0ef33a7\",\"248\":\"component---src-templates-pages-download-tsx\",\"306\":\"component---src-templates-pages-empty-tsx\",\"351\":\"commons\",\"517\":\"component---src-templates-documentation-tsx\",\"532\":\"styles\",\"533\":\"component---src-templates-pages-community-tsx\",\"542\":\"component---src-templates-pages-publications-and-presentations-tsx\",\"616\":\"component---src-templates-pages-docs-index-tsx\"}[chunkId] + \"-\" + {\"56\":\"0a7639f51e33385eb8f9\",\"175\":\"4aa0b654197d1d5fae53\",\"208\":\"7299e993a3830cab1f9e\",\"248\":\"581d01e80dae64748c16\",\"306\":\"167cd7a4c2900b63ac14\",\"351\":\"530c9aa67c6d41969a85\",\"517\":\"69662a933b3589c31818\",\"532\":\"9fa542d257a161f411e7\",\"533\":\"311a2a5f18f0af45971a\",\"542\":\"f09ec94dd1ed2f74a804\",\"616\":\"b914ffbd927f75fc8fee\"}[chunkId] + \".js\";\n};","// This function allow to reference all chunks\n__webpack_require__.miniCssF = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + \"styles\" + \".\" + \"8b00b2cda1c9b4bc348f\" + \".css\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"/\";","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t658: 0\n};\n\n__webpack_require__.f.j = function(chunkId, promises) {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(!/^(532|658)$/.test(chunkId)) {\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = function(event) {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t} else installedChunks[chunkId] = 0;\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunklingua_franca\"] = self[\"webpackChunklingua_franca\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"],"names":["deferred","inProgress","dataWebpackPrefix","loadStylesheet","installedCssChunks","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","length","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","f","e","chunkId","Promise","all","reduce","promises","u","miniCssF","g","globalThis","this","Function","window","obj","prop","prototype","hasOwnProperty","call","l","url","done","push","script","needAttach","scripts","document","getElementsByTagName","s","getAttribute","createElement","charset","timeout","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","clearTimeout","doneFns","parentNode","removeChild","forEach","setTimeout","bind","type","target","head","appendChild","Symbol","toStringTag","value","p","resolve","reject","href","fullhref","existingLinkTags","dataHref","tag","rel","existingStyleTags","findStylesheet","linkTag","errorType","realHref","err","Error","code","request","createStylesheet","miniCss","then","installedChunks","installedChunkData","test","promise","error","realSrc","message","name","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","id","chunkLoadingGlobal","self"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"webpack-runtime-587676fc722d4109d293.js","mappings":"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","sources":["webpack://lingua-franca/webpack/runtime/chunk loaded","webpack://lingua-franca/webpack/runtime/load script","webpack://lingua-franca/webpack/runtime/css loading","webpack://lingua-franca/webpack/bootstrap","webpack://lingua-franca/webpack/runtime/compat get default export","webpack://lingua-franca/webpack/runtime/define property getters","webpack://lingua-franca/webpack/runtime/ensure chunk","webpack://lingua-franca/webpack/runtime/get javascript chunk filename","webpack://lingua-franca/webpack/runtime/get mini-css chunk filename","webpack://lingua-franca/webpack/runtime/global","webpack://lingua-franca/webpack/runtime/hasOwnProperty shorthand","webpack://lingua-franca/webpack/runtime/make namespace object","webpack://lingua-franca/webpack/runtime/publicPath","webpack://lingua-franca/webpack/runtime/jsonp chunk loading"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"lingua-franca:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = function(url, done, key, chunkId) {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 120;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = function(prev, event) {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach(function(fn) { return fn(event); });\n\t\tif(prev) return prev(event);\n\t}\n\t;\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","var createStylesheet = function(chunkId, fullhref, resolve, reject) {\n\tvar linkTag = document.createElement(\"link\");\n\n\tlinkTag.rel = \"stylesheet\";\n\tlinkTag.type = \"text/css\";\n\tvar onLinkComplete = function(event) {\n\t\t// avoid mem leaks.\n\t\tlinkTag.onerror = linkTag.onload = null;\n\t\tif (event.type === 'load') {\n\t\t\tresolve();\n\t\t} else {\n\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\tvar realHref = event && event.target && event.target.href || fullhref;\n\t\t\tvar err = new Error(\"Loading CSS chunk \" + chunkId + \" failed.\\n(\" + realHref + \")\");\n\t\t\terr.code = \"CSS_CHUNK_LOAD_FAILED\";\n\t\t\terr.type = errorType;\n\t\t\terr.request = realHref;\n\t\t\tlinkTag.parentNode.removeChild(linkTag)\n\t\t\treject(err);\n\t\t}\n\t}\n\tlinkTag.onerror = linkTag.onload = onLinkComplete;\n\tlinkTag.href = fullhref;\n\n\tdocument.head.appendChild(linkTag);\n\treturn linkTag;\n};\nvar findStylesheet = function(href, fullhref) {\n\tvar existingLinkTags = document.getElementsByTagName(\"link\");\n\tfor(var i = 0; i < existingLinkTags.length; i++) {\n\t\tvar tag = existingLinkTags[i];\n\t\tvar dataHref = tag.getAttribute(\"data-href\") || tag.getAttribute(\"href\");\n\t\tif(tag.rel === \"stylesheet\" && (dataHref === href || dataHref === fullhref)) return tag;\n\t}\n\tvar existingStyleTags = document.getElementsByTagName(\"style\");\n\tfor(var i = 0; i < existingStyleTags.length; i++) {\n\t\tvar tag = existingStyleTags[i];\n\t\tvar dataHref = tag.getAttribute(\"data-href\");\n\t\tif(dataHref === href || dataHref === fullhref) return tag;\n\t}\n};\nvar loadStylesheet = function(chunkId) {\n\treturn new Promise(function(resolve, reject) {\n\t\tvar href = __webpack_require__.miniCssF(chunkId);\n\t\tvar fullhref = __webpack_require__.p + href;\n\t\tif(findStylesheet(href, fullhref)) return resolve();\n\t\tcreateStylesheet(chunkId, fullhref, resolve, reject);\n\t});\n}\n// object to store loaded CSS chunks\nvar installedCssChunks = {\n\t658: 0\n};\n\n__webpack_require__.f.miniCss = function(chunkId, promises) {\n\tvar cssChunks = {\"532\":1};\n\tif(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);\n\telse if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {\n\t\tpromises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(function() {\n\t\t\tinstalledCssChunks[chunkId] = 0;\n\t\t}, function(e) {\n\t\t\tdelete installedCssChunks[chunkId];\n\t\t\tthrow e;\n\t\t}));\n\t}\n};\n\n// no hmr","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = function(chunkId) {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce(function(promises, key) {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + {\"56\":\"component---src-templates-pages-index-tsx\",\"175\":\"component---src-templates-pages-docs-handbook-index-tsx\",\"208\":\"90c0253ea39b81a5e49d137436e825ead0ef33a7\",\"248\":\"component---src-templates-pages-download-tsx\",\"306\":\"component---src-templates-pages-empty-tsx\",\"351\":\"commons\",\"517\":\"component---src-templates-documentation-tsx\",\"532\":\"styles\",\"533\":\"component---src-templates-pages-community-tsx\",\"542\":\"component---src-templates-pages-publications-and-presentations-tsx\",\"616\":\"component---src-templates-pages-docs-index-tsx\"}[chunkId] + \"-\" + {\"56\":\"0a7639f51e33385eb8f9\",\"175\":\"4aa0b654197d1d5fae53\",\"208\":\"7299e993a3830cab1f9e\",\"248\":\"0fc7218df25b40189519\",\"306\":\"167cd7a4c2900b63ac14\",\"351\":\"b82d559d459fdf48f9b7\",\"517\":\"69662a933b3589c31818\",\"532\":\"9fa542d257a161f411e7\",\"533\":\"311a2a5f18f0af45971a\",\"542\":\"f09ec94dd1ed2f74a804\",\"616\":\"b914ffbd927f75fc8fee\"}[chunkId] + \".js\";\n};","// This function allow to reference all chunks\n__webpack_require__.miniCssF = function(chunkId) {\n\t// return url for filenames based on template\n\treturn \"\" + \"styles\" + \".\" + \"8b00b2cda1c9b4bc348f\" + \".css\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.p = \"/\";","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t658: 0\n};\n\n__webpack_require__.f.j = function(chunkId, promises) {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(!/^(532|658)$/.test(chunkId)) {\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; });\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = function(event) {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t} else installedChunks[chunkId] = 0;\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunklingua_franca\"] = self[\"webpackChunklingua_franca\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"],"names":["deferred","inProgress","dataWebpackPrefix","loadStylesheet","installedCssChunks","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","length","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","f","e","chunkId","Promise","all","reduce","promises","u","miniCssF","g","globalThis","this","Function","window","obj","prop","prototype","hasOwnProperty","call","l","url","done","push","script","needAttach","scripts","document","getElementsByTagName","s","getAttribute","createElement","charset","timeout","nc","setAttribute","src","onScriptComplete","prev","event","onerror","onload","clearTimeout","doneFns","parentNode","removeChild","forEach","setTimeout","bind","type","target","head","appendChild","Symbol","toStringTag","value","p","resolve","reject","href","fullhref","existingLinkTags","dataHref","tag","rel","existingStyleTags","findStylesheet","linkTag","errorType","realHref","err","Error","code","request","createStylesheet","miniCss","then","installedChunks","installedChunkData","test","promise","error","realSrc","message","name","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","id","chunkLoadingGlobal","self"],"sourceRoot":""} \ No newline at end of file diff --git a/webpack.stats.json b/webpack.stats.json index a3f8341bc..747cdefe4 100644 --- a/webpack.stats.json +++ b/webpack.stats.json @@ -1 +1 @@ -{"name":"build-javascript","namedChunkGroups":{"polyfill":{"name":"polyfill","assets":[{"name":"webpack-runtime-6b3dd33249beab12ce1c.js","size":4959},{"name":"polyfill-8093e63a736b03ae8dca.js","size":85019}],"filteredAssets":0,"assetsSize":89978,"filteredAuxiliaryAssets":2,"auxiliaryAssetsSize":224667},"app":{"name":"app","assets":[{"name":"webpack-runtime-6b3dd33249beab12ce1c.js","size":4959},{"name":"framework-fffeb206b88cc7e067cd.js","size":140366},{"name":"app-91a8e178c6b8a94bf17c.js","size":153277}],"filteredAssets":0,"assetsSize":298602,"filteredAuxiliaryAssets":3,"auxiliaryAssetsSize":969241},"component---src-templates-documentation-tsx":{"name":"component---src-templates-documentation-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","size":7108},{"name":"component---src-templates-documentation-tsx-69662a933b3589c31818.js","size":62342}],"filteredAssets":0,"assetsSize":242933,"filteredAuxiliaryAssets":5,"auxiliaryAssetsSize":523587},"component---src-templates-pages-community-tsx":{"name":"component---src-templates-pages-community-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js","size":9305}],"filteredAssets":0,"assetsSize":182788,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":407008},"component---src-templates-pages-docs-handbook-index-tsx":{"name":"component---src-templates-pages-docs-handbook-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js","size":7482}],"filteredAssets":0,"assetsSize":180965,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":408330},"component---src-templates-pages-docs-index-tsx":{"name":"component---src-templates-pages-docs-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js","size":9369}],"filteredAssets":0,"assetsSize":182852,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":412739},"component---src-templates-pages-download-tsx":{"name":"component---src-templates-pages-download-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-download-tsx-581d01e80dae64748c16.js","size":9104}],"filteredAssets":0,"assetsSize":182587,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":402501},"component---src-templates-pages-empty-tsx":{"name":"component---src-templates-pages-empty-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js","size":615}],"filteredAssets":0,"assetsSize":174098,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":390858},"component---src-templates-pages-index-tsx":{"name":"component---src-templates-pages-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","size":7108},{"name":"component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js","size":7073}],"filteredAssets":0,"assetsSize":187664,"filteredAuxiliaryAssets":6,"auxiliaryAssetsSize":500688},"component---src-templates-pages-publications-and-presentations-tsx":{"name":"component---src-templates-pages-publications-and-presentations-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-530c9aa67c6d41969a85.js","size":108820},{"name":"component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js","size":17116}],"filteredAssets":0,"assetsSize":190599,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":412437}},"assetsByChunkName":{"polyfill":["webpack-runtime-6b3dd33249beab12ce1c.js","polyfill-8093e63a736b03ae8dca.js"],"app":["webpack-runtime-6b3dd33249beab12ce1c.js","framework-fffeb206b88cc7e067cd.js","app-91a8e178c6b8a94bf17c.js"],"component---src-templates-documentation-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","component---src-templates-documentation-tsx-69662a933b3589c31818.js"],"component---src-templates-pages-community-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js"],"component---src-templates-pages-docs-handbook-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js"],"component---src-templates-pages-docs-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js"],"component---src-templates-pages-download-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-download-tsx-581d01e80dae64748c16.js"],"component---src-templates-pages-empty-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js"],"component---src-templates-pages-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js"],"component---src-templates-pages-publications-and-presentations-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-530c9aa67c6d41969a85.js","component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js"]},"childAssetsByChunkName":{}} \ No newline at end of file +{"name":"build-javascript","namedChunkGroups":{"polyfill":{"name":"polyfill","assets":[{"name":"webpack-runtime-587676fc722d4109d293.js","size":4959},{"name":"polyfill-8093e63a736b03ae8dca.js","size":85019}],"filteredAssets":0,"assetsSize":89978,"filteredAuxiliaryAssets":2,"auxiliaryAssetsSize":224667},"app":{"name":"app","assets":[{"name":"webpack-runtime-587676fc722d4109d293.js","size":4959},{"name":"framework-fffeb206b88cc7e067cd.js","size":140366},{"name":"app-91a8e178c6b8a94bf17c.js","size":153277}],"filteredAssets":0,"assetsSize":298602,"filteredAuxiliaryAssets":3,"auxiliaryAssetsSize":969241},"component---src-templates-documentation-tsx":{"name":"component---src-templates-documentation-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","size":7108},{"name":"component---src-templates-documentation-tsx-69662a933b3589c31818.js","size":62342}],"filteredAssets":0,"assetsSize":242933,"filteredAuxiliaryAssets":5,"auxiliaryAssetsSize":523587},"component---src-templates-pages-community-tsx":{"name":"component---src-templates-pages-community-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js","size":9305}],"filteredAssets":0,"assetsSize":182788,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":407008},"component---src-templates-pages-docs-handbook-index-tsx":{"name":"component---src-templates-pages-docs-handbook-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js","size":7482}],"filteredAssets":0,"assetsSize":180965,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":408330},"component---src-templates-pages-docs-index-tsx":{"name":"component---src-templates-pages-docs-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js","size":9369}],"filteredAssets":0,"assetsSize":182852,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":412739},"component---src-templates-pages-download-tsx":{"name":"component---src-templates-pages-download-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-download-tsx-0fc7218df25b40189519.js","size":9528}],"filteredAssets":0,"assetsSize":183011,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":403037},"component---src-templates-pages-empty-tsx":{"name":"component---src-templates-pages-empty-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js","size":615}],"filteredAssets":0,"assetsSize":174098,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":390858},"component---src-templates-pages-index-tsx":{"name":"component---src-templates-pages-index-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","size":7108},{"name":"component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js","size":7073}],"filteredAssets":0,"assetsSize":187664,"filteredAuxiliaryAssets":6,"auxiliaryAssetsSize":500688},"component---src-templates-pages-publications-and-presentations-tsx":{"name":"component---src-templates-pages-publications-and-presentations-tsx","assets":[{"name":"styles.8b00b2cda1c9b4bc348f.css","size":64663},{"name":"commons-b82d559d459fdf48f9b7.js","size":108820},{"name":"component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js","size":17116}],"filteredAssets":0,"assetsSize":190599,"filteredAuxiliaryAssets":4,"auxiliaryAssetsSize":412437}},"assetsByChunkName":{"polyfill":["webpack-runtime-587676fc722d4109d293.js","polyfill-8093e63a736b03ae8dca.js"],"app":["webpack-runtime-587676fc722d4109d293.js","framework-fffeb206b88cc7e067cd.js","app-91a8e178c6b8a94bf17c.js"],"component---src-templates-documentation-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","component---src-templates-documentation-tsx-69662a933b3589c31818.js"],"component---src-templates-pages-community-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-community-tsx-311a2a5f18f0af45971a.js"],"component---src-templates-pages-docs-handbook-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-docs-handbook-index-tsx-4aa0b654197d1d5fae53.js"],"component---src-templates-pages-docs-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-docs-index-tsx-b914ffbd927f75fc8fee.js"],"component---src-templates-pages-download-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-download-tsx-0fc7218df25b40189519.js"],"component---src-templates-pages-empty-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-empty-tsx-167cd7a4c2900b63ac14.js"],"component---src-templates-pages-index-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","90c0253ea39b81a5e49d137436e825ead0ef33a7-7299e993a3830cab1f9e.js","component---src-templates-pages-index-tsx-0a7639f51e33385eb8f9.js"],"component---src-templates-pages-publications-and-presentations-tsx":["styles.8b00b2cda1c9b4bc348f.css","commons-b82d559d459fdf48f9b7.js","component---src-templates-pages-publications-and-presentations-tsx-f09ec94dd1ed2f74a804.js"]},"childAssetsByChunkName":{}} \ No newline at end of file