From 66b1b0266e9b2e770c716c6b9cdeaf62f7f49c37 Mon Sep 17 00:00:00 2001 From: xrstf Date: Wed, 3 Jan 2024 18:25:27 +0100 Subject: [PATCH] allow more than one expression in user-defined functions (automatically assume a "(do...)") --- cmd/rudi/docs/data/functions/func.md.gz | Bin 1664 -> 1744 bytes docs/stdlib/rudifunc/func.md | 11 +++++++---- pkg/builtin/rudifunc/docs/func.md | 11 +++++++---- pkg/builtin/rudifunc/functions.go | 20 +++++++++++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/cmd/rudi/docs/data/functions/func.md.gz b/cmd/rudi/docs/data/functions/func.md.gz index 698ae61738ec27ff36b5f7d72fa6b187597ebc87..3f433a9ad59f316ffe1b5f9961c5438fb4b6cf13 100644 GIT binary patch literal 1744 zcmV;>1~2&^iwFP!00000|IAsvVlm5B*a$G6uK1XPl{f8Xw#G7!;l<` zTQ7Iz{gKQD0rDV44~5YO@uTEPidj+=MUj#c%RzKXq}}~yXJ)_oX8if-@fAEhe);_L z_>MonnM3MT{JQ$?)y|Kz`KL_Su)qneAVvidfwz=^(~wXq6oI03y~F}KFkU&4(-cnm zbm4xOUtGXHl*N?buiDWmwh0TdR#qfrC;?w?jwnWN34{ZbQm-wzj373IVUJE>Y4pNi zZb6DWn)lhZ`DgNhq36TWWvSM1a|^==&1iJeWl#K6xIM5DngE+>{gYs|J}XW)Y%99! zOvD+ig_NLZMFt$1TqtxjhR6`!3AF&Nh=ceypNKb>W0?y}b_&0*;;vJ0nXu!=(N`aK zGrof3{88HNI$~!9DU2i~5EY#G=%oWx2`Hk(@@t3)HcPx^<90D8lk(RIgWDGPIwMu> z;soXUfKZOi(vTwq+H{%}6(CeBeX`lO3u$~J7_9}C>%>bcF)5EI3EMZA8nnyEz@IhX zaw$a|o{HUj%>D1y;-c%t_BlShMzVlAL^6gl0h*K}6u)AyKC*OAo|FUY&{0mxS^kw> zU@@;8*d4~|!B631&1xGu5EdfsRp2(^EG*Fkj94#e47MUXb{1}VXDwXDhN;$7McTS& zUtZN$fK(fx2h5io9Zvil?yNsc}f9=Hq}t{8_zUPv9Gl*P2~f&H;i zn2VU#R7{>UXgo&6r|1=#wGocO@qMeuAf_~hLi^3W{%2Gb4TRWRr}LKAVy$9yEzi}r z?|N6AZ#g~XSd@k^DS#e?bryzY!U+qRSY)z*1DD`pA&W6A#EMkue&OD54wq%e3+X08 z)s~N@aMazL<{NiAQk}R%L)G39xJ>&+c3P2rE*bw_sF<4R-+ZdgJUU4TZp{g`|K>501SeEaZgB^5xBBKN(YPazGeZA5AK64Yu66#-gLvB)Z#* zI!O{u`$goXGBy;VKb2H!kEpo3sMz5~3hQoRSp9b}8Nelf^`j!IPfl$C2Ui0aTn}I{ z8{CfI0t!Jgu`sxrTu)|$=lY{Zw2BCB)a?%*ejK+A3osNZ45g#r!KH$$b1TVSSb%qD zhB^^8+?Vddb+6BS-2<=ySLek#6?XU*hMZNyNg3c#^S{@l@Oc(&i&D5FEXljntfg?* zODA&iFD5c=U`d8q3^xpEbxTJ@dPTf-MWz5N^*SsIRNGVY@4blcB+G>ixq8_pR)?3( z?qAX%fVdxM!jD0ye^TrKSyI;qG~`qe-F%f!SfQ*VOt?1G^_Sb(?`*Tb)60QCKWdZP zu3vvUzqq()6@ZFf6y`J+2`N_=$R%>Eov$c%+CZd}V!kbL-@g8Dw0kam`_I2y2P-dS zr;YFD-@g90y_hT1&YGZ36f{g)h}EWF+DaH_!`|<4W=FWXOe#IlVH8wOsT4s7Y;9#F zPcki&U}-od^nz?=UnM=!Bo4#AL7|y8kZVIt8B!V&G{KI`_>dFab!c|OXND(N_JD0& z$A$J_QKsNij1fp)*& zv*S>O`X))GFl|p8&-gi_0fmZ#1{}WKRP6KO?!j7eui+BUul^3gz6jQF*de?35*vd4 z`e(piV)c6sdtN>G>pA)L3wrsOlV28guUdw`vUE(2LbY)jg_2X%ODXPHmlo9^caNMv zs6gT4=D@L&sF>|arQE~FVsBK^oYw}%+})7la(BZI<=7*p9ydzn;!F4^WouK*Zx?PBG-EoRMmRyk2e7o+V?kI?&M1csb93RWmR zl_z-1we$Joo&$H^tJvi*v9zTv{2Dz(wf-!lsNT-$59P4!#9?Z4kSc{E6NJjqk!srm mt(j^g`rFWV#&MGmFEXCnan#e}m;VO<0RR7yWIb_#9smGomt9H# literal 1664 zcmV-`27mb7q?nh8IKf zP(1Z;MxGzZY!ILiQgl)HN&F~zlHv?0iK0kJj^!ZQB(`SeJ9B=%^PTa>hx-@saR2G! z!~Gln{AUWOSMkg0cQ3ZSoJ>DtWWxeST0txnLfq!h!M1QI2Cc z=F^7zZhCeGzbPwHgrBv;V{9V}u~wE6G?;)dH%Ax?ZxP6WkWw!#xC|jSD8e3{!rbVY z5xE6PH#qIGZS~*e9Yat1rOQ&S;p!UtkuZb7QI|dPQ{i^NM#2QxRO_zaYJebS|JDV7oX58%dyPKq8-EktGMeFTt;?SJNn|? zcE(q5oIgstZAWaaKqVrH2}A`azVOn4Pzfl+#Bw!62&*ODvT@s(qfz+a?Ej z%Pn~+3cD_EuB4Fdkx(;cDM|@VdjR?elVC804}2D8VnN!QRiD^&{17(o5(lki%d~eb z#*qcDs*sGkIq`?Gs_g+mkr!2>UP-d<8v50by-^R&`Bz`s@4Gtt2C#S0gWhEidXwJu z0M4Kwl!=Aj#prT0={+@>I`k7oaHX!l@bJU9O;~_FrO=lS{|Dy^E>10KI$;6coEYjz z*l=IG4cEOq@pbpW0$iLH>sZ+SYv^-U^+#oZ2lXE>2jTN1*ai*pa8mL}DW6IWsppPz z`hy8x8waEoDC<{ib8!bK33*@Z}GzD0xmtk2D03Ge;)>C{VxpQTLiPX+Tb$B^< z{gj4Y%+j+C`Q@l{%3Nm+*?VJ2O-0-mfMIq^Nx%Yi`OYm?iy zZ~r?zJ3DJIcttNt|MfQB!bnv0`!{*xBW;iX_)z6p~y~CFgn&n4zn^Fmz6x4autYPUJ)P*q7szsqjwcYwi!j=FYjz z(P}e*3JXh=a=6j$Vf5YzZee!^Av+9Z+V#Xz%H-xV+;bFNyCNkO2SwU{z0wb-#od9m z=w8CPIK39#3;Qfshhh8d-g8kKFD}0Z?767EU&EeOoql^xe))`EKIG)*h280v;m;2( zPb#6?xC}$fsrsd)8&=6h^}@MBPCzP9_^|#G*Gg2(cBO9bU}V~PL#RJw2FBdpkc;{D zhA+sUZ;jmePFELj36pCEuVv)bB~0M$6DR);4bbv_V>9h#QvDralMkH&V933TrTC_q z)gOAwr)J?|VcXLqOkx>=LFbKvg^(W06TIc#`E)J0=k7ZlyL@FUZD|U>+E7!iKZ(xm zoa4UJ%Hf?m@|Cgv!YIk12}0%ITVvA$jmO4X^f#ezjpHgGPOklT9QAPj>E8eV0RR8# KA7Lh%9RL98@iqPc diff --git a/docs/stdlib/rudifunc/func.md b/docs/stdlib/rudifunc/func.md index ab758c1..9ddfe92 100644 --- a/docs/stdlib/rudifunc/func.md +++ b/docs/stdlib/rudifunc/func.md @@ -23,7 +23,7 @@ embedding Rudi into other Go applications. `func!` creates a new function, which can be used in all subseqeuent statements in the same Rudi program. Function creation is bound to its parent scope, so defining a function within an `if` statement for example will make the function -available onside inside that statement, not globally. +available only inside that statement, not globally. Dynamically defined functions cannot overwrite statically defined functions, i.e. you cannot define a custom `concat` function using `func!` if `concat` already @@ -34,6 +34,10 @@ Since defining a new function is a side effect, `func` must always be used with the bang modifier (`func!`). The behaviour of Rudi programs that use `func` without the bang modifier is undefined. +Functions can contain an arbitrary number of expressions, which will be evaluated +in sequence and share a single context. This means user-defined functions form +a "sub-program" the same way the [`do`](core-do.md) function does. + ## Examples ``` @@ -56,12 +60,11 @@ inject a Go function statically into Rudi instead of defining it at runtime. ## Forms -### `(func! name:identifier params:vector body:expression)` ➜ `null` +### `(func! name:identifier params:vector body:expression…)` ➜ `null` * `name` is an identifier giving the function its name. * `params` is a vector containing identifiers that hold the parameter names. -* `body` is a single expression (use `do` for multiple statements) that forms - the function body. +* `body` is one or more expressions that form the function body. This form will create a new function called `name` with as many parameters as `params` has identifiers. `params` can be empty, but must otherwise contain only diff --git a/pkg/builtin/rudifunc/docs/func.md b/pkg/builtin/rudifunc/docs/func.md index ab758c1..9ddfe92 100644 --- a/pkg/builtin/rudifunc/docs/func.md +++ b/pkg/builtin/rudifunc/docs/func.md @@ -23,7 +23,7 @@ embedding Rudi into other Go applications. `func!` creates a new function, which can be used in all subseqeuent statements in the same Rudi program. Function creation is bound to its parent scope, so defining a function within an `if` statement for example will make the function -available onside inside that statement, not globally. +available only inside that statement, not globally. Dynamically defined functions cannot overwrite statically defined functions, i.e. you cannot define a custom `concat` function using `func!` if `concat` already @@ -34,6 +34,10 @@ Since defining a new function is a side effect, `func` must always be used with the bang modifier (`func!`). The behaviour of Rudi programs that use `func` without the bang modifier is undefined. +Functions can contain an arbitrary number of expressions, which will be evaluated +in sequence and share a single context. This means user-defined functions form +a "sub-program" the same way the [`do`](core-do.md) function does. + ## Examples ``` @@ -56,12 +60,11 @@ inject a Go function statically into Rudi instead of defining it at runtime. ## Forms -### `(func! name:identifier params:vector body:expression)` ➜ `null` +### `(func! name:identifier params:vector body:expression…)` ➜ `null` * `name` is an identifier giving the function its name. * `params` is a vector containing identifiers that hold the parameter names. -* `body` is a single expression (use `do` for multiple statements) that forms - the function body. +* `body` is one or more expressions that form the function body. This form will create a new function called `name` with as many parameters as `params` has identifiers. `params` can be empty, but must otherwise contain only diff --git a/pkg/builtin/rudifunc/functions.go b/pkg/builtin/rudifunc/functions.go index af5e8b7..f1bf440 100644 --- a/pkg/builtin/rudifunc/functions.go +++ b/pkg/builtin/rudifunc/functions.go @@ -19,7 +19,7 @@ var ( // funcFunction should never be called without the bang modifier, as without it, the created function // just instantly vanishes into thin air. -func funcFunction(ctx types.Context, name ast.Expression, namingVector ast.Expression, body ast.Expression) (any, error) { +func funcFunction(ctx types.Context, name ast.Expression, namingVector ast.Expression, body ...ast.Expression) (any, error) { nameIdent, ok := name.(ast.Identifier) if !ok { return nil, fmt.Errorf("first argument must be an identifier that specifies the function name, but got %T instead", name) @@ -60,7 +60,7 @@ func funcBangHandler(ctx types.Context, originalArgs []ast.Expression, value any type rudispaceFunc struct { name string params []string - body ast.Expression + body []ast.Expression } var _ types.Function = rudispaceFunc{} @@ -84,7 +84,21 @@ func (f rudispaceFunc) Evaluate(ctx types.Context, args []ast.Expression) (any, funcArgs[paramName] = arg } - _, result, err := ctx.Runtime().EvalExpression(ctx.WithVariables(funcArgs), f.body) + // user-defined functions form a sub-program and all statements share the same context + funcCtx := ctx.WithVariables(funcArgs) + runtime := ctx.Runtime() + + var ( + result any + err error + ) + + for _, expr := range f.body { + funcCtx, result, err = runtime.EvalExpression(funcCtx, expr) + if err != nil { + return nil, err + } + } return result, err }