Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
ego_script [2026/07/29 21:15] hermann |
ego_script [2026/08/31 15:12] (current) hermann |
||
|---|---|---|---|
| Line 88: | Line 88: | ||
| Constants supply a fixed value directly to an input port, without connecting a functor. Despite often looking similar in syntax, every constant is interpreted and validated according to the type of the port it is supplied to — a value that is valid for one port type may be rejected by another. They come in four forms. | Constants supply a fixed value directly to an input port, without connecting a functor. Despite often looking similar in syntax, every constant is interpreted and validated according to the type of the port it is supplied to — a value that is valid for one port type may be rejected by another. They come in four forms. | ||
| - | Automatic type conversion — such as a Real value becoming a Tuple, or a String becoming a Code value — only happens when a value is **connected** from one port to another: a variable reference, or a functor call inlined directly into an argument. It never applies to a constant written directly into a port. A constant is parsed by that port's own type-specific parser, which accepts only its own type's literal syntax and rejects anything else outright, even a literal that some other, compatible type would otherwise accept. See [[basic_data_flow|Basic Data Flow]] for this same distinction stated generally, independent of EGO Script or any other notation. | + | Automatic type conversion — such as a Real value becoming a Tuple, or a String becoming a Code value — only happens when a value is **connected** from one port to another: a variable reference, or a functor call inlined directly into an argument. It never applies to a constant written directly into a port. A constant is parsed by that port's own type-specific parser, which accepts only its own type's literal syntax and rejects anything else outright, even a literal that some other, compatible type would otherwise accept. See [[basic_data_flow|Basic Data Flow]] for this same distinction stated generally, independent of EGO Script or any other notation, and [[type_system|Type System]] for the complete catalog of types and how they convert into one another. |
| **Numeric literals** — integer or real values written directly: ''0'', ''250'', ''3.14''. | **Numeric literals** — integer or real values written directly: ''0'', ''250'', ''3.14''. | ||
| Line 104: | Line 104: | ||
| </code> | </code> | ||
| - | For ''String'' ports that contain line breaks or double-quote characters, use the extended form ''$"DELIMITER(content)DELIMITER"''. The content is everything between the opening ''('' and the closing '')'', and the DELIMITER — any sequence of characters placed between the ''%%"%%'' and the ''('', including empty — makes the closing '')"'' unambiguous. The delimiter must be identical on both sides. This extended form is **only accepted by ''String'' ports**: | + | [[code_type|Code]] ports also accept this basic ''"text"'' form, but interpret it differently from the types above: rather than validating the text itself, the content is decoded as base64 to produce the script — see the note below. |
| + | |||
| + | For ''String'' ports that contain line breaks or double-quote characters, use the extended form ''$"DELIMITER(content)DELIMITER"''. The content is everything between the opening ''('' and the closing '')'', and the DELIMITER — any sequence of characters placed between the ''%%"%%'' and the ''('', including empty — makes the closing '')"'' unambiguous. The delimiter must be identical on both sides. [[code_type|Code]] ports accept this same extended form directly too, using the content as the literal script text with no encoding involved — see [[code_type|Code Type]] for the full grammar. No other port type accepts it: | ||
| <code> | <code> | ||
| Line 156: | Line 158: | ||
| Here ''[ 2->1 0.05 ]'' is a structured constant representing a transition matrix with a 5% rate from class 2 to class 1. | Here ''[ 2->1 0.05 ]'' is a structured constant representing a transition matrix with a 5% rate from class 2 to class 1. | ||
| - | > **Note:** ''Code'' — the type used for expression inputs such as the Python code in [[Calculate Python Expression]] — is not a text constant. Code values are stored as base64 in the underlying script, making them impractical to write directly. When a port expects a ''Code'' value, supply a ''String'' carrier functor containing the expression text; the automatic ''String → Code'' conversion handles the rest. | + | > **Note:** [[code_type|Code]] — the type used for expression inputs such as the Python code in [[calculate_python_expression|Calculate Python Expression]] — has its own literal syntax, parsed directly rather than through a conversion, and accepts two forms: the extended ''$"DELIMITER(content)DELIMITER"'' form described above, taken directly as the script's text with no encoding involved; and the basic double-quoted ''"text"'' form, whose content is instead decoded as base64 to produce the script. See [[code_type|Code Type]] for the full grammar and examples of both forms. |
| ==== Positional syntax ==== | ==== Positional syntax ==== | ||
| Line 221: | Line 223: | ||
| </code> | </code> | ||
| - | Any output can be replaced with ''_'' to discard it when only some outputs are needed. | + | When only some of a functor's outputs are wanted, the rest can simply be left out of the block entirely — nominal syntax binds by name, so there's no position to hold open the way [[#positional_syntax|positional syntax]] requires. An unwanted output can also be bound to ''_'' instead of omitted; this changes nothing about execution, it only makes explicit, at the call site, that the output was considered and deliberately discarded rather than overlooked. |
| So inputs read ''portName = value'' while outputs read ''variable = portName''. The two directions are summarized below: | So inputs read ''portName = value'' while outputs read ''variable = portName''. The two directions are summarized below: | ||
| Line 288: | Line 290: | ||
| * **Loops.** Several containers repeat their contents. [[Repeat]] runs them a fixed number of times; [[For]] iterates over a numeric range; [[For Each]] iterates over the rows of a table; [[For Each Category]] iterates over the categories of a map; [[For Each Region]] iterates over the regions of a map; and [[While]] and [[Do While]] repeat for as long as a condition holds. | * **Loops.** Several containers repeat their contents. [[Repeat]] runs them a fixed number of times; [[For]] iterates over a numeric range; [[For Each]] iterates over the rows of a table; [[For Each Category]] iterates over the categories of a map; [[For Each Region]] iterates over the regions of a map; and [[While]] and [[Do While]] repeat for as long as a condition holds. | ||
| * **Region management.** [[Region Manager]] establishes a region context and runs its contents once for that context, exposing the region to the functors inside it. | * **Region management.** [[Region Manager]] establishes a region context and runs its contents once for that context, exposing the region to the functors inside it. | ||
| - | * **Error handling.** [[Skip On Error]] and [[Skip All On Error]] execute their contained functors while capturing and ignoring any error that occurs. Both accept a ''trapAndIgnoreErrors'' boolean input — when set to false, they behave as a plain ''Group'' and errors are not suppressed. Both also produce an ''executionCompletedSucessfully'' output that can be tested by subsequent functors. The two differ in what happens to results already produced when an error is raised: [[Skip On Error]] preserves the outputs of any functors that completed successfully before the error; [[Skip All On Error]] discards all of them. They are typically paired with a junction outside the container to react to the outcome — see [[#error-handling_pattern|Error-handling pattern]] below. | + | * **Error handling.** [[Skip On Error]] and [[Skip All On Error]] execute their contained functors while capturing and ignoring any error that occurs. Both accept a ''trapAndIgnoreErrors'' boolean input — when set to false, they behave as a plain ''Group'' and errors are not suppressed. Both also produce an ''executionCompletedSuccessfully'' output that can be tested by subsequent functors. The two differ in what happens to results already produced when an error is raised: [[Skip On Error]] preserves the outputs of any functors that completed successfully before the error; [[Skip All On Error]] discards all of them. They are typically paired with a junction outside the container to react to the outcome — see [[#error-handling_pattern|Error-handling pattern]] below. |
| The [[#calculator_functor_shorthand|calculator functors]] described later in this section are also containers: each holds the expression it evaluates, and in its verbose form holds a block of operand definitions. | The [[#calculator_functor_shorthand|calculator functors]] described later in this section are also containers: each holds the expression it evaluates, and in its verbose form holds a block of operand definitions. | ||
| Line 340: | Line 342: | ||
| * ''sequenceOutput'' — connecting this output to another container's ''sequenceInput'' forces this container to complete before the other one starts. | * ''sequenceOutput'' — connecting this output to another container's ''sequenceInput'' forces this container to complete before the other one starts. | ||
| - | These ports carry no data of their own; their only purpose is to impose execution ordering. Because any data type can be connected to a ''sequenceInput'' port, the output of one container can feed directly into the next container's sequencing slot: | + | These ports carry no data of their own; their only purpose is to impose execution ordering. Because any data type can be connected to a ''sequenceInput'' port — every type in the system converts to it automatically, the same conversion [[type_system#sequencing|Type System]] catalogs against every type it applies to — the output of one container can feed directly into the next container's sequencing slot: |
| <code> | <code> | ||
| Line 352: | Line 354: | ||
| </code> | </code> | ||
| - | > **Note:** Sequence ports are only needed when two containers have no natural data dependency between them — if one container already produces a value that the other consumes, the ordering is already established and no sequencing port is required. Before sequence ports were available, models used a **forced-dependency pattern** — creating a dummy data value inside one container and consuming it inside another to manufacture an artificial data dependency. This idiom still appears in older scripts but is now a last resort, used only for functors that do not expose ''sequenceInput'' and ''sequenceOutput'' ports (such as [[Skip On Error]] and [[Skip All On Error]], which instead expose an ''executionCompletedSucessfully'' boolean output that can serve the same purpose). | + | > **Note:** Sequence ports are only needed when two containers have no natural data dependency between them — if one container already produces a value that the other consumes, the ordering is already established and no sequencing port is required. Before sequence ports were available, models used a **forced-dependency pattern** — creating a dummy data value inside one container and consuming it inside another to manufacture an artificial data dependency. This idiom still appears in older scripts but is now a last resort, used only for functors that do not expose ''sequenceInput'' and ''sequenceOutput'' ports (such as [[Skip On Error]] and [[Skip All On Error]], which instead expose an ''executionCompletedSuccessfully'' boolean output that can serve the same purpose). |
| ==== Internal output ports ==== | ==== Internal output ports ==== | ||
| Line 424: | Line 426: | ||
| ^ Container ^ Internal output port ^ Type ^ Description ^ | ^ Container ^ Internal output port ^ Type ^ Description ^ | ||
| - | | [[Do While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 0. | | + | | [[Do While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | |
| | [[For]] | ''step'' | RealValue | Current value in the numeric range being iterated. | | | [[For]] | ''step'' | RealValue | Current value in the numeric range being iterated. | | ||
| | [[For Each]] | ''step'' | RealValue | Current row value from the table being iterated. | | | [[For Each]] | ''step'' | RealValue | Current row value from the table being iterated. | | ||
| Line 430: | Line 432: | ||
| | [[For Each Region]] | ''step'' | IntegerValue | Current region identifier. | | | [[For Each Region]] | ''step'' | IntegerValue | Current region identifier. | | ||
| | [[For Each Region]] | ''regionManager'' | RegionManager | The region manager for the current region. | | | [[For Each Region]] | ''regionManager'' | RegionManager | The region manager for the current region. | | ||
| - | | [[Repeat]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 0. | | + | | [[Repeat]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | |
| - | | [[While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 0. | | + | | [[While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | |
| | [[Region Manager]] | ''regionManager'' | RegionManager | The region manager for the current region context. | | | [[Region Manager]] | ''regionManager'' | RegionManager | The region manager for the current region context. | | ||
| | [[Region]] | ''regionManager'' | RegionManager | The region manager. | | | [[Region]] | ''regionManager'' | RegionManager | The region manager. | | ||
| Line 476: | Line 478: | ||
| ==== Error-handling pattern ==== | ==== Error-handling pattern ==== | ||
| - | The error-handling containers are most useful when combined with a [[#carrying_and_selecting_values_across_iterations|junction]] outside the block. The sentinel and the risky functor inside the container have no data dependency on each other, so they execute independently. The pattern works as follows: | + | The error-handling containers are most useful when paired with something outside the block that reacts to the outcome — a [[#carrying_and_selecting_values_across_iterations|junction]] when the goal is a fallback value, [[If Then]]/[[If Not Then]] when the branches need to do different things (see below). The functors inside the container are typically independent of each other, with no data dependency forcing a particular order; what changes between the two containers is what happens to their results if one of them fails partway through. The pattern works as follows: |
| - | - If the risky functor raises an error, [[Skip All On Error]] captures it and discards **all** results produced by functors inside the container — including the sentinel, even though it completed successfully on its own. | + | - If any functor inside raises an error, [[Skip All On Error]] captures it and discards **all** results produced by functors inside the container — even results that had already completed successfully on their own. |
| - | - Outside the container, a **junction** tests whether anything propagated out. If the sentinel was discarded (error case), the container produced nothing and the junction falls back to its default value. If no error occurred, the sentinel propagates normally and the junction forwards it. | + | - Outside the container, a **junction** tests whether a given value propagated out. If it was discarded (the error case), the junction falls back to its default. If no error occurred, the value propagates normally and the junction forwards it. |
| - | This example tests whether a map file can be loaded successfully: | + | This example loads a categorical map together with a lookup table of transition weights that only makes sense paired with that specific map; if either file is missing, both should fall back to a matched pair of defaults, rather than risking a real map paired with mismatched default weights, or the reverse: |
| <code> | <code> | ||
| _ := SkipAllOnError .yes {{ | _ := SkipAllOnError .yes {{ | ||
| - | // The sentinel has no dependency on LoadMap — both execute independently. | + | // loadedMap and loadedWeights need to succeed together or not at all -- |
| - | // If LoadMap raises an error, SkipAllOnError discards all results inside, | + | // if either file is missing, SkipAllOnError discards both, even |
| - | // including this sentinel. | + | // whichever one loaded successfully, so the junctions below always |
| - | booleanValue0 := BooleanValue .yes; | + | // fall back to a matched, consistent pair of defaults. |
| + | loadedMap := LoadMap mapFilename; | ||
| + | loadedWeights := LoadLookupTable weightsFilename; | ||
| + | }}; | ||
| + | |||
| + | // If either load failed, both loadedMap and loadedWeights were discarded, | ||
| + | // and both junctions fall back to their defaults. If both succeeded, each | ||
| + | // junction forwards the real value. | ||
| + | mapOrDefault := MapJunction loadedMap defaultMap; | ||
| + | weightsOrDefault := LookupTableJunction loadedWeights defaultWeights; | ||
| + | </code> | ||
| + | |||
| + | When only a pass/fail signal is needed — not an actual fallback value — a junction is unnecessary: [[Skip All On Error]] already reports success or failure directly through its own ''executionCompletedSuccessfully'' output, with no sentinel or junction required: | ||
| + | |||
| + | <code> | ||
| + | result := SkipAllOnError .yes {{ | ||
| _ := LoadMap inputMapFilename; | _ := LoadMap inputMapFilename; | ||
| }}; | }}; | ||
| - | // If the sentinel was discarded (error), the junction falls back to false (0). | ||
| - | // If no error occurred, the sentinel propagates and the junction returns true. | ||
| - | result := ValueJunction booleanValue0 0; | ||
| </code> | </code> | ||
| - | [[Skip On Error]] behaves differently: instead of discarding all results, it preserves the outputs of any functors that had already completed when the error was raised. In the pattern above this means the sentinel would always propagate — making [[Skip On Error]] suitable for cases where partial results from a failed block are still useful, not for a simple success/failure test. | + | ''result'' is that boolean directly. The paired-fallback pattern above earns its extra complexity only when a real value, not just a flag, needs a fallback on failure. |
| + | |||
| + | [[Skip On Error]] behaves differently: instead of discarding all results, it preserves the outputs of any functors that had already completed when the error was raised. Substituting it into the paired-loading example above would be a mistake: since ''loadedMap'' and ''loadedWeights'' have no data dependency on each other — see [[#functors_variables_and_binding|Functors, variables, and binding]] for that execution-order rule stated generally — nothing guarantees which of the two, if either, has already completed by the time the other's error is raised. The outcome would be a race: a real map could end up paired with default weights, or the reverse, unpredictably from run to run. [[Skip All On Error]] sidesteps that race entirely — it discards both regardless of which one failed or how far the other had gotten — which is exactly what "succeed together or not at all" requires. [[Skip On Error]] instead earns its place when partial results are genuinely fine to keep on their own — see below. | ||
| + | |||
| + | Whatever [[Skip On Error]] preserves is what each branch has to work with. If the branches only need to pick between two values — the case in the [[manipulating_tables_and_lookup_tables#printing_a_table_with_a_generic_format|generic printer]] above — a junction is the simplest tool. But when the branches need to carry out different logic rather than just hand a value onward, [[If Then]]/[[If Not Then]] can test [[Skip On Error]]'s own ''executionCompletedSuccessfully'' output directly — no separate sentinel needed, unlike the paired-loading [[Skip All On Error]] pattern above. This example attempts to load an optional mask map; if it loads, the matching branch has the mask itself to work with, and if it's missing or fails to load, the other branch proceeds without one: | ||
| + | |||
| + | <code> | ||
| + | maskLoaded := SkipOnError .yes {{ | ||
| + | mask := LoadMap maskFilename; | ||
| + | }}; | ||
| + | |||
| + | // maskLoaded is SkipOnError's own boolean output; mask itself -- preserved | ||
| + | // on success -- is what the matching branch below actually needs. | ||
| + | _ := IfThen maskLoaded {{ | ||
| + | Print "Mask loaded; masking enabled" .none {{ }}; | ||
| + | }}; | ||
| + | |||
| + | _ := IfNotThen maskLoaded {{ | ||
| + | Print "Mask not found; continuing without one" .none {{ }}; | ||
| + | }}; | ||
| + | </code> | ||
| + | |||
| + | Contrast this with picking a value out of two mutually exclusive attempts, as in [[manipulating_tables_and_lookup_tables#printing_a_table_with_a_generic_format|Manipulating Tables and Lookup Tables]]: when both branches would just hand the same kind of value onward, a junction is the simpler tool; ''IfThen''/''IfNotThen'' earns its place when the branches need to do something different. | ||
| ---- | ---- | ||
| Line 1080: | Line 1116: | ||
| | **Use abbreviated syntax for Calculate family functors** | Whether the ''Calculate'' family is written using the shorthand symbol (''#'', ''##'', ''%'', etc.) or the full functor name. See the [[#calculator_functor_shorthand|Calculator functor shorthand]] section for details. | | | **Use abbreviated syntax for Calculate family functors** | Whether the ''Calculate'' family is written using the shorthand symbol (''#'', ''##'', ''%'', etc.) or the full functor name. See the [[#calculator_functor_shorthand|Calculator functor shorthand]] section for details. | | ||
| | **Preferred number of columns before wrapping comments** | The line width at which the generator wraps long comment text. | | | **Preferred number of columns before wrapping comments** | The line width at which the generator wraps long comment text. | | ||
| - | |||