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/25 23:52] hermann |
ego_script [2026/08/07 03:51] (current) hermann |
||
|---|---|---|---|
| Line 39: | Line 39: | ||
| * Variables written **before** the '':='' operator are bound to its **output** ports. | * Variables written **before** the '':='' operator are bound to its **output** ports. | ||
| - | So in ''x := LoadMap "c:/map.tif"'', the string ''"c:/map.tif"'' is an input and ''x'' receives the output; in ''SaveMap x "c:/map-copy.ers"'', both ''x'' and the filename are inputs, and the call produces no output that the script needs to keep. | + | So in ''x := LoadMap "c:/map.tif"'', the string ''"c:/map.tif"'' is an input and ''x'' receives the output; in ''SaveMap x "c:/map-copy.ers"'', both ''x'' and the filename are inputs, and the call produces no output at all. |
| A variable name must begin with an underscore or a letter, and may be followed by any combination of underscores, letters, and digits (''[_a-zA-Z][_a-zA-Z0-9]*''). Variable names are case-sensitive. | A variable name must begin with an underscore or a letter, and may be followed by any combination of underscores, letters, and digits (''[_a-zA-Z][_a-zA-Z0-9]*''). Variable names are case-sensitive. | ||
| Line 59: | Line 59: | ||
| ===== Functor calls ===== | ===== Functor calls ===== | ||
| - | A functor call binds **outputs** — the variables written before '':='' — and consumes **inputs** — the values, variables, and constants written after the functor name. Every functor defines a fixed set of typed **[[ports]]** for its inputs and outputs: | + | A functor call binds **outputs** — the variables written before '':='' — and consumes **inputs** — the values, variables, and constants written after the functor name. Every functor defines a fixed set of typed **[[#ports|ports]]** for its inputs and outputs: |
| <code> | <code> | ||
| Line 68: | Line 68: | ||
| Inputs can be supplied in three interchangeable styles — [[#positional_syntax|positional]], [[#nominal_syntax|nominal]], and [[#inline_syntax|inline]]. The styles may be mixed freely between calls, and even within a single call. | Inputs can be supplied in three interchangeable styles — [[#positional_syntax|positional]], [[#nominal_syntax|nominal]], and [[#inline_syntax|inline]]. The styles may be mixed freely between calls, and even within a single call. | ||
| + | |||
| + | ==== Ports ==== | ||
| + | |||
| + | A **port** is a typed connection point on a functor. Input ports receive data; output ports produce it. Every input port is either **required** or **optional**: | ||
| + | |||
| + | * **Required** ports must always be supplied — either by connecting an output from another functor or by providing a constant value directly. A model cannot run if a required input is missing. | ||
| + | * **Optional** ports have a **default value**, used automatically when the port is left unconnected and no constant is supplied. The default is specific to each port and is shown in the functor's own documentation. See [[#positional_syntax|Positional syntax]] and [[#nominal_syntax|Nominal syntax]] below for how optional inputs are omitted in a call. | ||
| + | |||
| + | Some optional ports are also **nullable** — they explicitly accept the absence of a value as meaningful input. For a nullable port whose default is already ''.none'', omitting the port and explicitly supplying ''.none'' both leave it without a value — but they aren't quite the same: omitting it falls back to the default, while writing ''.none'' signals that the absence of a value was chosen deliberately. See [[#constants|Constants]] below for the ''.none'' and ''.UNBOUND'' dot constants, which are available to any input port. | ||
| + | |||
| + | An **editable** port can receive a constant value typed directly into the GUI or written as a constant in a script call. Non-editable ports must instead be connected to an output from another functor; they cannot be supplied with a constant. | ||
| + | |||
| + | A port's **type** determines what data it can carry — it accepts data of its exact type, or any type that converts to it automatically across a connection. See [[basic_data_flow#functors_and_connections|Basic Data Flow]] for how that conversion works, and [[type_system|Type System]] for the full list of types and their conversions. | ||
| + | |||
| + | For how a functor's inputs are supplied and its outputs are bound — including how to discard unwanted outputs — see [[#positional_syntax|Positional syntax]], [[#nominal_syntax|Nominal syntax]], and [[#inline_syntax|Inline syntax]] below. For how port names differ between the GUI and the script itself, see [[#alias_and_variable_name_conversion|Alias and variable name conversion]]. For the ports that connect containers to the functors nested inside them, and the ports that auto-bind to them, see [[#internal_output_ports|Internal output ports]] and [[#internal_input_ports|Internal input ports]] below. | ||
| ==== Constants ==== | ==== Constants ==== | ||
| Line 73: | 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 157: | Line 172: | ||
| <code> | <code> | ||
| - | landscape := LoadCategoricalMap "c:/landscape.ers" .no .no 0 0 .none .none; | + | landscape := LoadCategoricalMap "c:/landscape.ers" .none .default 0 0 0 100 100; |
| </code> | </code> | ||
| Line 169: | Line 184: | ||
| patchInfo _ _ := GetElevationGraphInfo elevationGraph .no .no; | patchInfo _ _ := GetElevationGraphInfo elevationGraph .no .no; | ||
| </code> | </code> | ||
| + | |||
| + | A functor with no outputs at all is written as a bare call, with no assignment and no underscore — there is nothing to discard. [[Print]] and [[Save Map]] are the common cases. | ||
| ==== Nominal syntax ==== | ==== Nominal syntax ==== | ||
| - | In nominal syntax (also referred to as **named parameters** in the Dinamica EGO GUI options), inputs are enclosed in a pair of **single curly braces** ''{ }'' and bound **by port name**: each entry is written as ''portName = value'', with entries separated by commas. Order does not matter, and any optional port may be omitted regardless of position. See [[ports]] for a full description of port properties including types, nullability, and default values. This is the clearest style for functors with many optional inputs: | + | In nominal syntax (also referred to as **named parameters** in the Dinamica EGO GUI options), inputs are enclosed in a pair of **single curly braces** ''{ }'' and bound **by port name**: each entry is written as ''portName = value'', with entries separated by commas. Order does not matter, and any optional port may be omitted regardless of position. See [[#ports|Ports]] above for a full description of port properties including types, nullability, and default values. This is the clearest style for functors with many optional inputs: |
| <code> | <code> | ||
| Line 271: | Line 288: | ||
| * **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 ''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. | + | * **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. |
| 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 294: | Line 311: | ||
| <code> | <code> | ||
| - | _ := Print "Beginning execution" "Finished execution" {{ | + | Print "Beginning execution" "Finished execution" {{ |
| // Perform some complicated computation that produces a numeric result... | // Perform some complicated computation that produces a numeric result... | ||
| result := ...; | result := ...; | ||
| Line 303: | Line 320: | ||
| <code> | <code> | ||
| - | _ := Print "Beginning execution" .none {{ | + | Print "Beginning execution" .none {{ |
| // Perform some complicated computation that produces a numeric result... | // Perform some complicated computation that produces a numeric result... | ||
| result := ...; | result := ...; | ||
| Line 310: | Line 327: | ||
| NumberValue result 1; | NumberValue result 1; | ||
| }}; | }}; | ||
| - | _ := Print message .none {{ }}; | + | Print message .none {{ }}; |
| }}; | }}; | ||
| </code> | </code> | ||
| Line 323: | Line 340: | ||
| * ''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 335: | Line 352: | ||
| </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 ''executionCompletedSuccessfully'' 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 ''executionCompletedSucessfully'' boolean output that can serve the same purpose). |
| ==== Internal output ports ==== | ==== Internal output ports ==== | ||
| Line 345: | Line 362: | ||
| <code> | <code> | ||
| ForEachCategory categorization {{ | ForEachCategory categorization {{ | ||
| - | currentCategory = step; // read the internal output port `step` | + | // read the internal output port `step` |
| + | currentCategory = step; | ||
| categoryId := Step currentCategory; | categoryId := Step currentCategory; | ||
| // ... | // ... | ||
| Line 352: | Line 370: | ||
| Here ''step'' is the port the loop provides, and ''currentCategory'' is the variable that receives it for use inside the block. | Here ''step'' is the port the loop provides, and ''currentCategory'' is the variable that receives it for use inside the block. | ||
| + | |||
| + | The binding is optional in this particular case. [[Step]]'s own input is auto-bound to the enclosing container's ''step'' port — see [[#auto-bound_ports|Auto-bound ports]] below — so ''categoryId := Step;'' on its own would have worked. It is written out here to make the port visible; internal outputs with no such auto-bound consumer, like the one in the next example, always have to be read explicitly. | ||
| ''RegionManager'' provides another example. It creates a region context and exposes it to the functors inside its block through an internal output port: | ''RegionManager'' provides another example. It creates a region context and exposes it to the functors inside its block through an internal output port: | ||
| Line 357: | Line 377: | ||
| <code> | <code> | ||
| RegionManager regionMap 0 {{ | RegionManager regionMap 0 {{ | ||
| - | manager = regionManager; // read the internal output port `regionManager` | + | // read the internal output port `regionManager` |
| + | manager = regionManager; | ||
| allRegions := GetAllRegionsInfo manager; | allRegions := GetAllRegionsInfo manager; | ||
| }}; | }}; | ||
| Line 368: | Line 389: | ||
| An **internal input port** carries a value the other way: from the functors inside the block back to the container. The contained functors produce the value; the container consumes it. The condition that decides whether a loop repeats is the clearest example. | An **internal input port** carries a value the other way: from the functors inside the block back to the container. The contained functors produce the value; the container consumes it. The condition that decides whether a loop repeats is the clearest example. | ||
| - | A ''While'' or ''DoWhile'' loop has an internal input port for its repeat condition. It is fed by placing a [[Set While Condition]] functor inside the loop body — the ''condition'' output port of ''SetWhileCondition'' is **auto-bound** to the loop's ''condition'' internal input, so no explicit connection is needed: | + | A ''While'' or ''DoWhile'' loop has an internal input port for its repeat condition. It is fed by placing a [[Set While Condition]] functor inside the loop body — the ''condition'' output port of ''SetWhileCondition'' is **auto-bound** (see [[#auto-bound_ports|Auto-bound ports]] below) to the loop's ''condition'' internal input, so no explicit connection is needed: |
| <code> | <code> | ||
| _ := DoWhile .none {{ | _ := DoWhile .none {{ | ||
| // ... contained functors compute `keepGoing`, a non-zero value to continue ... | // ... contained functors compute `keepGoing`, a non-zero value to continue ... | ||
| - | _ := SetWhileCondition keepGoing; // condition output auto-binds to the loop | + | // condition output auto-binds to the loop |
| + | _ := SetWhileCondition keepGoing; | ||
| }}; | }}; | ||
| </code> | </code> | ||
| Line 380: | Line 402: | ||
| A container may also expose a sequencing-only internal port (carrying no data) used to force one container to finish before another begins — see [[#sequence_ports|Sequence ports]]. | A container may also expose a sequencing-only internal port (carrying no data) used to force one container to finish before another begins — see [[#sequence_ports|Sequence ports]]. | ||
| + | |||
| + | ==== Auto-bound ports ==== | ||
| + | |||
| + | Some ports are **auto-bound** — when a functor is placed inside a compatible container, the port connects automatically to that container's matching internal port, with no explicit binding needed. This is the general mechanism behind two examples already seen above: [[Step]]'s input auto-binds to a loop's ''step'' internal output, and [[Set While Condition]]'s output auto-binds to a loop's ''condition'' internal input. | ||
| + | |||
| + | **Auto-bound input ports** connect automatically to an internal output of the enclosing container: | ||
| + | |||
| + | * ''step'' — on [[Step]], all ''Load*'' and ''Save*'' file I/O functors, and all ''Select*'' functors. Binds to the ''step'' internal output of the enclosing loop container ([[Repeat]], [[For]], [[For Each]], [[For Each Category]], [[For Each Region]], [[While]], [[Do While]]). | ||
| + | * ''workdir'' — on all ''Load*'' and ''Save*'' file I/O functors. Binds to the ''workdir'' internal output of the enclosing [[Workdir]] container. | ||
| + | * ''regionManager'' — on [[Get All Regions Info]], [[Get Region Info]], [[Region]], [[Region Manager Value]], [[Regional Map]], [[Regional Categorical Map]], [[Regionalize Map]], [[Regionalize Categorical Map]], [[Merge Regional Maps]], [[Merge Regional Categorical Maps]]. Binds to the ''regionManager'' internal output of the enclosing [[Region Manager]], [[Region]], or [[For Each Region]] container. | ||
| + | * ''regionId'' — on [[Get Region Info]], [[Region]], [[Regional Map]], [[Regional Categorical Map]], [[Regionalize Map]], [[Regionalize Categorical Map]]. Binds to the ''regionId'' internal output of the enclosing [[Region]] container. | ||
| + | * ''tableManager'' — on [[Merge Sub Tables]], [[Sub Table]], [[Table Manager Value]]. Binds to the ''tableManager'' internal output of the enclosing [[Table Manager]] container. | ||
| + | * ''currentIndividual'' — on [[Get Current Individual]]. Binds to the ''currentIndividual'' internal output of the enclosing [[Genetic Algorithm Tool]] container. | ||
| + | |||
| + | **Auto-bound output ports** connect automatically to an internal input of the enclosing container: | ||
| + | |||
| + | * ''condition'' — on [[Set While Condition]]. Binds to the ''condition'' internal input of the enclosing [[While]] or [[Do While]] container. | ||
| + | * ''fitness'' — on [[Set Fitness]]. Binds to the ''fitness'' internal input of the enclosing [[Genetic Algorithm Tool]] container. | ||
| + | |||
| + | The full set of internal ports exposed by each container: | ||
| + | |||
| + | ^ Container ^ Internal output port ^ Type ^ Description ^ | ||
| + | | [[Do While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | | ||
| + | | [[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 Category]] | ''step'' | IntegerValue | Current category code from the categorical map. | | ||
| + | | [[For Each Region]] | ''step'' | IntegerValue | Current region identifier. | | ||
| + | | [[For Each Region]] | ''regionManager'' | RegionManager | The region manager for the current region. | | ||
| + | | [[Repeat]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | | ||
| + | | [[While]] | ''step'' | NonNegativeIntegerValue | Current iteration index, starting at 1. | | ||
| + | | [[Region Manager]] | ''regionManager'' | RegionManager | The region manager for the current region context. | | ||
| + | | [[Region]] | ''regionManager'' | RegionManager | The region manager. | | ||
| + | | [[Region]] | ''regionId'' | IntegerValue | The current region identifier. | | ||
| + | | [[Workdir]] | ''workdir'' | Workdir | The working directory defined by the container. | | ||
| + | | [[Table Manager]] | ''tableManager'' | TableManager | The table manager defined by the container. | | ||
| + | | [[Genetic Algorithm Tool]] | ''currentIndividual'' | LookupTableGroup | The current individual in the genetic algorithm population. | | ||
| + | |||
| + | ^ Container ^ Internal input port ^ Type ^ Set by ^ | ||
| + | | [[While]] | ''condition'' | BooleanValue | [[Set While Condition]] — the ''condition'' output port is auto-bound to this internal input. | | ||
| + | | [[Do While]] | ''condition'' | BooleanValue | [[Set While Condition]] — the ''condition'' output port is auto-bound to this internal input. | | ||
| + | | [[Genetic Algorithm Tool]] | ''fitness'' | RealValue | [[Set Fitness]] — the ''fitness'' output port is auto-bound to this internal input. | | ||
| ==== Carrying and selecting values across iterations ==== | ==== Carrying and selecting values across iterations ==== | ||
| Line 402: | Line 465: | ||
| </code> | </code> | ||
| - | > **Note:** A mux's feedback input is the one exception to the dependency rule in [[#functors_variables_and_binding|Functors, variables, and binding]] — the mux does not wait for the functor that produces its feedback value to run before it runs; it emits the value that functor produced on the previous iteration instead. This is also why, in the example above, ''nextCount'' is passed into ''MuxValue'' on a line above the line that assigns ''nextCount'': the mux and the calculator each depend on the other's output, so no matter which one is written first in the script, the other's variable is necessarily used before it is assigned. | + | > **Note:** A mux's feedback input is the one exception to the dependency rule in [[#functors_variables_and_binding|Functors, variables, and binding]] — the mux does not wait for the functor that produces its feedback value to run before it runs; it emits the value that functor produced on the previous iteration instead. This is also why, in the example above, ''nextCount'' is passed into ''MuxValue'' on a line above the line that assigns ''nextCount'': the mux and the calculator each depend on the other's output, so no matter which one is written first in the script, the other's variable is necessarily used before it is assigned. See [[basic_data_flow#the_one_exception_feedback_in_loops|Basic Data Flow]] for this same exception explained generally, independent of EGO Script, including why it's what makes loops schedulable at all and when a loop's iterations can run in parallel. |
| A **junction** selects between two dataflows. It reads its first input; if that input carries data, the junction passes it through. If the first input is empty, it passes the second input through instead. If both are empty, the junction reports an error. Junctions are typed, with one per data kind — [[Map Junction]], [[Categorical Map Junction]], [[Table Junction]], [[Lookup Table Junction]], [[Value Junction]], [[String Junction]], and so on: | A **junction** selects between two dataflows. It reads its first input; if that input carries data, the junction passes it through. If the first input is empty, it passes the second input through instead. If both are empty, the junction reports an error. Junctions are typed, with one per data kind — [[Map Junction]], [[Categorical Map Junction]], [[Table Junction]], [[Lookup Table Junction]], [[Value Junction]], [[String Junction]], and so on: | ||
| Line 413: | Line 476: | ||
| ==== 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 ''executionCompletedSucessfully'' 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 ''executionCompletedSucessfully'' 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 792: | Line 889: | ||
| | ''@submodel.largeicon'' | Base64-encoded large icon (32×32 px) displayed in the functor library. | //base64 string// | | | ''@submodel.largeicon'' | Base64-encoded large icon (32×32 px) displayed in the functor library. | //base64 string// | | ||
| | ''@submodel.smallicon'' | Base64-encoded small icon (16×16 px) displayed in the functor library. | //base64 string// | | | ''@submodel.smallicon'' | Base64-encoded small icon (16×16 px) displayed in the functor library. | //base64 string// | | ||
| + | |||
| + | These describe the submodel's own identity. A script that instead **calls** an external submodel it may not be able to resolve uses ''@submodel.import'' — see [[#importing_an_external_submodel_interface|Importing an external submodel interface]] below. The two aren't mutually exclusive: a submodel definition that itself calls other submodels can carry both, declaring its own identity with these properties while also declaring ''@submodel.import'' for whichever of those calls it can't guarantee will resolve. | ||
| ==== Declaring input ports ==== | ==== Declaring input ports ==== | ||
| Line 979: | Line 1078: | ||
| See [[submodels|Submodels]] for full details on submodel types, storage paths, and the Submodel Store. | See [[submodels|Submodels]] for full details on submodel types, storage paths, and the Submodel Store. | ||
| + | |||
| + | ==== Importing an external submodel interface ==== | ||
| + | |||
| + | A script that calls a submodel normally resolves the call by locating and loading the submodel's actual implementation, following [[#file_location|File location]] above. The ''@submodel.import'' property handles the case where that resolution fails — the submodel isn't installed, or isn't present alongside the script being opened. This applies equally to a plain model and to a submodel definition — ''ValidateInputValue'' in the [[#complete_example|Complete example]] above calls ''AbortConditionally'' internally, and could carry ''@submodel.import = AbortConditionally { ... }'' if ''AbortConditionally'' weren't guaranteed to be resolvable wherever ''ValidateInputValue'' is opened. The property always names the external submodel being called — never the script's own submodel, if it is one; ''ValidateInputValue'' would not declare itself this way. | ||
| + | |||
| + | The property is written once, model-level: the called submodel's functor name, followed by three bracketed groups giving its required inputs, its optional inputs, and its outputs, each a comma-separated list of ''"portName": TypeName'' pairs: | ||
| + | |||
| + | <code> | ||
| + | @submodel.import = CreateElevationGraphPatchesAndProperties { { "elevationMap": Map, "verticalResolution": PositiveIntegerValue } { "onlyOrthogonalNeighborsAreAllowed": BooleanValue } { "elevationNeighborhoodTable": NeighborhoodTable, "elevationGraphProperties": Table, "discretizedElevationMap": Map, "uniqueDiscretizedPatchMap": CategoricalMap } } | ||
| + | </code> | ||
| + | |||
| + | When a script calls more than one submodel it may need to import this way, every declaration is packed into the same property, one submodel per semicolon-separated group: | ||
| + | |||
| + | <code> | ||
| + | @submodel.import = CreateAndFilterElevationGraph { { "elevationPatchNeighborhoodTable": NeighborhoodTable, "elevationPatchProperties": Table } { "minimumElevationPatchSize": RealValue, "maximumElevationPatchSize": RealValue } { "elevationGraph": ElevationGraph } }; IdentifyHilltops { { "elevationGraph": ElevationGraph, "maximumHilltopDistance": RealValue, "minimumHilltopToSaddleHeight": RealValue, "minimumHilltopToSaddleSlopeAngle": RealValue, "hilltopFraction": Percent, "maximumHilltopClusteringDistance": RealValue } { "printInfo": BooleanValue, "maximumNodes": NonNegativeIntegerValue } { "hillTopAnalysis": Table, "hilltopRationale": LookupTable } } | ||
| + | </code> | ||
| + | |||
| + | When the real submodel can't be resolved, the parser falls back to this declaration and constructs a **hollow functor**: a shell with the declared ports, but no implementation behind them. This is what lets the model load at all despite the missing piece — every connection, type, and the overall graph structure still check out, even though nothing is actually there to execute. | ||
| + | |||
| + | > **Caution:** A hollow functor is a provisional placeholder, not a stable state. A model can't be run while it contains one, since there's nothing behind it to execute — and it can't be saved in that state either. The real submodel implementation has to be resolved and substituted back in first, by making it locatable again (installing it, or placing it alongside the script per [[#file_location|File location]] above). | ||
| + | |||
| + | This property describes the underlying model graph, not something specific to one notation — it works identically whether the model is written as EGO Script or [[xml_script|EGOML Script]]. | ||
| ===== Script generation options ===== | ===== Script generation options ===== | ||
| Line 993: | Line 1114: | ||
| | **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. | | ||
| - | |||
| - | ---- | ||
| - | |||
| - | ===== A note on ''CalcAreas'' ===== | ||
| - | |||
| - | ''CalcAreas'' returns a **single** output, ''areas'', of type ''Table''. The table has a key column ''Category'' and the data columns ''Area_In_Cells'', ''Area_In_Hectares'', and ''Area_In_Square_Meters'', so one call yields every area measure for every category at once. A common mistake is to treat the output as if it were a single hectares figure; it is a table. To work with one measure, read the corresponding column from it: | ||
| - | |||
| - | <code> | ||
| - | areaTable := CalcAreas landscape .no; | ||
| - | // the Area_In_Hectares column | ||
| - | hectaresColumn := GetTableColumn areaTable 3; | ||
| - | </code> | ||
| - | |||