Generated reference › API — ICoreBlocks/ICoreCoder
kind: generated#api#icoreblocks-icorecoder

API — ICoreBlocks/ICoreCoder

The public contract of 43 header(s) under src/ICoreBlocks/ICoreCoder — 52 class/struct definition(s), 219 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

HeaderDefinesDeclarationsBases
ICoreCoderAPI.h—0—
ICoreCoderShell.hICoreCoderShell6—
ICoreAccountCommands.hICoreAccountCommands5—
ICoreClickScript.hICoreClickScript1—
ICoreCommandEngine.hICoreCommandEngine6—
ICoreCommandGlossary.hICoreCommandGlossary1—
ICoreCommandResult.hICoreCommandResult4—
ICoreCommandSamples.hICoreCommandSamples1—
ICoreConsoleInterpreter.hICoreConsoleInterpreter18—
ICoreConsoleStatements.hICoreStatementValue, ICoreStatementCall, ICoreStatementResult, ICoreConsoleStatements9—
ICoreConsoleSyntax.hStatement, Fragment, FunctionSignature0—
ICoreDataFileCommands.hICoreDataFileCommands1—
ICoreLogSinkRegistry.hICoreLogSinkRegistry3—
ICoreMathToolCommands.hICoreMathToolCommands1—
ICoreMessageTrafficCommands.hICoreMessageTrafficCommands1—
ICoreModelConfigCommands.hICoreModelConfigCommands5—
ICoreNavigationCommands.hICoreNavigationCommands1—
ICoreRecipeClipboard.hICoreRecipeClipboard2—
ICoreRecipeFileTransfer.hICoreRecipeFileTransfer7—
ICoreRecipeInterpreter.hICoreRecipeInterpreter12—
ICoreRecipeSerializer.hICoreRecipeSerializer6—
ICoreRecipeToSimulinkEmitter.hICoreRecipeToSimulinkEmitter7—
ICoreScriptAnalyzer.hICoreScriptAnalyzer0—
ICoreScriptDiagnostic.hICoreScriptStackFrame, ICoreScriptDiagnostic, ICoreScriptDiagnosticText2—
ICoreScriptLayout.hICoreScriptLayout8—
ICoreScriptRunner.hICoreScriptRunner31—
ICoreScriptTargets.hICoreScriptTargets9—
ICoreSimulationCommands.hICoreSimulationCommands3—
ICoreToolchainCommands.hICoreToolchainCommands1—
ICoreUpdateCommands.hICoreUpdateCommands2—
ICoreWorkspaceCommands.hICoreWorkspaceCommands1—
ICoreWorkspaceInspector.hICoreWorkspaceInspector4—
ICoreMatlabCommandBridge.hICoreMatlabCommandBridge3—
ICoreMatlabCommandCatalog.hICoreMatlabCommandCatalog14—
ICoreMatlabCommandImporter.hICoreMatlabCommandImporter2—
ICoreSimulinkBlockCatalog.hICoreSimulinkBlockCatalog10—
ICoreSimulinkBridge.hICoreSimulinkBridge2—
ICoreSimulinkExchangeModel.hICoreSimulinkExchangeBlock, ICoreSimulinkExchangeLink, ICoreSimulinkExchangeSystem, ICoreSimulinkExchangeReport5—
ICoreSimulinkMCodec.hICoreSimulinkMCodec5—
ICoreSimulinkRecipeCodec.hICoreSimulinkRecipeCodec1—
ICoreSimulinkSolverCodec.hICoreSimulinkSolverCodec8—
ICoreTemplateCommands.hICoreTemplateCommands1—
ICoreTemplateLibrary.hICoreTemplateLibrary10—

ICoreCoderAPI.h#

src/ICoreBlocks/ICoreCoder/ICoreCoderAPI.h

The one header C++ callers outside ICoreCoder include to run command scripts: everything here forwards to the public API of ICoreScriptRunner, so callers never reach into the module tree themselves. Add a forwarder here when a runner API is meant for outside use; internal-only helpers stay off this surface.

The terminal-style REPL rides along: ICoreCoderShell (a sibling of this header) is part of the same outside-facing surface, so including this one file also brings ICoreCoderShell::execute() and the blocking run() loop.

The contracts are the runner's, not restated here — see ICoreScriptRunner.h for the GUI-thread rule, first-failing-line semantics and comment handling.

File-scope declarations#

// Which line failed and what the console printed for it (line 0 = the
// script never ran a line, e.g. its file could not be read).
using LineFailure = ICoreScriptRunner::LineFailure;

ICoreCoderShell.h#

src/ICoreBlocks/ICoreCoder/ICoreCoderShell.h

ICoreCoderShell#

ICoreCoderShell.h:27 · class · pImpl · 6 declaration(s)

A terminal-style shell over the command console: prompt, read a line, evaluate, print, repeat.

class ICoreCoderShell {
public:
    ICoreCoderShell();
    ~ICoreCoderShell();

    // One terminal interaction: a line in, the printed result out. Blank
    // lines and '#' comments succeed silently, exactly as in a script.
    static ICoreCommandResult execute(const ICoreString& rawLine);

    // Blocking REPL over the given streams: prompt, read, execute, print,
    // until EOF or an "exit"/"quit" line. Returns the number of failed
    // lines (0 = every command succeeded).
    int run(std::istream& in, std::ostream& out);

    // The prompt text printed before each read ("icore> " by default).
    void setPrompt(const ICoreString& prompt);
    const ICoreString& prompt() const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreAccountCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreAccountCommands.h

ICoreAccountCommands#

ICoreAccountCommands.h:46 · class · nested Observation, TokenSketch · 5 declaration(s)

ICoreAccountCommands Binds licenseStatus to the console, so what this build thinks of its licence is answerable from a script — the same shape checkUpdates has.

class ICoreAccountCommands {
public:
    ICoreAccountCommands() = delete;

    static void registerAll();

    // ---- the parts that can be wrong, exposed so they can be tested --------
    //
    // PURE. The command's value is entirely in what it SAYS, and a report that
    // omits the one line explaining a refusal is the bug worth guarding. A
    // test drives every branch here without setting an environment variable,
    // binding a gate, or owning a token.

    // Everything the command looked at.
    struct Observation {
        // The raw ICORE_LICENSE_TOKEN, or empty when it is not set. ⚠ NEVER
        // printed back in full: a token is a bearer credential, and console
        // output lands in CI logs that are far more widely readable than the
        // secret store it came from.
        std::string envToken;

        // ICoreJwtVerifier::pinnedKeys().size(). Zero in every build in this
        // tree today, which is why it is the first thing the report explains.
        std::size_t pinnedKeyCount = 0;

        // False when nothing has bound a gate — which is every build until
        // A3.1 lands, and is not an error.
        bool gateBound = false;

        // As icore::LicenseState spells it.
        std::string state = "Unlicensed";

        int graceDaysRemaining = 0;
    };

    [[nodiscard]] static std::string report(const Observation& observed);

    // A JWT's shape and two of its header/payload fields, read WITHOUT
    // verifying anything. Diagnostics only — see the header note.
    struct TokenSketch {
        bool wellFormed = false;

        std::string  algorithm;        // header `alg`
        std::string  kid;              // header `kid` — the field that decides
                                       // whether a pinned key could match
        std::int64_t expiresAtUnix = 0;  // payload `exp`, 0 when absent

        // Why it is not well formed. Empty when it is.
        std::string why;
    };

    [[nodiscard]] static TokenSketch sketchUnverified(std::string_view token);

    // ⚠ THE ONLY FORM A TOKEN MAY BE PRINTED IN. First 6 and last 4
    // characters, everything between them elided — enough to tell two tokens
    // apart in a log, not enough to use one. A short string is elided
    // entirely rather than mostly-shown.
    [[nodiscard]] static std::string redact(std::string_view token);
};
};

ICoreClickScript.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreClickScript.h

ICoreClickScript#

ICoreClickScript.h:15 · class · nested Outcome · 1 declaration(s)

What a click on a block runs: a script from the project's scripts/ folder, by name, through the console's own run command -- so it runs in the console workspace exactly as run <name> typed at t...

class ICoreClickScript {
public:
    struct Outcome {
        bool ran = false;      // false: refused before anything ran
        bool ok = false;       // the script ran to its end
        std::string output;    // the refusal, or what `run` answered
    };

    // Refused, with the reason and nothing run: no name; a script already
    // running in the Script IDE (a second entry would run inside its frames);
    // code export running (the model is frozen while it reads it). Otherwise
    // `run <name>`, and its verdict. A refusal or a failure is also shown to
    // the user as a warning titled with `blockLabel`, since nobody typed the
    // line whose answer would otherwise be lost.
    static Outcome run(const std::string& scriptName, const std::string& blockLabel = std::string());
};
};

ICoreCommandEngine.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreCommandEngine.h

ICoreCommandEngine#

ICoreCommandEngine.h:25 · class · pImpl · 6 declaration(s)

Central registry + dispatcher for the self-use command console.

class ICoreCommandEngine {
public:
    // args = tokens after the command name. Returns text for the history panel.
    using Handler = std::function<std::string(const ICoreStringList& args)>;

    // Same, for a command that can fail. A plain Handler's text is always taken
    // as success, which is right for the many commands whose only outcome is
    // "here is what you asked for" — but a command that can genuinely fail has
    // to say so, because the ok flag is what colours the console red, what stops
    // a script at its first bad line (ICoreCommandScriptWindow, ICoreScriptRunner),
    // and what --console turns into the process exit code.
    using ResultHandler = std::function<ICoreCommandResult(const ICoreStringList& args)>;

    static ICoreCommandEngine* instance();

    void registerCommand(const ICoreString& name,
                         Handler handler,
                         const ICoreString& description = ICoreString());
    void registerCommand(const ICoreString& name,
                         ResultHandler handler,
                         const ICoreString& description = ICoreString());
    bool hasCommand(const ICoreString& name) const;

    // Parse `rawLine` into name + args and dispatch. Never throws: a handler
    // that throws is reported as a failure result instead of propagating.
    ICoreCommandResult run(const ICoreString& rawLine) const;

    ICoreStringList commandNames() const;            // sorted — for help / autocomplete
    ICoreString     describe(const ICoreString& name) const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreCommandGlossary.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreCommandGlossary.h

ICoreCommandGlossary#

ICoreCommandGlossary.h:11 · class · nested Entry · 1 declaration(s)

Single catalog of everything the console understands: registered commands (from ICoreCommandEngine), the language's keywords (from ICoreConsoleInterpreter), the matrix functions (from ICoreExpressi...

class ICoreCommandGlossary {
public:
    struct Entry {
        ICoreString name;
        // "command", "keyword", "function", "recipe" or "property". A kind is
        // how a surface decides what to do with an entry: names() drops
        // "property" (get()/info arguments, not line-start verbs), formatted()
        // gives each its own section, and the highlighter its own colour.
        ICoreString kind;
        ICoreString signature;
        ICoreString description;
        // Which MATLAB toolbox this name comes from, as MATLAB's `ver` spells
        // it, and empty for the core-MATLAB names — which is most of them.
        // Read off the MATLAB catalog's own tag rather than kept a second time
        // here: the catalog is where the crossing is decided, so it is where
        // the provenance lives.
        // Only a `function` entry can carry one; a command or a recipe verb is
        // this console's own vocabulary, not MATLAB's.
        ICoreString toolbox;
        // True for the toolbox names this console carries AHEAD of MATLAB's
        // own form of them. `help` says so, because until that form lands,
        // what a MATLAB call spelled the same way means here is undecided.
        bool        toolboxExtra = false;
    };

    static ICoreList<Entry> entries();   // commands, keywords, functions, recipe verbs, properties
    static ICoreStringList   names();    // sorted, de-duplicated — for completion/suggestions
    static ICoreString       formatted();// human-readable multi-section listing

    // One entry rendered for `help <name>`: kind, signature and description.
    // Empty when nothing in the catalog answers to `name` — the caller decides
    // whether that is an error (`help` does; it is the only caller today).
    // Case-sensitive, like every other name lookup in the console.
    static ICoreString describe(const ICoreString& name);
};
};

ICoreCommandResult.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreCommandResult.h

ICoreCommandResult#

ICoreCommandResult.h:8 · struct · 4 declaration(s)

Outcome of running one command line through ICoreCommandEngine.

struct ICoreCommandResult {
public:
    bool    ok      = true;   // false => render the output as an error (red)
    bool    handled = true;   // false => the command name was not registered
    ICoreString output;           // text to show in the history panel

    static ICoreCommandResult success(const ICoreString& text);
    static ICoreCommandResult failure(const ICoreString& text);
    static ICoreCommandResult unknown(const ICoreString& name);
};
};

ICoreCommandSamples.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreCommandSamples.h

ICoreCommandSamples#

ICoreCommandSamples.h:8 · class · 1 declaration(s)

Sample command registrations, kept out of the engine so the engine stays a generic framework.

class ICoreCommandSamples {
public:
    static void registerAll();
};
};

ICoreConsoleInterpreter.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreConsoleInterpreter.h

ICoreConsoleInterpreter#

ICoreConsoleInterpreter.h:51 · class · nested OutputLine, LineResult, Keyword, LineFailure · 18 declaration(s)

The console language's statement interpreter — the one pipeline every console line resolves through, whichever surface fed it: the Command Window prompt, the Script Runner, --console, the REPL sh...

class ICoreConsoleInterpreter {
public:
    // One line of printed output and the statement that printed it
    // (Script IDE board, S1.9): the script file (empty for none) and the 1-based
    // source line (0 when the lines carried no numbering). Output printed by a
    // script-defined function's body carries the line IN THE FUNCTION, so an
    // output pane can link each line back to where it came from.
    struct OutputLine {
        ICoreString text;
        ICoreString file;
        int         line = 0;
    };

    // Outcome of resolving one line (or one closed block).
    struct LineResult {
        bool        ok = true;   // false => the line failed (render/log as an error)
        ICoreString output;      // the text the prompt would have printed
        // A `return` ran (the core-MATLAB row C2.6). The line SUCCEEDED —
        // this is not a failure — and asks whatever is feeding lines to stop
        // feeding them: a script ends here, and the prompt has nothing to end.
        bool        stopped = false;
        // The run was STOPPED BY THE USER (requestStop, the Script IDE board S1.6).
        // `ok` is false so that a caller that knows nothing of this still
        // stops feeding lines, but it is not a failure of the script: a surface
        // that shows one should say "stopped", not "error". `try` never
        // catches it.
        bool        cancelled = false;
        // The source line of the statement this result is about: the one that
        // FAILED, for a failure inside a block (Script IDE board, S1.1) — not the
        // line whose `end` closed the block. 0 when the lines were fed with no
        // numbering (a prompt) and nothing better is known.
        int         line = 0;
        // `output`, one entry per line (output split on '\n'), each tagged
        // with the statement that printed it (S1.9). Empty when `output` is.
        std::vector<OutputLine> origins;
        // How many LEADING lines of `output` were PRINTED rather than echoed:
        // what a called function's body printed, and what disp / display /
        // fprintf / warning print. A ';' after the statement keeps these and
        // hides only the echo of a value -- MATLAB's rule, where `f();` still
        // shows f's `disp` and `disp(x);` still prints (Script IDE board, S1.10).
        int         printedLines = 0;
    };

    static LineResult evaluateLine(const ICoreString& line);

    // True when every statement on `line` is ';'-terminated (nothing follows the
    // last top-level ';'), so a successful run prints nothing whatsoever —
    // callers that echo the source line before running it drop the echo too.
    static bool isFullySuppressed(const ICoreString& line);

    // True when `name` is a function this session's scripts defined
    // (the core-MATLAB row C2.7). Read by the MATLAB bridge, which has to
    // tell a CALL to a script-defined function -- which crosses verbatim,
    // because MATLAB spells a local-function call the same way -- from a name
    // it has no mapping for. The registry itself is session-scoped and lives
    // in the .cpp; nothing outside needs to enumerate it.
    static bool isUserFunction(const std::string& name);

    // MATLAB's `format short` / `format long` (C11.16), which is DISPLAY and
    // nothing else: the values never move, only how many of their digits reach
    // the screen and the variables panel. It lives here because this is where
    // the console's printing lives -- the evaluator is a pure map from
    // arguments to values and has no business holding a display mode.
    //
    // `short` is the console's own default (what C1.20 decided to keep) rather
    // than MATLAB's 5 significant digits; `long` is 16, which is MATLAB's own
    // and enough to round-trip a double. The `format` COMMAND that sets it is
    // registered in ICoreWorkspaceCommands beside clc and clear.
    static void setLongFormat(bool longForm);
    static bool longFormat();

    // Variable lookup for ICoreExpressionEvaluator: rebuilds the typed ICoreValue
    // (matrix / polynomial / tf / state-space / series) from the global space.
    static bool resolveGlobalVariable(const std::string& name, ICoreValue& out);

    // ---- the language's keywords ---------------------------------------------

    // The words that are syntax rather than a command or a function. This is
    // the ONE table for the whole tree: the block accumulator below reads its
    // openers, ICoreCommandGlossary lists it under entry kind "keyword" — which
    // is what puts a keyword into `glossary`, `help <keyword>` and Tab
    // completion — and ICoreScriptEditorHighlighter colours what it names. A
    // keyword added anywhere else is a keyword one of those three surfaces does
    // not know about. The table itself, with the reasoning behind each row, is
    // keywords() in the .cpp.
    //
    // `row` identifies the planned work that makes the keyword RUN, and it is
    // what the refusal message names: the accumulator already knows the
    // multi-line shape, so a user who types a block is told where execution
    // stands rather than watching the word fall through to "unknown command".
    struct Keyword {
        const char* word;
        bool        opensBlock;   // if for while switch function try — the six
        const char* row;          // "C2.1" … ; "" for `end`, which closes any block
        const char* signature;
        const char* description;
    };
    static const std::vector<Keyword>& keywords();

    // The names this module answers as STATEMENTS rather than handing to the
    // expression evaluator. Each is a call with no value -- error/warning/
    // assert raise or print (C2.9), disp/display show (C11.1), the three file
    // writers write (C14.4) and fprintf prints (C11.2) -- so none of them can
    // come back through a path that expects one value, and every one of them
    // has to be recognised BEFORE the expression arm.
    //
    // fprintf is the one of these that MATLAB gives an output to (the byte
    // count), and this console does not: a knowing divergence, recorded on its
    // row rather than worked around, because sprintf answers the text and is
    // what a caller wanting a value actually wants.
    //
    // It is a list rather than a set of `if`s because two places need the same
    // answer, and they are in different modules: this file dispatches on it,
    // and the command-parity ScriptCase runner asks it whether a script needs
    // the real interpreter or can go through its own mini-runner
    // (ICoreCommandParityCommands.cpp). That runner used to hand-list three of
    // these names, so a fourth statement was invisible to it and its cases
    // failed as "writing a file has no value to assign" -- found by C14.4,
    // which was the fourth.
    static const std::vector<const char*>& statementHeads();

    // ---- the block accumulator ----------------------------------------------

    ICoreConsoleInterpreter();
    ~ICoreConsoleInterpreter();

    // Feed one physical line. Inside an open block the line is buffered and the
    // result is a silent success; the line that closes the outermost block runs
    // the block and returns its result. `end` with no block open is a failure.
    LineResult feed(const ICoreString& line);

    // A block is open: the next feed() is buffered, not run.
    bool pending() const;
    // A line ended in "..." and the statement is unfinished, or a lone "%{"
    // opened a block comment that no "%}" has closed yet (C1.16). Both are
    // physical-line states: they are feed()'s business and never reach
    // evaluateLine, which only ever sees whole statements. A surface that
    // shows a prompt shows a continuation for either; a script that ENDS in
    // either has dropped a statement on the floor and fails.
    bool continuing() const;
    bool inBlockComment() const;
    // Nesting depth of the open block (0 when none) and the keyword that opened
    // the outermost one ("for", "if", …) for a continuation prompt.
    int         depth() const;
    ICoreString openKeyword() const;
    // Drop the open block without running it (the prompt's Ctrl+C).
    void abandon();

    // ---- whole scripts --------------------------------------------------------

    // Failure detail for runLines. `line` is 1-based; 0 means nothing ran.
    struct LineFailure {
        int         line = 0;
        ICoreString text;     // the failing line, trimmed
        ICoreString output;   // what the console printed for it
        // Stopped by the user rather than failed (S1.6); `output` says so.
        bool        cancelled = false;
        // The same failure, placed (Script IDE board, S1.2): the innermost file
        // and line — inside a called function or a `run` script when that is
        // where it failed — the message, MATLAB's identifier when error() gave
        // one, and the call stack, innermost first.
        ICoreScriptDiagnostic diagnostic;
    };

    // Run lines through a fresh accumulator: blank lines and '#' comments are
    // skipped, a block spanning several lines runs as one unit, and a block
    // still open after the last line is a failure on that line. Stops at the
    // first failure, and `failure->line` is the STATEMENT that failed — inside
    // a block too (Script IDE board, S1.1), not the line that closed the block.
    static bool runLines(const ICoreStringList& lines, LineFailure* failure = nullptr);

    // What a surface that SHOWS a run is told as it goes (the Script Runner
    // window): each line it feeds, before and after. `line` is 1-based.
    // `continuation` is true when the line lands inside an open block, a "..."
    // continuation or a %{ comment, where a prompt reads "..." rather than
    // ">>>". Lines runLines skips (blank, '#') are not reported. Either member
    // may be empty. This is the same run as the overload above -- the functions
    // a script defines are registered first, `return` ends it, and a block
    // still open at the end fails -- so a window cannot drift into a second
    // grammar again (Script IDE board, S0.2).
    struct LineObserver {
        std::function<void(int line, const ICoreString& text, bool continuation)> before;
        std::function<void(int line, const ICoreString& text, bool continuation,
                           const LineResult& result)> after;
    };
    static bool runLines(const ICoreStringList& lines, LineFailure* failure,
                         const LineObserver& observer);

    // The same run, for lines that came FROM a file: `file` (a name such as
    // "setup.icore", or empty) is what the call stack, the diagnostic, the
    // execution hook and every output origin name (S1.2, S1.5, S1.9).
    // ICoreScriptRunner::runScript -- and so `run <name>` -- goes through here.
    static bool runScript(const ICoreString& file, const ICoreStringList& lines,
                          LineFailure* failure, const LineObserver& observer = LineObserver());

    // ---- the execution hook (Script IDE board, S1.5) ------------------------------
    //
    // A per-statement observer: called before every statement a run executes —
    // each top-level line, each statement inside a block, each re-test of a
    // `while` condition and each `elseif` — with where it is. What it answers
    // decides what happens next:
    //
    //   Continue — run the statement;
    //   Pause    — call the pause handler, which BLOCKS until the user resumes
    //              (a nested event loop, per decision D2) and answers Continue
    //              or Stop; a null pause handler is Continue;
    //   Stop     — requestStop(), so the run ends as "stopped by user".
    //
    // This is the seam for Stop, breakpoints and stepping (S1.6, S5). With no
    // hook installed the cost is one test of an empty std::function per
    // statement. The hook is NOT re-entered: a statement run from inside it or
    // the pause handler (a watch expression, the Variables panel) is not
    // reported. One hook at a time; installing replaces, and a null hook
    // removes. GUI thread only, like everything else here.
    struct ExecutionLocation {
        ICoreString file;       // the script, or empty
        ICoreString function;   // the script-defined function, or empty at script level
        int         line  = 0;  // 1-based source line; 0 when unnumbered
        int         depth = 0;  // user-function call depth: 0 in the script's workspace
        // Frames on callStack(): every script being run (a `run` adds one) and
        // every function call. What Step Over / Step Out compare (S5.3): a
        // `run` target shares its caller's workspace, so `depth` cannot see it.
        int         stackDepth = 0;
    };
    enum class ExecutionDecision { Continue, Pause, Stop };
    using ExecutionHook = std::function<ExecutionDecision(const ExecutionLocation&)>;
    static void setExecutionHook(ExecutionHook hook, ExecutionHook pauseHandler = ExecutionHook());

    // The call stack of the statement running now, innermost first — what a
    // pause handler shows as its Call Stack (S5.4). Empty when nothing runs.
    static std::vector<ICoreScriptStackFrame> callStack();

    // ---- Stop (Script IDE board, S1.6) --------------------------------------------
    //
    // Ask the running script to stop. It is checked at every statement
    // boundary, at every loop iteration and at every function call, and it
    // ends the run with LineResult::cancelled — not a failure, and not
    // catchable by `try`. A request made while nothing runs is dropped when
    // the next run starts, and a finished run clears it. Safe to call from
    // any thread; the check is on the running one.
    static void requestStop();
    static bool stopRequested();

    // ---- keeping the window alive (Script IDE board, S1.7, decision D2) -------------
    //
    // A run stays on the GUI thread and YIELDS: at a statement boundary, once
    // `yieldIntervalMs` has passed since the last yield, the event pump lends
    // the toolkit a slice -- repaints happen, a Stop button can be pressed.
    // A pause (the execution hook's pause handler) is a loop over
    // pumpEvents(waitMs) until the user resumes.
    //
    // The PUMP is installed once by whoever owns the event loop (the Shell's
    // Application), because this layer may not name it; with none installed,
    // pumpEvents() does nothing and no run ever yields (--console, suites).
    // The INTERVAL is set by a surface for the runs it starts, and 0 -- the
    // default -- never yields.
    //
    // ⚠ Yielding lets the user act mid-run, so every surface that can START a
    // line must ask isRunning() first and refuse while it is true: a second
    // entry would run inside the first one's frames. isRunning() is true
    // exactly while a run is on the stack AND the caller arrived through its
    // event pump (a yield or a pause) -- not merely while some run is below,
    // which is also true of a command called by a script.
    //
    // The pump answers whether the SESSION is still running: false once the
    // application is quitting, which is what ends a pause loop instead of
    // spinning it. pumpEvents() answers the same, and false when no pump is
    // installed -- a pause with nothing to pump cannot be resumed by anyone.
    using EventPump = std::function<bool(int waitMs)>;
    static void setEventPump(EventPump pump);
    [[nodiscard]] static EventPump eventPump();   // to restore after swapping one in (a test)
    static bool pumpEvents(int waitMs = 0);
    static void setYieldInterval(int milliseconds);
    [[nodiscard]] static bool isRunning();

    // ---- stop on error (Script IDE board, S5.6) ------------------------------------------
    //
    // Called when a statement fails with no `try` around it, BEFORE anything
    // unwinds: the frames, the call stack and the workspace are still those of
    // the failing statement, so a debugger can pause there and show them.
    // Once per failure, never for a Stop, never from inside the hook. When it
    // returns the failure proceeds as it always did.
    using ErrorHook = std::function<void(const ExecutionLocation& at, const ICoreString& message)>;
    static void setErrorHook(ErrorHook hook);

    // ---- the frames' workspaces (Script IDE board, S1.8) ---------------------------
    //
    // The user-function calls open right now (0 when none: the script and the
    // prompt see the global space), and the variables of one workspace — depth
    // 0 is the GLOBAL space whatever is running, depth 1 the outermost call,
    // callDepth() the innermost. A frame's locals are deliberately invisible to
    // ICoreVariablesSpace (C2.7); this is how a paused debugger sees them.
    // ICoreWorkspaceInspector is the reader to use.
    static int                      callDepth();
    static std::vector<std::string> frameVariableNames(int depth);
    static bool                     frameVariable(int depth, const std::string& name, ICoreValue& out);

private:
    class Impl;
    std::unique_ptr<Impl> impl;
};

#endif

};

ICoreConsoleStatements.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreConsoleStatements.h

Spelled out rather than taken from pch.h: the module that registers here includes public ICoreEssentials headers only (R-C9).

ICoreStatementValue#

ICoreConsoleStatements.h:63 · struct · 0 declaration(s)

One evaluated argument, or one answered value.

struct ICoreStatementValue {
public:
    enum class Kind {
        Number = 0,   // a real scalar: rows = cols = 1, values = { it }
        Array  = 1,   // a real rows x cols array (empty is 0 x 0); logicals as 0/1
        Text   = 2,   // a string, in either quote spelling: `text`
        Name   = 3,   // a bare identifier with no value, or with one that cannot be
                      // an argument (`i`, a struct): `text` is the name
    };

    Kind                kind = Kind::Number;
    int                 rows = 0;
    int                 cols = 0;
    std::vector<double> values;       // ROW-major: values[r * cols + c]
    ICoreString         text;         // Text and Name only

    ICoreString         source;       // the argument as written, trimmed
    bool                isIdentifier = false;   // `source` is one bare identifier
};
};

ICoreStatementCall#

ICoreConsoleStatements.h:83 · struct · 0 declaration(s)

One claimed statement.

struct ICoreStatementCall {
public:
    ICoreString                      verb;        // `i3d_curve`
    ICoreString                      target;      // left of `=`; empty for none
    std::vector<ICoreStatementValue> arguments;   // in order
    ICoreString                      statement;   // the whole statement, trimmed
};
};

ICoreStatementResult#

ICoreConsoleStatements.h:90 · struct · 0 declaration(s)

struct ICoreStatementResult {
public:
    bool                ok = true;
    ICoreString         output;       // printed as-is; `error: ` is the handler's to add
    // The handler STOPPED at the user's request -- a long verb whose progress
    // was cancelled, or that saw the run's Stop (ED13.10). The
    // console ends the run exactly as its own Stop does: cancelled, not
    // failed, `try` does not catch it, and the request is spent. `ok` is
    // ignored when this is set.
    bool                stopped = false;
    bool                hasValue = false;
    ICoreStatementValue value;        // stored into target (or ans) when hasValue
};
};

ICoreConsoleStatements#

ICoreConsoleStatements.h:103 · class · 9 declaration(s)

class ICoreConsoleStatements {
public:
    using Handler = std::function<ICoreStatementResult(const ICoreStatementCall& call)>;

    // Claim every statement whose verb starts with `prefix`. The prefix must
    // be a legal identifier start -- `i3d_`, never `3d_` (ICore 3D plan §T.29)
    // -- and must not overlap one already registered (neither may be a prefix
    // of the other). Answers false, and registers nothing, otherwise.
    static bool registerPrefix(const ICoreString& prefix, Handler handler);
    static bool unregisterPrefix(const ICoreString& prefix);

    // Whether `name` would be claimed as a verb.
    static bool claims(const ICoreString& name);

    // A prefix may publish its VERBS (ED13.10). knows() is what
    // the script analyzer asks: a claimed name is known when its prefix
    // published no list, or the list holds it -- so a misspelt verb under a
    // prefix that published one (`i3d_curv`) is an unknown name at analysis,
    // as it would be at run time. setVerbs answers false for a prefix that is
    // not registered; unregisterPrefix drops the list.
    static bool setVerbs(const ICoreString& prefix, const ICoreStringList& verbs);
    static bool knows(const ICoreString& name);
    static ICoreStringList prefixes();                // sorted

    // Run the handler that claims `call.verb`. The console's own entry, after
    // it has parsed and evaluated the statement; a verb nothing claims answers
    // a failure. A handler that throws is reported as a failure, never
    // propagated -- as ICoreCommandEngine::run does for a command.
    static ICoreStatementResult dispatch(const ICoreStatementCall& call);

    // ---- the settle point (ED13.9) ------------------------------
    //
    // The console SETTLES when a line typed at the prompt has run, and when a
    // whole block or script has run -- the same points at which it tells the
    // editor host, once, that console variables changed (X9). At each settle,
    // every prefix that claimed a statement since the previous one has its
    // settled handler called, once. It is where a module does what should
    // happen once per run rather than once per statement: ICore 3D autosaves
    // its model there (D17), so a script of ten thousand i3d_ statements
    // saves once. A handler may run statements; they are settled next time.
    //
    // setSettledHandler answers false when `prefix` is not registered; an empty
    // handler clears it, and unregisterPrefix clears it too.
    using Settled = std::function<void()>;
    static bool setSettledHandler(const ICoreString& prefix, Settled settled);
    // The console's own call, at its settle points; a no-op when no prefix
    // claimed a statement since the last one.
    static void settle();

    ICoreConsoleStatements() = delete;
};
};

ICoreConsoleSyntax.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreConsoleSyntax.h

The console language's SHAPE, with nothing run: how a line splits into statements, where a comment starts, which words open and close a block, and what a function header says. These are the pieces ICoreConsoleInterpreter has always used to decide what to run, lifted out of its anonymous namespace so that a reader of the same text -- ICoreScriptAnalyzer, the Script IDE's problems and outline (Script IDE board, S1.3) -- reads it with the SAME rules rather than a second copy that drifts.

The definitions stay in ICoreConsoleInterpreter.cpp, beside the code that runs what they recognise. Only commandOwnsWholeLine, functionNameRefusal and refusalIn consult live state (the command registry, the evaluator's function table, the variables space); every other function is a pure map from text.

Statement#

ICoreConsoleSyntax.h:25 · struct · 0 declaration(s)

One statement carved out of a console line, plus whether a top-level ';' ended it (a ';'-terminated statement is silent on success).

struct Statement {
public:
    ICoreString text;
    bool        terminated = false;
    int         column     = 0;
};
};

Fragment#

ICoreConsoleSyntax.h:71 · struct · 0 declaration(s)

One statement of a block, with where it came from.

struct Fragment {
public:
    ICoreString text;
    bool        terminated = false;   // a ';' closed it: silent on success
    int         line       = 0;
    int         column     = 0;
};
};

FunctionSignature#

ICoreConsoleSyntax.h:93 · struct · 0 declaration(s)

function [o1, o2] = name(a, b) and its shorter spellings, read for their parts.

struct FunctionSignature {
public:
    ICoreString     name;
    ICoreStringList outputs;   // in MATLAB's order
    ICoreStringList params;
};
};

ICoreDataFileCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreDataFileCommands.h

ICoreDataFileCommands#

ICoreDataFileCommands.h:54 · class · nested Entry · 1 declaration(s)

Core MATLAB's delimited-text file functions: A = readmatrix("data.csv") read a comma-separated file as a matrix A = csvread("data.csv") MATLAB's older name for the same read A = dlmread("data.csv")...

class ICoreDataFileCommands {
public:
    // The ExternalCall shape (see ICoreExpressionEvaluator::setExternalCall).
    // Returns false when `name` is none of the eight, which is what lets
    // "unknown function" stay the last word; true with `error` set when the
    // name WAS one of them and the call failed.
    static bool call(const std::string& name, const std::vector<ICoreValue>& args,
                     size_t nargout, std::vector<ICoreValue>& results,
                     std::string& error);

    // Name and one-line description for each, for ICoreCommandGlossary. They
    // are not in ICoreExpressionEvaluator::functions() -- they are not
    // implemented there -- so nothing discovers them automatically and the
    // glossary lists them from here, the way it hand-lists the recipe verbs.
    struct Entry { const char* name; const char* signature; const char* description; };
    static const std::vector<Entry>& entries();

};
};

ICoreLogSinkRegistry.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreLogSinkRegistry.h

ICoreLogSinkRegistry#

ICoreLogSinkRegistry.h:15 · class · pImpl · nested ActiveScope · 3 declaration(s)

Lets the console cls / clear / clearAllLogs commands reach the log panels they should wipe.

class ICoreLogSinkRegistry {
public:
    using ClearFn = std::function<void()>;

    static ICoreLogSinkRegistry* instance();

    // `owner` is any stable pointer identifying the panel (typically `this`).
    // Re-registering the same owner replaces its callback.
    void registerSink(const void* owner, ClearFn clearFn);
    void unregisterSink(const void* owner);

    // RAII: mark `owner` as the active sink for the duration of a run, restoring
    // the previously active sink on scope exit (so nested runs behave).
    class ActiveScope {
    public:
        explicit ActiveScope(const void* owner);
        ~ActiveScope();
        ActiveScope(const ActiveScope&)            = delete;
        ActiveScope& operator=(const ActiveScope&) = delete;
    private:
        class Impl;
        std::unique_ptr<Impl> impl;
    };

    // Command handlers.
    bool clearActive();   // clears the active sink; false if none is active/registered
    int  clearAll();      // clears every registered sink; returns how many were cleared

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreMathToolCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreMathToolCommands.h

ICoreMathToolCommands#

ICoreMathToolCommands.h:13 · class · 1 declaration(s)

Console commands for the math tool windows' non-numeric affordances -- the things a panel shows in a combo box or a hint label rather than computes.

class ICoreMathToolCommands {
public:
    static void registerAll();
};
};

ICoreMessageTrafficCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreMessageTrafficCommands.h

ICoreMessageTrafficCommands#

ICoreMessageTrafficCommands.h:16 · class · 1 declaration(s)

Console bindings for the last run's message traffic (ICoreMessageTraffic, FEATURES_TO_ADD.md BF4.7): messageTraffic one line per event, in order: "t=1 sent Home/Send -> Home/Receive 101" "t=1 taken...

class ICoreMessageTrafficCommands {
public:
    static void registerAll();
};
};

ICoreModelConfigCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreModelConfigCommands.h

ICoreModelConfigCommands#

ICoreModelConfigCommands.h:25 · class · 5 declaration(s)

Console bindings for the simulation/solver settings held by ICoreModelConfigurator.

class ICoreModelConfigCommands {
public:
    static void registerAll();

    // The property names, in `modelConfig` print order.
    [[nodiscard]] static ICoreStringList propertyNames();
    // The current value of one property as `modelConfig` prints it; false and
    // an untouched `value` for a name the table does not have. Matching is
    // case-insensitive, as the commands are.
    [[nodiscard]] static bool readProperty(const ICoreString& name, ICoreString& value);
    // Writes one property from its text form. Empty on success; otherwise the
    // reason (unknown property, unparsable value, ambiguous choice).
    [[nodiscard]] static ICoreString writeProperty(const ICoreString& name, const ICoreString& value);
    // Every property as a `setModelConfig <property> <value>` recipe line, in
    // table order -- what an exported .icore carries and the replay applies.
    [[nodiscard]] static ICoreStringList recipeLines();
    // The command names the three bindings register.
    static const char* const COMMAND_PRINT;   // "modelConfig"
    static const char* const COMMAND_SET;     // "setModelConfig"
    static const char* const COMMAND_GET;     // "getModelConfig"
};
};

ICoreNavigationCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreNavigationCommands.h

ICoreNavigationCommands#

ICoreNavigationCommands.h:17 · class · 1 declaration(s)

Console bindings for the subsystem-navigation access setting held by ICoreSubsystemTreeNodeRegistry: navigationRootAccess print the setting and what it hides setNavigationRootAccess <on|off> switch...

class ICoreNavigationCommands {
public:
    static void registerAll();
};
};

ICoreRecipeClipboard.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreRecipeClipboard.h

ICoreRecipeClipboard#

ICoreRecipeClipboard.h:41 · class · nested Plan · 2 declaration(s)

The diagram's half of copy and paste, as TEXT: what Copy puts on the system clipboard, and how Paste decides whether text it finds there is a recipe it may build.

class ICoreRecipeClipboard {
public:
    // The text Copy puts on the clipboard: ICoreRecipeFileTransfer's source
    // header naming `node`, then serializeSelection(node, selection), its
    // handles prefixed so a paste never rebinds a handle typed at the console.
    static ICoreString compose(ICoreSubsystemTreeNode* node, const ICoreRecipeSerializer::Selection& selection);

    struct Plan {
        bool ok = false;
        ICoreString reason;      // ok == false: why the text is not a recipe this paste builds
        ICoreString script;      // ok == true: the statements to replay, one per line, aimed at
                                 // the target, names made unique, terminators off, no comments
        int creations = 0;       // statements that make an object
        int dropped = 0;         // project lines left out (solver, globals, enums, timestamps)
    };

    // Recognise `text` as a pasteable recipe and prepare it for `target`, placed
    // so the top-left of what it builds lands on `topLeft` -- in the recipe's own
    // space: relative to the target's origin anchor, +y UP, as move() takes it.
    // "Top-left" is the smallest x and the largest y any top-level move() or
    // setCorners() names, so a link routed above its blocks counts.
    // Side-effect free: reads the target's block names, runs nothing.
    static Plan prepare(const ICoreString& text, ICoreSubsystemTreeNode* target, const ICorePoint& topLeft);
};
};

ICoreRecipeFileTransfer.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreRecipeFileTransfer.h

ICoreRecipeFileTransfer#

ICoreRecipeFileTransfer.h:21 · class · nested Result · 7 declaration(s)

Native recipe file transfer: save the CONTENTS of a diagram level as an .icore script (ICoreRecipeSerializer's full-fidelity output — charts, decorations, corner routing and all, unlike the Simulin...

class ICoreRecipeFileTransfer {
public:
    struct Result {
        bool ok = false;
        ICoreString failureReason;   // set when ok == false
        ICoreStringList warnings;    // per-line replay failures and skip notes
        int blockCount = 0;      // block/subsystem creations that landed
        int linkCount = 0;       // connect() statements that landed
    };

    // Serialize `node`'s contents and write them (with the source header) to
    // `filePath` (".icore" appended when missing).
    static Result exportRecipe(ICoreSubsystemTreeNode* node, const ICoreString& filePath);

    // The text exportRecipe writes, without writing it: the source header, then
    // (when `withModelConfig`) the `setModelConfig` lines, then the serialized
    // contents. A referenced subsystem's file (ICoreReferencedFiles, BF13.2) is
    // this without the model configuration: it replays into a level of a model
    // whose solver is the model's own, and must not change it. Empty when `node`
    // is null or dead.
    static ICoreString recipeFileText(ICoreSubsystemTreeNode* node, bool withModelConfig);
    // The source header alone, "// ICoreBlocks recipe (source: <sourcePath>)",
    // with its newline: what retargetRecipe() and sourcePathFromHeader() read.
    static ICoreString sourceHeader(const ICoreString& sourcePath);

    // Read `filePath`, retarget it at `node`, and replay it into the live
    // diagram as ONE undo step.
    static Result importRecipe(ICoreSubsystemTreeNode* node, const ICoreString& filePath);

    // Same, for recipe text already in hand (a bundled template, a string built
    // in memory) rather than a file. `recipeText` may carry the same source
    // header an exported file does; it is read for the retarget and otherwise
    // ignored.
    //
    // `offset` shifts the arriving objects by (dx, dy), +y up like move(), so a
    // snippet dropped into a populated diagram can be placed clear of what is
    // already there instead of landing on top of it. Only TOP-LEVEL objects
    // move: a nested block is positioned against its own subsystem's origin
    // anchor, and shifting those too would scatter the insides of every
    // subsystem the snippet brings with it. Positions written by a chained
    // statement (`block(Gain).move(...)`) are left alone -- the serializer
    // never emits one, so this only affects hand-written recipes.
    static Result importRecipeText(ICoreSubsystemTreeNode* node, const ICoreString& recipeText,
                                   const ICorePoint& offset, const std::string& undoLabel);

    // The retarget alone, without replaying: rewrites `recipeText`'s top-level
    // creations to sit under `targetToken` and shifts their positions by
    // `offset`, returning the rewritten script.
    //
    // `targetToken` is anything block()'s parent argument accepts — a level PATH,
    // or a subsystem HANDLE bound EARLIER IN THE SAME SCRIPT. The handle form is
    // the point of exposing this: a caller can emit `h = subsystem(...)` and then
    // append a template body retargeted at `h`, so creating the container and
    // filling it replay together as one statement stream, and therefore as one
    // undo step. Retargeting against a node that has to exist first could not do
    // that.
    static ICoreString retargetRecipe(const ICoreString& recipeText, const ICoreString& targetToken,
                                  const ICorePoint& offset = ICorePoint());

    // The level an exported recipe came FROM, read off the "source:" header
    // exportRecipe writes. Empty for text with no header (hand-written, or
    // exported from Home, which leaves parents off entirely).
    //
    // Public because knowing which creations are TOP LEVEL means comparing their
    // parent against this, and more than the retarget wants that — drawing a
    // preview of a template has to skip the contents of the subsystems inside
    // it. The header format stays spelled in exactly one place.
    static ICoreString sourcePathFromHeader(const ICoreString& recipeText);

    // The shared replay loop (also used by ICoreSimulinkBridge's import):
    // recipe grammar first, plain `name = value` lines fall back to a
    // global-variable declare, //-comments are skipped. Action logging is
    // paused for the duration and `undoLabel` logged once, so the whole replay
    // is a single undoable step. Per-line failures land in warnings; ok is
    // always true (a partial replay still applied the rest).
    static Result replayIntoDiagram(const ICoreString& recipeText, const std::string& undoLabel);
};
};

ICoreRecipeInterpreter.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreRecipeInterpreter.h

The one live-object handle that needs more than a raw pointer: a weak, self-nulling reference to a chart line path. It is a studio type because the self-nulling is Qt's (ICoreChartLinePath is a QObject); naming it here rather than QPointer is what keeps this header clear of Qt. Everything else this file needs from the UI side goes through ICoreRecipeStudioBridge, included by the .cpp.

ICoreRecipeInterpreter#

ICoreRecipeInterpreter.h:357 · class · pImpl · nested Result · 12 declaration(s)

Interprets the block-diagram "recipe" mini-language used to construct a diagram from a script.

class ICoreRecipeInterpreter {
public:
    struct Result {
        bool    handled = false; // false => not a recipe line; caller falls through to its own grammar
        bool    ok      = true;  // false => recipe line failed (render as an error)
        ICoreString output;          // confirmation or error text for the history panel
    };

    static ICoreRecipeInterpreter* instance();

    // Try to evaluate `line` as a recipe statement. handled==false means the line
    // was not recipe syntax and the caller should continue with its own grammar.
    Result evaluate(const ICoreString& line);

    // Drop all handles. Called on project (re)load so pointers never dangle.
    void reset();

    // Read-only snapshots of the current handle bindings, sorted by handle name.
    // For the pointer-inspector UI; callers should check the objects' isAlive().
    ICoreList<std::pair<ICoreString, ICoreBlock*>>          heldHandles() const;
    ICoreList<std::pair<ICoreString, ICorePort*>>           heldPortHandles() const;
    ICoreList<std::pair<ICoreString, ICoreLinkBranch*>>     heldBranchHandles() const;
    ICoreList<std::pair<ICoreString, ICoreCanvasArea*>>     heldAreaHandles() const;
    ICoreList<std::pair<ICoreString, ICoreImage*>>          heldImageHandles() const;
    ICoreList<std::pair<ICoreString, ICoreCanvasTextBox*>>  heldTextBoxHandles() const;
    ICoreList<std::pair<ICoreString, ICoreCanvasSelectionModel*>> heldSelectionModelHandles() const;

    // The property keys readable via handle.get()/handle.info, with a one-line
    // description each, in display order. For the command glossary.
    static ICoreList<std::pair<ICoreString, ICoreString>> blockPropertyGlossary();

    // Resolve a diagram-level PATH token the way a recipe's parent argument
    // does: '~' and ':' are app-root aliases, leading/trailing slashes are
    // optional, and a missing app-name prefix is supplied. An empty token means
    // Home. Returns null when no such level exists.
    //
    // Public because every console command taking a "which subsystem" argument
    // (generateRecipe, useTemplate, ...) has to spell paths exactly the way the
    // recipe grammar does, and one shared spelling is the only way that stays
    // true. Handles are NOT accepted here — those belong to the interpreter's
    // own bindings, which a command argument has no business reaching into.
    static ICoreSubsystemTreeNode* resolveTreeNodeByPathToken(const ICoreString& token);
private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreRecipeSerializer.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreRecipeSerializer.h

ICoreRecipeSerializer#

ICoreRecipeSerializer.h:86 · class · nested Selection, Options, LocalSnapshot · 6 declaration(s)

Inverse of ICoreRecipeInterpreter: walks a LIVE block diagram and emits a recipe script (see ICoreRecipeInterpreter's class comment for the language itself) that reconstructs it when the script is ...

class ICoreRecipeSerializer {
public:
    // Builds the recipe script reproducing everything inside `root`. Returns
    // an empty string for a null root. The result has no trailing newline;
    // join with "\n" already applied between statements.
    static ICoreString serialize(ICoreSubsystemTreeNode* root);

    // Some of one level's objects -- what Copy and Cut put on the clipboard.
    // Every pointer must be a child of the `node` passed beside it; a selection
    // only ever holds siblings (ICoreCanvasSelectionModel's own rule).
    struct Selection {
        std::vector<ICoreBlock*> blocks;               // a subsystem block brings its whole interior
        std::vector<ICoreLinkBranch*> linkBranches;    // any branch of a link selects that link
        std::vector<ICoreCanvasArea*> areas;
        std::vector<ICoreImage*> images;
        std::vector<ICoreCanvasTextBox*> textBoxes;
    };

    // serialize()'s output restricted to `selection` at `node`'s level: the same
    // statements, addressed the same way (so ICoreRecipeFileTransfer's retarget
    // aims it at any other level), every handle starting with `handlePrefix`.
    //   * A link is written when it is selected AND the blocks at its ends are;
    //     each tee only when its own head block is. A link whose root ends
    //     outside the selection is rebuilt from its first tee that lands (see
    //     emitLinkWithoutItsRoot) -- connect() cannot make a branch with no head.
    //   * A subsystem in the selection is written whole, interior and all.
    //   * No global variables and no timestamps: a copy is new objects, perhaps
    //     in another project, not this one's state.
    static ICoreString serializeSelection(ICoreSubsystemTreeNode* node, const Selection& selection,
                                          const ICoreString& handlePrefix);
    // serialize() with two choices a caller makes (FEATURES_TO_ADD.md BF13.3):
    //   * `handlePrefix` starts every minted handle, so text that is later spliced
    //     into another script (a referenced subsystem's file, expanded into the
    //     project it is opened in) cannot rebind a handle of the script around it;
    //   * `savedAsPath`, when set and true for a subsystem, writes that subsystem
    //     as a REFERENCE instead of its contents: the pair
    //         //@reference-begin <handle> <path>
    //         ...its gates only, with their ports...
    //         //@reference-end
    //     where <path> is its Subsystem block's Referenced File. The gates keep the
    //     face's ports, and so every link to them, replayable on their own; whoever
    //     opens the text puts the file's contents in place of the pair
    //     (ICoreReferencedFiles::expand). Both lines are comments, so replaying the
    //     text unexpanded gives the subsystem its gates and nothing else.
    struct Options {
        ICoreString handlePrefix;
        std::function<bool(ICoreSubsystemTreeNode*)> savedAsPath;
    };
    static ICoreString serialize(ICoreSubsystemTreeNode* root, const Options& options);
    static const char* const kReferenceBegin;   // "//@reference-begin "
    static const char* const kReferenceEnd;     // "//@reference-end"

    // Inverse of the ';' terminator serialize() appends: returns `line` trimmed,
    // with one trailing ';' removed if present. Safe on hand-written recipe text
    // (which has no terminator) and on comment/blank lines.
    static ICoreString stripStatementTerminator(const ICoreString& line);

    // ---------------- Per-subsystem ("local") texts -- per-subsystem undo, H3
    //
    // serialize() emits one script for a whole tree. A LOCAL text is the part of
    // that script ONE subsystem level owns: its own blocks (gates included, with
    // their ports), links, areas, images, text boxes and charts, and for each
    // child subsystem its subsystem block, that block's face-port descriptions and
    // a `//@child <NodeId>` marker where the child's own content goes -- never the
    // child's interior. A comment to the interpreter, so a marker can never be
    // replayed by mistake.
    //
    // It is what a per-subsystem history stores and diffs, so it must change when
    // -- and only when -- that level changes:
    //   * handles are minted from the node's own NodeId (`n<id>_<name>`), and a
    //     child's subsystem block is always `s<childId>`, so a node's text does
    //     not depend on any OTHER node, and handles are unique across a
    //     composition by construction -- composing is pure splicing, no string in
    //     the user's content is ever rewritten;
    //   * a node's own objects name their parent as `s<id>` (Home: no parent
    //     token), never by path, so renaming it or an ancestor does not touch it;
    //   * timestamps and global variables are kept OUT of the text (timestamps
    //     restamp on every structural edit anywhere below; globals belong to no
    //     level) and travel beside it.
    // Same NodeIds, same diagram => same texts; the ids are what make that hold,
    // so a text is only meaningful against the tree whose ids it names.
    struct LocalSnapshot {
        ICoreString text;
        std::vector<std::uint64_t> children;     // child NodeIds, in marker order
        long long createdTimeMs = 0;
        long long lastModifiedTimeMs = 0;
    };
    using LocalMap = std::unordered_map<std::uint64_t, LocalSnapshot>;

    static LocalSnapshot serializeLocal(ICoreSubsystemTreeNode* node);
    // `root` and every subsystem below it, keyed by NodeId.
    static LocalMap serializeLocalTree(ICoreSubsystemTreeNode* root);
    // The global-variable declarations serialize() puts at the top of Home's script.
    static ICoreString serializeGlobals();

    // Rebuilds a replayable whole-diagram script for Home from local texts: Home's
    // text with every `//@child` marker replaced, recursively, by that child's
    // text, then the timestamp lines, with `globals` first. Replaying it and
    // calling serialize(Home) gives back what serialize(Home) gave before -- the
    // invariant the `history` suite pins. `subsystemOrder` receives the child
    // NodeIds in the order their subsystem() statements appear, which is the order
    // a replay creates them in. Returns false, and leaves `out` alone, when a
    // marker names a node the map does not hold.
    static bool compose(std::uint64_t homeId, const LocalMap& map, const ICoreString& globals,
                        ICoreString* out, std::vector<std::uint64_t>* subsystemOrder = nullptr);
};
};

ICoreRecipeToSimulinkEmitter.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreRecipeToSimulinkEmitter.h

ICoreRecipeToSimulinkEmitter#

ICoreRecipeToSimulinkEmitter.h:14 · class · pImpl · 7 declaration(s)

Accumulates a MATLAB/Simulink model-building script as a side effect of a recipe running for real against the live model.

class ICoreRecipeToSimulinkEmitter {
public:
    ICoreRecipeToSimulinkEmitter();
    ~ICoreRecipeToSimulinkEmitter();

    // Append one line of MATLAB source (e.g. an add_block(...) call) to the
    // script.
    void addLine(const ICoreString& line);

    // A block() or subsystem() statement succeeded. blockType is the
    // recipe's bare leaf type name (e.g. "Gain"), or "Subsystem" for a
    // subsystem's own face block (mapped to Simulink's built-in Subsystem
    // block - see simulinkLibraryPathForType). blockPath is the block's
    // full path relative to the model root, slash-separated, e.g. "Gain1"
    // at the top level or "Sub1/Gain1" nested one subsystem deep - the
    // caller (ICoreRecipeInterpreter) is responsible for resolving nesting;
    // this class only ever deals in already-resolved paths, never live
    // tree-node pointers. Emits the model-opening boilerplate once, on the
    // first call. A blockType with no known Simulink library equivalent
    // produces a "%" comment instead of a broken add_block call.
    void accumulateBlockCreated(const ICoreString& blockType, const ICoreString& blockPath);

    // A block's position was (re)established - either the implicit
    // placement every block()/subsystem() gets at creation, or an explicit
    // move(). (x, y) is relative to the origin anchor, +y up - the same
    // convention the recipe's move() takes; width/height are the block UI's
    // pixel size, used to turn a point into Simulink's [left top right
    // bottom] Position rect. A no-op if blockPath was never successfully
    // added (accumulateBlockCreated skipped it for lack of a type mapping) -
    // set_param on a block that was never add_block'd would be broken MATLAB.
    void accumulateBlockMoved(const ICoreString& blockPath, double x, double y,
                              double width, double height);

    // A plain port-to-port connect() statement succeeded (branching off an
    // existing link is a later phase). tailBlockPath/headBlockPath are full
    // model-relative block paths, same convention as accumulateBlockCreated;
    // tailPortNumber/headPortNumber are 1-based (Simulink convention) output/
    // input port numbers. Both blocks must live in the same subsystem (the
    // interpreter already enforces this before calling), so their paths
    // share the same containing system. A no-op if either endpoint's block
    // was never successfully added.
    void accumulateConnected(const ICoreString& tailBlockPath, int tailPortNumber,
                             const ICoreString& headBlockPath, int headPortNumber);

    // A .setConfig() statement succeeded. blockType is the recipe's bare
    // leaf type name; blockPath is the block's full model-relative path
    // (same convention as accumulateBlockCreated); varName is the recipe's
    // config variable name (e.g. "Gain Value"); value is the string it was
    // set to. Produces a set_param(...) call if (blockType, varName) has a
    // known Simulink parameter mapping, else a "%" comment - same skip
    // convention as an unmapped block type. A silent no-op (not even a
    // comment) if blockPath was never successfully added - the block itself
    // is already fully explained by accumulateBlockCreated's own comment.
    void accumulateConfigSet(const ICoreString& blockType, const ICoreString& blockPath,
                             const ICoreString& varName, const ICoreString& value);

    // A recipe statement was recognized and succeeded against the live model,
    // but has NO Simulink equivalent at all - plot(), selectionModel(), and
    // the canvas decoration objects area()/image()/textbox() (see
    // ICoreRecipeInterpreter's class comment). Unlike an unmapped block type
    // or config key, there is no table to consult here - these statements
    // can never gain a mapping, so this always emits a "%" comment.
    // `description` is a short human-readable label for what was skipped,
    // e.g. "area() in Sub1" or "plot()".
    void accumulateUnsupportedStatement(const ICoreString& description);

    // The generated script so far, one statement per line, no trailing
    // newline. Empty if nothing has been accumulated.
    ICoreString script() const;

    // Drop everything accumulated so far.
    void reset();

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreScriptAnalyzer.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreScriptAnalyzer.h

ICoreScriptAnalyzer#

ICoreScriptAnalyzer.h:41 · class · nested FunctionOutline, Symbol, Outline, Analysis · 0 declaration(s)

Reads an .icore script WITHOUT RUNNING IT and says what is wrong with it and what is in it (Script IDE board, S1.3, S1.4).

class ICoreScriptAnalyzer {
public:
    // A script-defined function: its header's parts and the lines it spans.
    struct FunctionOutline {
        ICoreString     name;
        ICoreStringList inputs;
        ICoreStringList outputs;
        int             line    = 0;   // the `function` line, 1-based
        int             endLine = 0;   // its `end`; 0 when it has none
    };

    // A name at a place: a top-level assignment's variable, or a `run` target.
    struct Symbol {
        ICoreString name;
        int         line   = 0;   // 1-based
        int         column = 0;   // 1-based
    };

    struct Outline {
        std::vector<FunctionOutline> functions;
        // Each variable the script's own workspace assigns, once, at its FIRST
        // assignment (a function's locals are not the script's).
        std::vector<Symbol>          assignments;
        // `run <name>` targets, as written (without the .icore suffix).
        std::vector<Symbol>          runTargets;
    };

    struct Analysis {
        std::vector<ICoreScriptDiagnostic> diagnostics;   // in line order
        Outline                            outline;
    };

    // `file` names the script in every diagnostic. `siblings` are the other
    // scripts that share the session -- (file name, text) -- read only for the
    // functions they define, to warn when two scripts define the same name.
    static Analysis analyze(const ICoreString& text, const ICoreString& file = ICoreString(),
                            const std::vector<std::pair<ICoreString, ICoreString>>& siblings = {});
};
};

ICoreScriptDiagnostic.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreScriptDiagnostic.h

One problem with an .icore script, in the shape an editor can place: which file, which line, which column, how bad, and what to say (the Script IDE board S1.2). Two producers fill it and nothing else:

  • a RUN -- ICoreConsoleInterpreter::runLines / runScript put one in

LineFailure::diagnostic when a script fails, with the call stack that led to the failing statement;

  • the ANALYZER -- ICoreScriptAnalyzer::analyze reads a script without

running it and answers a list of these.

It sits BESIDE the text failure (LineFailure's line/text/output), which is kept: every surface that prints "script:line: message" still prints it, and only a surface that wants to underline, link or list a problem reads this.

ICoreScriptStackFrame#

ICoreScriptDiagnostic.h:24 · struct · 0 declaration(s)

One frame of the call stack a run failure carries: a script (function is empty) or a script-defined function, and the line in file that was running in it.

struct ICoreScriptStackFrame {
public:
    ICoreString file;
    ICoreString function;
    int         line = 0;
};
};

ICoreScriptDiagnostic#

ICoreScriptDiagnostic.h:30 · struct · 0 declaration(s)

struct ICoreScriptDiagnostic {
public:
    enum class Severity { Error, Warning, Info };

    ICoreString file;
    int         line   = 0;   // 1-based; 0 when unknown
    int         column = 0;   // 1-based; 0 when unknown -- a run rarely knows one
    Severity    severity = Severity::Error;
    // The message WITHOUT the console's "error: " prefix, which is rendering.
    ICoreString message;
    // MATLAB's error identifier (`error('pkg:id', msg)`), or a stable name for
    // an analyzer check ("icore:unclosedBlock"); empty when there is none.
    ICoreString identifier;
    // Innermost frame first: stack.front() is where it failed, stack.back() the
    // script the run started in. Empty for an analyzer diagnostic.
    std::vector<ICoreScriptStackFrame> stack;
};
};

ICoreScriptDiagnosticText#

ICoreScriptDiagnostic.h:48 · class · 2 declaration(s)

Rendering, kept out of the struct so the struct stays a plain value.

class ICoreScriptDiagnosticText {
public:
    // "error", "warning" or "info".
    static ICoreString severityName(ICoreScriptDiagnostic::Severity severity);

    // "<file>:<line>:<column>: <severity>: <message>" -- the compiler shape an
    // editor and a terminal both know -- with the parts that are unknown left
    // out, followed by one "    at <function> (<file>:<line>)" line per stack
    // frame when the stack has more than one.
    static ICoreString format(const ICoreScriptDiagnostic& diagnostic);
};
};

ICoreScriptLayout.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreScriptLayout.h

ICoreScriptLayout#

ICoreScriptLayout.h:19 · class · 8 declaration(s)

How an .icore script is laid out on the page: which line opens a block, which line closes or splits one, what the indent of a line should be, and how a line is commented out.

class ICoreScriptLayout {
public:
    // One indent level, in spaces. The editor's Tab inserts the same four.
    static constexpr int INDENT_WIDTH = 4;

    // The line's leading whitespace, as written.
    static ICoreString leadingWhitespace(const ICoreString& line);

    // True when the line's first word closes or splits the block it is in --
    // `end`, `else`, `elseif`, `case`, `otherwise`, `catch` -- so its indent
    // comes from the block's opener rather than from the line above it.
    static bool startsWithDedentKeyword(const ICoreString& line);

    // The indent for a new line typed after `line` (Enter): `line`'s own
    // whitespace, one level more when `line` leaves a block open
    // (`for k = 1:3`) or opens an arm (`else`, `case 2`). A one-line block
    // (`for k = 1:3, s = s + k; end`) opens nothing.
    static ICoreString indentForLineAfter(const ICoreString& line);

    // The indent a line starting with a dedent keyword should have, given the
    // lines above it: its opener's indent for `end`/`else`/`elseif`/`catch`,
    // one level in from the `switch` for `case`/`otherwise`. False when no
    // block is open above it, and `indent` is left alone.
    static bool indentForClosingLine(const ICoreStringList& linesAbove,
                                     const ICoreString& line, ICoreString& indent);

    // Comment the line out, or back in. A line whose code starts with '#' or
    // '%' is uncommented (the marker and one following space go); any other
    // non-blank line gets "# " after its indent, '#' being this console's
    // whole-line comment. Blank lines come back unchanged.
    static ICoreString toggleLineComment(const ICoreString& line);

    // The same over several lines as ONE decision (Script IDE board, S3.14):
    // when every non-blank line is already a comment, each is uncommented;
    // otherwise every non-blank line gets "# " -- an already-commented one
    // too, so toggling back restores the lines exactly. Blank lines are kept.
    static ICoreStringList toggleLinesComment(const ICoreStringList& lines);

    // The 0-based index of the line that closes the block `lines[start]`
    // opens, or `start` itself when that line opens nothing (or closes what
    // it opens, a one-line block). -1 when the block never closes. What "Run
    // Current Line" runs when the caret is on a block's first line: a `for`
    // line on its own is a statement that cannot run (Script IDE board, S4.5).
    static int blockEndLine(const ICoreStringList& lines, int start);

    // The bracket that pairs with the one at `position` in `text` (a whole
    // script, lines joined by '\n'): '(' with ')', '[' with ']', '{' with '}',
    // in either direction. -1 when the character there is not a bracket, or
    // has no partner. Brackets inside a "..." string or a comment (a whole-line
    // '#', a '%' outside brackets) are not brackets (Script IDE board, S3.6).
    static int matchingBracket(const ICoreString& text, int position);

    // Re-indent a whole script by its block structure, INDENT_WIDTH spaces a
    // level. Blank lines become empty; lines inside a %{ ... %} block comment
    // are kept as written. A surplus `end` never takes the level below zero.
    static ICoreStringList reindent(const ICoreStringList& lines);

};
};

ICoreScriptRunner.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreScriptRunner.h

ICoreScriptRunner#

ICoreScriptRunner.h:16 · class · nested LineFailure · 31 declaration(s)

Headless runner for the saved command scripts under "<active project folder>/scripts".

class ICoreScriptRunner {
public:
    // The custom script file extension (single source of truth).
    static const ICoreString& scriptExtension();

    // The active project folder itself. It is the console's working directory
    // in every sense that matters to a script: `run <name>` looks for its
    // .icore under it, and the C14.4 data-file functions resolve a RELATIVE
    // path against it. Deliberately not the process working directory, which
    // is wherever the binary happened to be launched from and would make the
    // same .icore read a different file on two machines.
    static std::filesystem::path projectFolderPath();

    // "<active project folder>/scripts", created if missing.
    static std::filesystem::path scriptsFolderPath();

    // Every script in the scripts folder AND ITS SUBFOLDERS (the Script IDE
    // board, D5), as a relative name with '/' between parts --
    // "fit.icore", "tools/fit.icore" -- sorted case-insensitively. That name
    // is the script's key everywhere: `run tools/fit`, a tab, session.ini.
    static ICoreStringList listScripts();
    // Every folder under the scripts folder, empty ones included, the same way.
    static ICoreStringList listScriptFolders();

    // The explorer's file operations (S4.2), on relative names. Each answers
    // an empty string on success or what went wrong; a name that would leave
    // the scripts folder ("..", an absolute path) is refused. A rename moves a
    // script's (or a folder's scripts') breakpoints, caret line and the
    // last-opened mark with it.
    static ICoreString createScriptFolder(const ICoreString& folder);
    static ICoreString renameScriptEntry(const ICoreString& from, const ICoreString& to);
    static ICoreString duplicateScript(const ICoreString& fileName, ICoreString* copyName = nullptr);
    static ICoreString deleteScriptFolder(const ICoreString& folder);   // and everything in it

    // The script the Script Runner had open, remembered per project so that
    // reopening the panel - in this session or a later one - lands back where
    // the user left off instead of on whatever sorts first. Empty when there is
    // none, or when the remembered file has since been deleted.
    //
    // Stored in "<scripts folder>/session.ini" rather than in the project's
    // .iproj: it is UI session state, not diagram content, and putting it in
    // the recipe would make selecting a script an undo step. listScripts()
    // filters on scriptExtension(), so the ini never shows up as a script.
    static ICoreString lastOpenedScript();
    static void setLastOpenedScript(const ICoreString& fileName);

    // Per-script editor state, in the same session.ini and for the same reason
    // (Script IDE board, S5.1, S4.11): the breakpoints set on a script (1-based
    // lines, sorted, no duplicates) and the line the caret was on. An empty
    // list / 0 removes the entry. A script that no longer exists answers
    // nothing -- its entries are left for a rename to find, not trusted.
    static std::vector<int> breakpointsFor(const ICoreString& fileName);
    static void setBreakpointsFor(const ICoreString& fileName, const std::vector<int>& lines);
    static int  caretLineFor(const ICoreString& fileName);
    static void setCaretLineFor(const ICoreString& fileName, int line);

    // The Script Runner's layout, in the same session.ini (the Script IDE board
    // S4.18): the scripts open in tabs, in tab order -- the active one is
    // lastOpenedScript() -- and the sizes of one of its split panes, by name.
    // openScripts() leaves out a script that no longer exists; an empty list
    // removes the entry.
    // A breakpoint's CONDITION (an expression; the run pauses there only when
    // it holds) and HIT COUNT (pause from the Nth time the line is reached),
    // per script and line (Script IDE board, S5.7). Empty / 0 means none and
    // removes the entry. Keyed by LINE: a breakpoint that moves with an edit
    // leaves its condition behind, and the Debug menu shows what is set.
    static ICoreString breakpointConditionFor(const ICoreString& fileName, int line);
    static void setBreakpointConditionFor(const ICoreString& fileName, int line, const ICoreString& condition);
    static int  breakpointHitCountFor(const ICoreString& fileName, int line);
    static void setBreakpointHitCountFor(const ICoreString& fileName, int line, int hits);

    // Whether the Script IDE saves silently (Script IDE board, D1): off by
    // default -- an explicit save, a dirty dot and a question on close -- and
    // switched on per project from its File menu.
    static bool scriptAutosave();
    static void setScriptAutosave(bool on);

    static ICoreStringList openScripts();
    static void setOpenScripts(const ICoreStringList& fileNames);
    static std::vector<int> paneSizes(const ICoreString& pane);
    static void setPaneSizes(const ICoreString& pane, const std::vector<int>& sizes);
    // The Script IDE's panels the user put away (S7.1) -- "navigator",
    // "output", "problems", "variables", "debug" -- per project.
    static ICoreStringList hiddenPanels();
    static void setHiddenPanels(const ICoreStringList& panels);
    // Where each of its docking panels sits beside the code (S7.16), in
    // order: "output=bottom", "problems=right", ... -- per project.
    static ICoreStringList panelDocks();
    static void setPanelDocks(const ICoreStringList& docks);

    // Failure detail for the run* entry points below. `line` is 1-based;
    // 0 means the script never ran a line (e.g. the file could not be read).
    struct LineFailure {
        int         line = 0;
        ICoreString text;     // the failing command line, trimmed
        ICoreString output;   // what the console printed for it
        // Stopped by the user (ICoreConsoleInterpreter::requestStop), not failed.
        bool        cancelled = false;
        // The failure placed for an editor: innermost file and line, message,
        // identifier and call stack (Script IDE board, S1.2). The frames name
        // scripts by file name, as listScripts() spells them.
        ICoreScriptDiagnostic diagnostic;
    };

    // Run command lines handed in directly — the programmatic entry point for
    // C++ callers that assemble their own command sequence, no file involved.
    // Blank lines and '#' comments are skipped without failing. Stops at the
    // first failing line and reports it through `failure` (when given).
    //
    // MUST be called on the GUI thread: it touches the variables space, command
    // engine and notification center. Same for the two entry points below.
    static bool runLines(const ICoreStringList& lines, LineFailure* failure = nullptr);

    // Run one saved script from the scripts folder, by file name (as returned
    // by listScripts()). A file that cannot be read counts as a failure.
    static bool runScript(const ICoreString& fileName, LineFailure* failure = nullptr);

    // Run every script in the folder, in name order. Stops the whole batch at
    // the first failing line and posts a warning notification; otherwise posts
    // a friendly summary. Returns true only if every line of every script
    // succeeded (empty folder = true).
    static bool runAllScripts();

};
};

ICoreScriptTargets.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreScriptTargets.h

ICoreScriptTargets#

ICoreScriptTargets.h:35 · class · final · 9 declaration(s)

ICoreScriptTargets Deploy to Hardware from a script (the agent-bridge board, AB.23): what the recipe interpreter's target handles do, kept out of the interpreter so the rules live in one short file.

class ICoreScriptTargets final {
public:
    // A new target in `language` (one of ICoreCodeEngine::getAvailableCodeTypes(),
    // any case; empty is Python), named after it, exporting Home into
    // <project>/code/<name>. Null with `message` set when the engine refuses.
    static ICoreCodeExportTarget* create(const std::string& language, std::string& message);

    // The existing target called `name` (case-insensitive); null, with the
    // names that do exist in `message`, when there is none.
    static ICoreCodeExportTarget* findByName(const std::string& name, std::string& message);

    // Still one of the engine's targets: the panel can delete a target a
    // script holds a handle on.
    static bool isAlive(const ICoreCodeExportTarget* target);

    // Keys: Name, Language, Source, Folder, Verification, Tolerance, Pulse Width,
    // Amplitude Min, Amplitude Max, Seed, Compiler Scan, Compiler Path -- any
    // case, spaces optional. A value that is not allowed is refused with the
    // values that are.
    static bool setConfig(ICoreCodeExportTarget* target, const std::string& key,
                          const std::string& value, std::string& message);
    static bool getConfig(const ICoreCodeExportTarget* target, const std::string& key,
                          std::string& value);

    // Every key with its value and what it accepts (listConfig / info).
    static std::string describe(const ICoreCodeExportTarget* target);

    // Exports now. `report` says what happened: the files written or changed in
    // the target's folder, or why nothing was -- every error the export logged,
    // and any question it asked.
    static bool fire(ICoreCodeExportTarget* target, std::string& report);

    static bool remove(ICoreCodeExportTarget* target, std::string& message);

    // One line per target: name, language, source, folder (`targets`).
    static std::string listAll();

    // `targets` -- the console command that lists them.
    static void registerCommands();

    // The key names, in the order describe() prints them.
    static std::vector<std::string> keyNames();
};
};

ICoreSimulationCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreSimulationCommands.h

ICoreSimulationCommands#

ICoreSimulationCommands.h:25 · class · final · nested Signal, Report · 3 declaration(s)

ICoreSimulationCommands simulate (the agent-bridge board, AB.22): run the open model from its start time to its stop time, with the solver settings it has, and say what the signals did -- so a sc...

class ICoreSimulationCommands final {
public:
    // One probed signal: a port, or one element of a matrix-valued port.
    struct Signal {
        std::string name;          // "Home/Scope:in0", "Home/Mux:out0[1,0]"
        double first = 0, last = 0, min = 0, max = 0, mean = 0;
        bool nonFinite = false;    // a NaN or an infinity appeared at some step
    };

    struct Report {
        bool ok = false;
        std::string error;                    // why it did not run, or stopped
        std::vector<std::string> problems;    // every error and warning the run logged
        double startTime = 0, stopTime = 0;
        long long steps = 0;
        std::vector<Signal> signals;
        std::string csvFile;                  // written when asked for
    };

    // `blockPaths` as getBlock() takes them ("Scope", "Home/Controller/Kp");
    // empty probes every sink block in the model. `csvPath` empty writes no
    // file; a relative one is under the project folder.
    static Report run(const std::vector<std::string>& blockPaths, const std::string& csvPath);

    // The report as the console prints it.
    static std::string format(const Report& report);

    static void registerAll();
};
};

ICoreToolchainCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreToolchainCommands.h

ICoreToolchainCommands#

ICoreToolchainCommands.h:30 · class · 1 declaration(s)

Console bindings for the toolchain registry (ICoreToolchains, ICoreServices; toolchains row TC4.1) -- the same answers the Toolchains panel in the rail's Tools section shows: toolchain same as `too...

class ICoreToolchainCommands {
public:
    static void registerAll();
};
};

ICoreUpdateCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreUpdateCommands.h

ICoreUpdateCommands#

ICoreUpdateCommands.h:33 · class · 2 declaration(s)

ICoreUpdateCommands Binds checkUpdates to the console, so the update decision is assertable from a script and from --console with no GUI and no clicking -- the same shape the version command has.

class ICoreUpdateCommands {
public:
    ICoreUpdateCommands() = delete;

    static void registerAll();
};
};

ICoreWorkspaceCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreWorkspaceCommands.h

ICoreWorkspaceCommands#

ICoreWorkspaceCommands.h:35 · class · 1 declaration(s)

Core MATLAB's workspace and session commands: clc clear the active output log clear remove every variable from the variables space clear x y remove those variables (an unknown name is a failure) cl...

class ICoreWorkspaceCommands {
public:
    static void registerAll();
};
};

ICoreWorkspaceInspector.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/ICoreWorkspaceInspector.h

ICoreWorkspaceInspector#

ICoreWorkspaceInspector.h:24 · class · nested Entry · 4 declaration(s)

What whos knows about a workspace, as data rather than as a printed table (Script IDE board, S1.8): each variable's name, MATLAB class, size, byte count and a one-line preview of its value.

class ICoreWorkspaceInspector {
public:
    struct Entry {
        ICoreString name;
        // MATLAB's class name for the value: "double", "logical", "char",
        // "double (complex)", "sym", "tf", "struct", "function_handle", …;
        // "unknown" when the stored text does not read back as a value.
        ICoreString className;
        int         rows    = 1;
        int         columns = 1;
        ICoreString size;       // "rows x columns" as `whos` prints it: "2x3"
        size_t      bytes = 0;  // what `whos` reports; 0 where it cannot say honestly
        // One line, at most ~60 characters: the value itself when it is short
        // (a scalar, a small matrix, a string), else its size and class.
        ICoreString preview;
    };

    // One value, described under `name`.
    static Entry describe(const ICoreString& name, const ICoreValue& value);

    // The global workspace, in ASCII order (uppercase first, as `whos` lists).
    static std::vector<Entry> globals();

    // The workspace of the user-function call at `depth` (1 = outermost,
    // ICoreConsoleInterpreter::callDepth() = innermost); 0 is the global one.
    // Empty when no call is open at that depth.
    static std::vector<Entry> frameLocals(int depth);

    // The workspace the running statement sees: the innermost call's locals
    // while one is open, else the global workspace.
    static std::vector<Entry> current();
};
};

ICoreMatlabCommandBridge.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/MatlabBridge/ICoreMatlabCommandBridge.h

ICoreMatlabCommandBridge#

ICoreMatlabCommandBridge.h:30 · class · nested Result · 3 declaration(s)

Facade over the console -> MATLAB command translation, the sibling of ICoreSimulinkBridge one directory up: that bridge carries the DIAGRAM recipe language to Simulink, this one carries the console...

class ICoreMatlabCommandBridge {
public:
    struct Result {
        bool ok = false;
        ICoreString failureReason;   // set when ok == false
        ICoreStringList warnings;    // user-facing skip notes, one per refused statement
        int statementCount = 0;      // statements seen
        int translatedCount = 0;     // statements that crossed
        // Import only. `filePath` is the .icore actually written -- report
        // THIS, not the path asked for. `ignoredCount` counts the session
        // noise (clc, figure, xlabel) dropped on the way in: dropped, but
        // never silently.
        ICoreString filePath;
        int ignoredCount = 0;
    };

    // Translate ONE console line (which may hold several ';'-separated
    // statements) into MATLAB text. Returns an empty string when nothing on
    // the line could cross; every refusal appends one warning. Helper names
    // the translation used are collected into `usedHelpers` when given (the
    // caller appends their bodies via ICoreMatlabCommandCatalog::helperBody).
    static ICoreString translateLine(const ICoreString& line,
                                     ICoreStringList& warnings,
                                     std::set<std::string>* usedHelpers = nullptr,
                                     std::set<std::string>* assignedNames = nullptr,
                                     // Names this SCRIPT defines with `function … end`
                                     // (C2.7). A call to one crosses verbatim, because
                                     // MATLAB spells a local-function call exactly as the
                                     // console does — but only if the translator knows it
                                     // is one, and a script's own definition may sit
                                     // BELOW the call (MATLAB requires it to). The live
                                     // session's registry is consulted as well, for the
                                     // one-line `toMatlab f(2)` at a prompt.
                                     const std::set<std::string>* definedFunctions = nullptr);

    // Whole console script -> standalone .m text: header, translated lines
    // (blank lines and '#' comments carried across as blank lines and
    // '%' comments), ICORE-UNSUPPORTED comments for refusals, used helper
    // bodies appended as local functions.
    static ICoreString translateScript(const ICoreStringList& lines, Result& result);

    // translateScript + write to `filePath` (".m" appended when missing).
    static Result exportToMatlabScript(const ICoreStringList& lines,
                                       const ICoreString& filePath);

    // ---- the import direction (MATLAB -> console) -------------------------
    //
    // The mirror of the three above, engine in ICoreMatlabCommandImporter.
    // Same honesty rule read the other way: a MATLAB statement crosses only
    // when the mapping is a catalog row or a deterministic composition of
    // catalog rows, and what does not cross comes out as a
    // '# ICORE-NOT-IMPORTED(reason): <matlab line>' comment, so the produced
    // .icore always accounts for the whole .m.
    //
    // The two directions do NOT share a marker, and the difference is not
    // cosmetic. '% ICORE-UNSUPPORTED(...)' is the EXPORT's: its payload is
    // console text that could not become MATLAB, which is why an import
    // restores it live. '# ICORE-NOT-IMPORTED(...)' is the IMPORT's: its
    // payload is MATLAB the console cannot run, so it stays a comment
    // forever. One marker for both would make an import refusal export as a
    // '%' comment and come back as console on the next round trip.

    // One MATLAB logical line -> zero or more console lines (one MATLAB
    // statement is one console line, so `a = 1, b = 2` yields two). A refusal
    // appends a warning and yields nothing for that statement, never half a
    // line; a soft warning (a mapping sound only under a stated condition) is
    // appended to the same list and does not stop the statement.
    static ICoreStringList translateFromMatlabLine(const ICoreString& line,
                                                   ICoreStringList& warnings);

    // Whole .m text -> .icore text: a '#' header, comments carried across,
    // refusals as ICORE-NOT-IMPORTED comments, the bridge's own icp_* local
    // functions stripped silently and any other function block refused.
    // A file whose first statement is `function` is refused whole.
    static ICoreString translateFromMatlabScript(const ICoreString& mText, Result& result);

    // Read `mPath`, translate, and write "<scripts folder>/<scriptName>.icore"
    // (scriptName empty -> the .m file's stem). An existing target is a
    // failure unless `overwrite`: the import never overwrites silently.
    static Result importFromMatlabScript(const ICoreString& mPath,
                                         const ICoreString& scriptName,
                                         bool overwrite);

    // Console commands: `toMatlab` / `matlabExportScript` (out) and
    // `fromMatlab` / `matlabImportScript` (in). Called once from Initialization.
    static void registerConsoleCommands();
};
};

ICoreMatlabCommandCatalog.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/MatlabBridge/ICoreMatlabCommandCatalog.h

ICoreMatlabCommandCatalog#

ICoreMatlabCommandCatalog.h:36 · class · nested Form, Entry, ImportEntry, MultiOutputEntry, ImportRefusal, ToolboxTag, ToolboxMark · 14 declaration(s)

The dictionary between the console math language (ICoreExpressionEvaluator's functions plus the operator layer) and MATLAB: one entry per console function, saying whether it can cross to MATLAB at ...

class ICoreMatlabCommandCatalog {
public:
    enum class Support {
        Yes,   // translates via one of Entry::forms (helpers included)
        None,  // no MATLAB equivalent; reported and skipped, see notes
    };

    // How a parity case over this function must compare the two sides.
    // How a parity case for this row must compare the two sides. The names
    // pair with the parity corpus's `kind` strings (exact, sorted, logical,
    // string, complex): this enum is what a row DECLARES, the corpus is what
    // a case does, and they are meant to say the same thing.
    enum class Compare {
        Exact,      // element-by-element |a-b| <= tolerance
        SortedRows, // sort rows on both sides first (eigenvalue/root order
                    // is implementation-defined on both sides)
        Logical,    // numeric AND MATLAB's result must be a logical array. The
                    // class check is the whole point: the console answers 1.0
                    // or 0.0 as a double until it has a logical flag, and a
                    // numeric comparison alone can never see that divergence
        Skip,       // translation is sound but the VALUE is not comparable
                    // (nondeterministic, or representation-unique); see notes
        Property,   // both sides are RIGHT and not equal, and what they share
                    // is a property rather than digits: a degenerate linear
                    // programme's x is one vertex here and another there, and
                    // each is feasible with the same objective. The row's
                    // notes state the property and the parity case carries it
                    // as MATLAB text over `actual` and `expected`
                    // (the toolbox board's T0.10 and X4). It is neither a
                    // wider band -- a wrong answer fails a property -- nor
                    // Skip, which asserts nothing
    };

    // ONE accepted call format. A row holds a LIST of these (Entry::forms),
    // and the list is the whole of what the row accepts: the bridge tries them
    // in order and refuses a call matching none of them.
    //
    // ⚠ THIS REPLACES the four fixed slots (matlabTemplate/argCount plus
    // altTemplate..altTemplate3) this Entry carried until 2026-09-05. Their
    // own comment said "a FIFTH form must not be added on top of this one" and
    // called the vector<Form> refactor owed; X12 is that refactor, and it was
    // owed for a reason four row notes had already written down -- with four
    // slots, `pid(Kp, Ki)` could not cross at all (T1.30), `chirp` and
    // `gauspuls` crossed at three of five arities each (T2.26), `optimoptions`
    // at four of five (T5.11), and `tf2ss` had to grade its two meanings with
    // one Compare because the mode was on the ENTRY and not on the FORM
    // (T2.8). A row may now hold as many forms as MATLAB has.
    struct Form {
        // MATLAB expression with %1..%N argument slots (they do NOT stop at
        // nine -- see readTemplateSlot).
        const char* matlabTemplate = "";
        // How many console arguments this form takes. When tailStride is set
        // this is the SHORTEST arity the form accepts, not the only one.
        int argCount = 0;
        // A repeatable NAME-VALUE TAIL, and the reason a form list alone was
        // not enough. MATLAB's option-set and estimator names take an
        // UNBOUNDED tail -- optimoptions(solver, name, value, ...) is legal at
        // every odd arity there is -- so no list of fixed arities can ever be
        // the whole of what the row accepts, and enumerating four of them was
        // never going to close it.
        //
        // 0 is a fixed-arity form. n > 0 means the form also matches
        // argCount + k*n for every k >= 1, and the template's LAST n slots are
        // the group that repeats. The template of such a form must therefore
        // END with "%<argCount>)" -- expansion appends ", %argCount+1, ..."
        // before the closing parenthesis and nothing else, which is what makes
        // one template answer for every arity. selfCheck() asserts the shape
        // rather than trusting it.
        //
        // ⚠ The two directions are NOT symmetric and the asymmetry is
        // deliberate. The EXPORT side expands a tail on demand, so it is
        // genuinely unbounded. The IMPORT side compiles patterns ahead of
        // time, so it registers the tail expanded to kImportTailRepeats
        // repetitions and a longer MATLAB call is refused with a reason
        // naming the bound -- a refusal a reader can act on, rather than a
        // silent mistranslation.
        int tailStride = 0;
        // ⚠ THERE IS DELIBERATELY NO PER-FORM `compare` HERE, and the reason is
        // a measurement rather than an omission. T2.8 asked for one -- one
        // Entry carries both meanings of `tf2ss` and only a per-form grade
        // could say that the numeric form agrees to the digit while a MODEL
        // realization is representation-defined. Reading the row settles it
        // the other way: `tf2ss(G)` is REFUSED on purpose and points the
        // caller at `ss(G)`, MATLAB's own name for it, so the row has one
        // form and one honest grade. Every other multi-form row in the
        // catalog was checked the same way and none grades its forms
        // differently today. A field nothing sets is a field nothing
        // maintains; when a row needs it, it is two lines here and one in
        // the bridge (X12).
    };

    struct Entry {
        const char* icoreName;      // console function name ("inv")
        Support     support = Support::None;
        // Name of the icp_* helper the templates call, or "" for none. The
        // body comes from helperBody(); listing the name here is what lets the
        // bridge know which bodies an exported script needs.
        const char* helperName = "";
        // Every call format this row accepts, in the order a reader tries
        // them. Empty when support is None.
        std::vector<Form> forms;
        // The row's parity grade: what a form that does not grade itself uses.
        Compare compare = Compare::Exact;
        double  tolerance = 1e-12;
        const char* notes = "";     // why unsupported / mapping caveats, user-facing
        // ---- what the four fixed slots used to be asked -------------------
        //
        // The first form is the row's PRIMARY one, and it is what the two
        // function-handle paths read: an '@name' can only cross when MATLAB
        // spells the call the same way the console does, which is a question
        // about one form and always the first.
        const char* primaryTemplate() const;
        int         primaryArgCount() const;
        // The form a call of `count` arguments takes, or nullptr when the row
        // accepts no such arity. A tail form answers for every arity it
        // reaches, and `expandTail` below is what turns its template into the
        // one that arity needs.
        const Form* formFor(int count) const;
    };

    // A tail form's template written out at `count` arguments: the stored
    // template with ", %argCount+1, ... , %count" spliced in before its
    // closing parenthesis. Returns the template unchanged for a fixed-arity
    // form and for `count == form.argCount`.
    static std::string expandTail(const Form& form, int count);

    // How many repetitions of a name-value tail the IMPORT side registers
    // patterns for. Ten pairs is past every call any corpus here spells; the
    // export side has no such bound.
    static constexpr int kImportTailRepeats = 10;

    static const std::vector<Entry>& entries();

    // ---- the import direction (MATLAB -> console) -------------------------
    //
    // The Support::Yes rows above ARE the import dictionary: EVERY form's
    // template is read backwards as a PATTERN whose %1..%N are wildcards,
    // matched structurally against a parsed MATLAB statement
    // (ICoreMatlabCommandImporter). One table, one edit, both directions.
    // A form the inverse cannot see is a form that crosses one way only,
    // which is why the rule table walks `forms` and not just the first.
    //
    // The three tables below hold only what the forward table cannot say:
    // MATLAB idioms with no console export image, MATLAB's multi-output
    // calls, and the session noise an imported .m carries.

    // A MATLAB idiom with no export image but a deterministic console form.
    // Same honesty rule as Entry: a mapping is a catalog row or it does not
    // happen. `notes` is the user-facing caveat.
    struct ImportEntry {
        const char* matlabPattern = "";    // "zeros(%1, %2)", "norm(%1)", "chol(%1, 'upper')"
        const char* consoleTemplate = "";  // "repmat(0, %1, %2)", "norm2(%1)", "chol(%1)'"
        const char* notes = "";            // user-facing caveat; "" when none
        bool        softWarning = false;   // report `notes` on every use (dot, eig, fft, step(G))
        // Set when the console form is real console text that deliberately has
        // NO export image, so the self-check must not prove it by translating
        // it back out. Exactly two rows are like this today -- step(G) and
        // impulse(G) -- because the console picks the duration from the
        // model's poles and MATLAB picks its own, which is why the export
        // refuses that form on purpose.
        bool        consoleOnly = false;
    };

    // MATLAB's `[a, b, c] = f(x)`, which the console splits into one function
    // per factor. One console template per output POSITION, in MATLAB's order;
    // a statement whose output count differs from outputs.size() is refused
    // (that is what turns [L, U] = lu(A) down: MATLAB folds P into L there).
    struct MultiOutputEntry {
        const char*              matlabCall = "";  // "qr(%1)"
        std::vector<const char*> outputs;          // "qrq(%1)", "qrr(%1)"
        const char*              notes = "";
        bool                     softWarning = false;
        // The console now spells this statement the SAME WAY (C1.12 gave it
        // multiple return values), so the import emits one native line rather
        // than one line per output and `outputs` is empty. A row flips to this
        // the day its function grows a console multi-output form; until then
        // the per-output templates are the honest mapping.
        bool                     nativeStatement = false;
    };

    // A MATLAB idiom that is recognised precisely so it can be refused with a
    // reason better than "no console mapping" — the false friends above all,
    // where a name exists on both sides meaning different things.
    struct ImportRefusal {
        const char* matlabPattern = "";
        const char* reason = "";
    };

    static const std::vector<ImportEntry>&      importEntries();
    static const std::vector<MultiOutputEntry>& multiOutputEntries();
    static const std::vector<ImportRefusal>&    importRefusals();

    // Statement heads that an imported .m carries but a console script has no
    // use for: figure/plot furniture and session hygiene. Dropped and COUNTED,
    // never silently. `rng` is deliberately absent — seeding is a claim about
    // determinism the console cannot honour, so it is refused, not dropped.
    static const std::vector<const char*>& ignoredStatements();

    // MATLAB command syntax the console spells word for word, so an imported
    // statement crosses VERBATIM: `clear x y`, `clc`, `who`, `run demo`.
    // These four left ignoredStatements() on 2026-09-02 (C2.10, C2.11, C14.1)
    // for the reason tic/toc did before them -- they mean something on this
    // console now, and dropping `clear x` would leave the imported script
    // holding a variable the original had removed.
    static const std::vector<const char*>& verbatimStatements();

    // Exact name lookup, or nullptr (a name absent from the catalog is treated
    // by callers exactly like Support::None).
    static const Entry* findByName(const std::string& icoreName);

    // ---- reading an argument slot out of a template -------------------------
    //
    // A template's slots are `%1`, `%2`, ... and they do NOT stop at nine:
    // `integral3`'s eleven-argument form and `atmoslapse`'s ten-constant form
    // both spell `%10` and `%11`. Every reader of a template goes through this
    // one function so the export side, the import side and the multi-output
    // side cannot disagree about where a slot ends.
    //
    // `at` indexes the '%'. Returns the slot number and sets `length` to the
    // characters consumed including the '%'; returns 0 when tmpl[at] is not the
    // start of a slot, and then `length` is 1. Digits are read GREEDILY up to
    // two, so `%11` is slot eleven and never slot one followed by a '1' -- a
    // template that wants a literal digit after a slot has to be written some
    // other way, and none does.
    static int readTemplateSlot(const std::string& tmpl, size_t at, size_t& length);

    // ---- which toolbox a name comes from -----------------------------------
    //
    // Most console names are core MATLAB and carry no tag. The rest come from
    // one of the eight toolboxes that board covers, and an untagged row cannot
    // say so: `lyap` and `xcorr` look alike in this table, and only one of them
    // needs a licence to run on the other side. So a row whose name belongs to
    // a toolbox opens its `notes` with the tag
    //
    //     toolbox: <slug>            a landed row: MATLAB's name, MATLAB's way
    //     toolbox: <slug> (extra)    the console's own, ahead of that row
    //
    // and the tag is the FIRST thing in the note, so one parse finds it. The
    // slugs are MATLAB's own toolbox FOLDER names (`ver('control')`), which is
    // what the parity runner needs to ask whether a toolbox is installed;
    // the parity runner maps each slug to the licence feature on the MATLAB
    // side, and this table is the list of slugs it maps FROM — never a second
    // copy of it.
    //
    // `(extra)` is the core board's own word for these names (its C0.10), and
    // it is a statement about SCOPE, not about quality: the console already
    // has the name, outside the core-MATLAB surface that board certified, and
    // the toolbox row that decides what MATLAB's form of it means here has not
    // landed. Some of them already ARE MATLAB's form (`lyap(A, Q)` is the
    // identity in both directions); others are the console's own shape under a
    // MATLAB name (`bode(G, w0, w1, n)`, a 4 x 1 quaternion). Which is which
    // is that row's business, and until it lands, `(extra)` is what `help` can
    // honestly say: this is the console's, ahead of MATLAB's.
    struct ToolboxTag {
        const char* slug;   // MATLAB's toolbox folder name: ver('control')
        const char* title;  // what MATLAB's `ver` calls it, for help and docs
    };
    static const std::vector<ToolboxTag>& toolboxes();

    // The tag on one row, parsed from its `notes`. `slug` is empty for a core
    // MATLAB name, for a name no toolbox owns, and for a name the catalog does
    // not hold at all — the three cases callers treat alike.
    struct ToolboxMark {
        std::string slug;
        std::string title;
        bool        extra = false;
    };
    static ToolboxMark toolboxOf(const std::string& icoreName);

    // What every reader of the tag is entitled to assume, checked rather than
    // trusted: the tag opens the note (so one parse finds it), it names a slug
    // this table knows, and a note that talks about a MATLAB toolbox in prose
    // carries a tag rather than only saying so in a sentence nothing can read.
    // Empty when the catalog is sound; one line per failure otherwise.
    // Reported by `fromMatlab --selfcheck` beside the import inverse's own
    // failures, so one console line asserts the whole catalog.
    static std::vector<std::string> toolboxSelfCheck();

    // The MATLAB function body for one icp_* helper name, newline-terminated,
    // or "" for an unknown name. Bodies are complete `function ... end` blocks
    // valid both as end-of-script local functions and as standalone .m files.
    static std::string helperBody(const std::string& helperName);

    // Every helper name, for the parity suite to emit as icp_*.m files.
    static std::vector<std::string> helperNames();
};
};

ICoreMatlabCommandImporter.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/MatlabBridge/ICoreMatlabCommandImporter.h

ICoreMatlabCommandImporter#

ICoreMatlabCommandImporter.h:40 · class · nested ScriptResult · 2 declaration(s)

The MATLAB -> console half of the command bridge: the exact mirror of ICoreMatlabCommandBridge, reading a .m file back into the console command language.

class ICoreMatlabCommandImporter {
public:
    // One MATLAB LOGICAL line (continuations already joined, comments already
    // stripped) -> zero or more console lines, in order. A refusal appends its
    // reason to `warnings` and yields no line for that statement — never half
    // a line. Soft warnings (a mapping that is sound only under a stated
    // condition) are appended to `notes` and do not stop the statement.
    static std::vector<std::string> translateLine(const std::string& matlabLine,
                                                 std::vector<std::string>& warnings,
                                                 std::vector<std::string>& notes,
                                                 std::set<std::string>* assignedNames = nullptr);

    // Whole .m text -> .icore text. Counts are reported through `summary`.
    struct ScriptResult {
        bool        ok = false;
        std::string failureReason;              // set when ok == false
        std::vector<std::string> warnings;      // one per refused statement
        int statementCount = 0;                 // statements seen (ignored ones excluded)
        int translatedCount = 0;                // statements that crossed
        int ignoredCount = 0;                   // session-noise statements dropped
    };
    static std::string translateScript(const std::string& matlabText,
                                       ScriptResult& summary);

    // Every way the derived inverse could be wrong, checked without running
    // anything: each Support::Yes template parses as a pattern; each pattern
    // instantiated with fresh identifiers matches ITS OWN row and no other
    // (which is where ambiguity would bite); each import-only console template
    // is real console text (proved by translating it back through the export);
    // each icp_* helper body is reachable from some pattern. Empty when clean;
    // one line per failure otherwise. commandParityPrepare runs it first, so a
    // catalog edit that breaks the inverse cannot land green.
    static std::vector<std::string> selfCheck();

    // The lexer's quote rule, for the tree's other MATLAB reader. A ' after a
    // value (an identifier, a number, a closing bracket, another ' or .') is a
    // TRANSPOSE; anywhere else it opens a string, and inside one '' is a quote.
    // ICoreSimulinkMCodec::read used to toggle quote state on every ' and so
    // read `k = A'; set_param(...)` as ONE statement, losing the set_param
    // without a warning (fixed 2026-09-03); it reads through these two now,
    // so the rule exists once. (This comment becomes a public API page, so it
    // names no working document of this tree.)
    //
    // stripComment: one PHYSICAL line -> its code with the trailing %-comment
    // removed. `continued` reports a `...` continuation (whatever followed the
    // dots was a comment too, and is gone). A % or ... inside 'text' is text.
    static std::string stripComment(const std::string& physicalLine,
                                    bool* continued = nullptr);
    // stringMask: one flag per byte of comment-free `code`, true where the
    // byte sits inside a string literal, quotes included -- what a splitter
    // needs to find a top-level ';' or ',' under the same rule.
    static std::vector<bool> stringMask(const std::string& code);
};
};

ICoreSimulinkBlockCatalog.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

ICoreSimulinkBlockCatalog#

ICoreSimulinkBlockCatalog.h:26 · class · nested EnumPair, ParamRule, Entry · 10 declaration(s)

The dictionary between the ICore block library and the Simulink standard library: one entry per registered ICore block type, saying whether it can cross to/from Simulink at all, which Simulink libr...

class ICoreSimulinkBlockCatalog {
public:
    enum class Support {
        Both,        // exchanges in both directions
        ExportOnly,  // ICore -> Simulink only
        ImportOnly,  // Simulink -> ICore only
        None,        // no equivalent; reported and skipped
    };

    // How the Simulink-side "number of inputs" parameter is derived from the
    // block's ICore input-port LIST (these are port-list edits in ICore, not
    // config variables, so they can't ride through ParamRule). For the two sign
    // kinds the parameter also carries each input's sign, which ICore keeps in
    // the port's description label — so they depend on the port descriptions
    // too, not the count alone.
    enum class PortsParam {
        None,
        // ⚠ UNIONED AT THE 2026-09-10 MERGE. Both branches extended this enum and
        // neither name set is a superset: main added LogicalOperatorInputs and
        // BitwiseNumInputPorts, ui-swap added the four below, and the recipe codec
        // plus three Signal_Routing blocks name these four directly.
        LogicOperatorInputs,
        MultiPortSwitchInputs,
        VariantNumChoices,
        VariantNumChoicesOut,
        SumSigns,       // Sum:      Inputs = one sign per input port, "+" default
        SubtractSigns,  // Subtract: same, but a fresh block defaults to "+-"
        DivideSigns,    // Divide:   Inputs = one "*" or "/" per input port, "*" default
        MuxCount,       // Mux:      Inputs = input-port count as a number
        ScopeNumInputs, // Scope:    NumInputPorts, only when the count is > 1
        // Math Function: the port count is not a parameter at all on either
        // side. Simulink derives it from Operator (pow, hypot, rem and mod show
        // a second input; the other eleven show one), so EXPORT writes nothing
        // here - setting Operator is what moves the ports. Import is the
        // direction that needs the rule: reading Operator has to widen the
        // ICore port list to match, or the second add_line lands on a port that
        // was never created. Unlike every kind above, this one does not consume
        // its key - Operator is also a mapped config variable, so the reader
        // sets the count and then lets the ParamRule translate the value.
        MathFunctionOperator,
        // Reshape: the same shape of rule as Math Function's, and for the same
        // reason. Simulink derives the port count from OutputDimensionality -
        // "Derive from reference input port" shows a second input carrying the
        // shape to copy, the other four show one - so EXPORT writes nothing here
        // and setting OutputDimensionality is what moves the ports. Import is
        // the direction that needs the rule: without it the second add_line
        // lands on a port that was never created. Like the Math Function kind it
        // does not consume its key, because OutputDimensionality is also a
        // mapped config variable whose value still has to be translated.
        ReshapeOutputDimensionality,
        // Trigonometric Function: the same shape of rule again, over a different
        // operator list - atan2 is the ONE of the thirteen real operators that
        // shows a second input port, and Simulink moves its own ports when
        // Operator is set. Kept as its own kind rather than folded into
        // MathFunctionOperator because the binary SET is what the rule actually
        // is: sharing one kind between two blocks with different sets would have
        // an imported Trigonometric Function consulting Math Function's
        // {pow, hypot, rem, mod} and giving atan2 one port.
        TrigFunctionOperator,
        // MinMax: Inputs = input-port count as a number, exactly as MuxCount
        // writes it - but floored at ONE rather than two. A separate kind
        // because that floor is the whole difference and it is load-bearing:
        // MinMax at one input is not a degenerate two-input block, it is the
        // block's other mode, collapsing its single input over all elements to
        // a scalar. Routed through MuxCount it would be exported as Inputs = 2
        // and the reduction would silently become an elementwise comparison
        // against a port that does not exist.
        MinMaxCount,
        // Matrix Concatenate: the same count-as-a-number rule again, over a
        // parameter that is not called "Inputs". Its counterpart names it
        // NumInputs, and set_param on a name a block does not define is a hard
        // MATLAB error, so the name cannot simply be shared with MuxCount.
        ConcatNumInputs,
        // Identity Matrix: the same shape of rule as Reshape's and Math
        // Function's - Simulink moves its own port when the parameter is set, so
        // EXPORT writes nothing here and IMPORT is the direction that needs the
        // rule - over InheritOutputPortAttributes rather than an operator list.
        // Like those two it does not consume its key, because the parameter is
        // also a mapped config variable whose value still has to be translated.
        //
        // IT IS THE ONE KIND WHOSE DEFAULT PORT COUNT IS ZERO. Identity Matrix is
        // a SOURCE when the parameter is 'off' and grows a single input - whose
        // DIMENSIONS the output copies, and whose values are never read - when it
        // is 'on'. Every other kind here widens a block that already had at least
        // one input, which is why ICoreSimulinkRecipeCodec's port-edit guard had
        // to admit a zero default: at `defaultCount > 0` this kind's edits were
        // skipped silently and an imported inheriting block came back with no
        // input port at all.
        IdentityInheritAttributes,
        // Demux: Outputs = the OUTPUT-port count as a number. The first kind here
        // that moves the output list rather than the input one, which is the
        // whole reason it is not MuxCount with a different parameter name:
        // clearPorts() clears whichever lists a block makes editable, and on
        // Demux that is the outputs, so the codec's port-edit replay has to write
        // addPort(out, ...) and consult the type's default OUTPUT count.
        //
        // Simulink also accepts a VECTOR here ("[2 3]"), which sets each output's
        // width individually rather than splitting evenly. ICore's Demux derives
        // its widths from the input, so only the COUNT crosses; the reader takes
        // the vector's length and reports that the widths did not.
        DemuxOutputs,
        // Logical Operator: `Inputs` as a plain number again, floored at ONE --
        // and with a DEFAULT of TWO, which is the whole difference from
        // MinMaxCount and is load-bearing in the other direction. The floor is
        // one because NOT negates a single signal; the default is two because a
        // fresh Logical Operator is a two-input AND on both sides. Routed
        // through MinMaxCount its default would read as ONE, and the recipe
        // codec would emit a port edit on every fresh block to widen a list that
        // was already right; routed through MuxCount the floor would be two, and
        // an imported NOT would come back with a second input port that its own
        // operator forbids.
        LogicalOperatorInputs,
        // Bitwise Operator: the count-as-a-number rule again, floored at ONE
        // (NOT complements a single signal) with a default of TWO -- the same
        // pair as the kind above -- but over a parameter that is NOT called
        // `Inputs`. Its counterpart names it `NumInputPorts`, and set_param on a
        // name a block does not define is a hard MATLAB error, so the name
        // cannot simply be shared. The same reason ConcatNumInputs is its own
        // kind rather than MuxCount with a different string.
        //
        // ScopeNumInputs already writes `NumInputPorts`, and is also not
        // shareable: it writes the parameter only when the count is GREATER THAN
        // ONE and its default count is one, so a fresh two-input Bitwise
        // Operator would carry a spurious port edit on every import.
        BitwiseNumInputPorts,
        // Compose String: the input COUNT and each input's TYPE follow the Format
        // parameter, one input per conversion, measured on R2026a (1..128
        // conversions; %d/%i an int32, %u/%x/%X/%o a uint32, %f/%e/%g a double, %s
        // a string). So EXPORT writes nothing here: setting Format is what moves
        // Simulink's ports. Import is the direction that needs the rule: reading
        // Format has to size AND type the ICore port list, or the second add_line
        // lands on a port that was never created, or a text wire meets a port
        // typed for a number. Like Math Function's, the reader does not consume its
        // key: Format is also a mapped config variable.
        ComposeStringFormat,
        // Scan String: Compose String's rule mirrored onto the OUTPUT list -- one
        // output per conversion, each typed by it, measured on R2026a (1..128;
        // %d/%ld int32, %hd int16, %u/%lu uint32, %hu uint16, %f/%e/%g single,
        // %lf/%le/%lg double, %s/%c string). Export writes nothing (Format moves
        // Simulink's ports); import sizes and types the ICore output list from
        // Format, and the String to Double / String to Single folds reach it
        // through their implied Format. Not consumed: Format is also a mapped
        // config variable.
        ScanStringFormat,
        // Horizontal Wind Model 14: the input COUNT follows `model` -- three inputs
        // (LLA, day, seconds) for Quiet, a fourth (Ap) for Total and Disturbance,
        // measured on R2026a. The same shape of rule as Math Function's: EXPORT
        // writes nothing (setting model is what moves Simulink's ports), IMPORT
        // widens the ICore port list from it, and the key is not consumed --
        // model is also a mapped config variable.
        HwmModelInputs,
        // Digital DATCOM Forces and Moments: not a port count but the one
        // parameter two configs make together. Simulink's `dcase` is a MATLAB
        // expression naming a datcomimport struct; ICore reads the file itself,
        // from `DATCOM File` and `Case`. EXPORT writes dcase as
        // subsref(datcomimport('<file>',true,0),substruct('{}',{<case>})) --
        // measured to work as a mask value on R2026a -- and IMPORT reads that
        // exact form back into the two configs, reporting any other dcase (a
        // workspace variable cannot be read from here). Consumed: dcase is no
        // config variable. The input count follows the FILE (the DAMP card adds
        // two), which neither side can see from the script, so it is left as the
        // recipe carries it.
        DatcomStructure,
        // Custom Python Model Predict: the port COUNTS, and each input's numpy type and rank,
        // live in two table parameters on the Simulink side -- InputTable, one row
        // {name, type, permutation, rank} per input, and OutputTable, one row {name,
        // permutation, max size} per output -- which set the block's ports when set (measured
        // on R2026a). ICore keeps the counts in its port lists and the types and ranks in two
        // comma-list configs, so EXPORT writes both tables from those and IMPORT reads them
        // back into the same three things. Consumed: neither table is a config variable. Both
        // lists are user-editable, so the recipe replays them together, as for SymbolSpec.
        PycoexCustomTables,
        // Scikit-learn Model Predict: the same InputTable, three columns {name, type,
        // permutation}, for its single input -- the only place the input's numpy type lives.
        // EXPORT writes it from `Input Data Type`, IMPORT reads it back. The ports are fixed.
        PycoexSklearnTable,
        // If and Switch Case (FEATURES_TO_ADD.md BF8.5): BOTH port lists follow
        // the parameters on the Simulink side, and no parameter carries a count.
        // An If has NumInputs inputs and one output for IfExpression, one per
        // ElseIfExpressions entry (split at EVERY comma, measured) and one for
        // ShowElse 'on'; a Switch Case one input and one output per
        // CaseConditions entry plus one for ShowDefaultCase 'on'. EXPORT writes
        // nothing (setting the parameters is what moves Simulink's ports); IMPORT
        // derives both counts once every parameter is read, and the recipe
        // replays both lists.
        ActionDriverPorts,
        // For Iterator, While Iterator and For Each (FEATURES_TO_ADD.md BF9.6):
        // Simulink derives their ports from their parameters (a For's external
        // count, a shown iteration output, a do-while's missing IC), so EXPORT
        // writes only the parameters and IMPORT sets the counts from them; the
        // recipe replays both lists against the factory's initial ports. For
        // Each's per-port partition and join lists cross as cells.
        IteratorPorts,
        // Send and Receive (FEATURES_TO_ADD.md BF4.8): one parameter shows one
        // more port, and Simulink moves it itself, so EXPORT writes nothing here.
        // IMPORT sets the count from it: Send's ShowEnablePort adds the enable
        // as its FIRST input, Receive's ShowQueueStatus the status as its FIRST
        // output. Two kinds, because the recipe replays one list per kind, and
        // these are different lists.
        SendEnablePort,
        ReceiveStatusPort,
        // Real-Imag to Complex and Complex to Real-Imag (FEATURES_TO_ADD.md
        // BF23.1): `Input` / `Output` = 'Real and imag' shows two ports, 'Real' or
        // 'Imag' one, and Simulink moves them itself (measured on R2026a), so
        // EXPORT writes nothing here. IMPORT sets the count from it. Two kinds,
        // because one moves the input list and the other the output list.
        RealImagInputs,
        RealImagOutputs,
        // Function-Call Split: NumOutputPorts = the OUTPUT-port count as a
        // number, floored at two (R2026a refuses one: FcnCallSplitInvalidNumOutputs).
        // Demux's rule over another parameter name, with one more duty on import:
        // every output it creates is a function call, so the replayed addPort
        // lines carry ICoreFunctionCall rather than the double default.
        FcnCallSplitOutputs,
        // Variant Source (inputs) and Variant Sink and Variant Start (outputs): the
        // port count is the length of the CELL `VariantControls`, one condition per
        // port, and setting it is what moves Simulink's ports (measured on R2026a: a
        // 3-entry cell gives Ports [3 1]). EXPORT writes the "Variant Controls"
        // config as that cell; IMPORT reads the cell back into the config and sets
        // the count from its length. Two kinds, one per list, as for the manual pair.
        VariantControlsIn,
        VariantControlsOut,
        // Variant End: no parameter carries its count -- Simulink sizes it from the
        // Variant Start of the same VariantStartEndTag. EXPORT writes nothing; IMPORT
        // gives each End its Start's output count once the whole script is read.
        VariantEndInputs,
        // Data Type Duplicate: NumInputPorts = the input count as a number, ALWAYS
        // written and floored at ONE (measured: 1 is legal, 0 is refused, and the
        // default is 2, so ScopeNumInputs' "only when > 1" would export one input as two).
        DataTypeDuplicateInputs,
        // Message Merge: NumberInputPorts = the input count as a number, always written
        // (R2026a's default is 2).
        MessageMergeInputs,
        // Queue: OverwriteOldest off together with NumberEntitiesInBlock on shows the count
        // port, which takes output 1 and moves the message to output 2 (measured); Simulink
        // moves its own ports, so export writes nothing, and import follows the two keys.
        QueueCountPort,
    };

    // One config variable <-> one Simulink parameter. An empty enumValues means
    // the value text passes through unchanged (numbers and matrices — "[0 1;-1 -1]"
    // is valid in both languages). A non-empty enumValues translates a combo-box
    // value; export takes the first icore->simulink match, import the first
    // simulink->icore match (so a many-to-one mapping round-trips to the first
    // listed ICore value).
    struct EnumPair {
        const char* icoreValue;
        const char* simulinkValue;
    };
    struct ParamRule {
        const char* icoreConfig;    // ICore config-variable name ("Gain Value")
        const char* simulinkParam;  // Simulink parameter name ("Gain")
        std::vector<EnumPair> enumValues;
        // The value is ANOTHER BLOCK'S PATH: the config is a block reference
        // (ICoreBlockConfigVariable::setIsBlockReference), and the Simulink
        // parameter a block path -- Parameter Writer's ParameterOwnerBlock,
        // State Reader's StateOwnerBlock (FEATURES_TO_ADD.md BF11.6). Such a
        // path crosses RELATIVE to the level exchanged ("Plant/Gain"; see
        // ICoreSimulinkExchangeBlock::params), is written as the full path
        // Simulink requires ([model '/Plant/Gain'] -- a bare 'Gain' is
        // InvSimulinkObjectName, measured in BF11.1), and, because Simulink
        // checks the named block exists when the parameter is set, the whole
        // set_param of a block holding one is written once every block exists.
        // Last, so existing aggregate initializers are unaffected.
        bool namesBlock = false;
    };

    struct Entry {
        const char* icoreFullType;  // "Control_Systems/Base_Blocks/Gain"
        const char* simulinkPath;   // "simulink/Math Operations/Gain"; "" when support == None
        Support support = Support::None;
        PortsParam portsParam = PortsParam::None;
        // ORDER IS SIGNIFICANT. The .m writer emits one set_param call carrying
        // these pairs in the order listed here, and Simulink validates each pair
        // as it is applied — so a parameter validated AGAINST another must come
        // after it. Slider Gain is the case: `gain` is range-checked against
        // `low`/`high` the moment it is set, and setting it first is a hard
        // "Value ... is out of range" that aborts the whole generated script.
        // Its entry therefore lists Minimum and Maximum before Gain. Every other
        // entry's parameters are independent, so their order is just the order a
        // reader finds most natural.
        std::vector<ParamRule> params;
        // Config variables that intentionally do NOT cross (ICore-side workflow,
        // e.g. a discrete block's continuous source matrices). Anything neither
        // mapped nor listed here raises a warning, so new config variables are
        // never silently lost.
        std::vector<const char*> ignoredParams;
        const char* notes = "";     // why unsupported / mapping caveats, user-facing
        // Simulink parameters this ICore block type always implies, with no
        // config variable behind them because the block has no choice to offer:
        // e.g. the Signal Recorder always produces a time series, so its Simulink
        // counterpart is always 'SaveFormat','Timeseries'. Emitted on export, and
        // on import a differing value is reported rather than silently accepted.
        std::vector<std::pair<const char*, const char*>> fixedParams;
        // Whether the Simulink counterpart actually HAS a SampleTime parameter.
        // Almost all do, which is why the pair is mapped globally (see below) --
        // but not all: Wrap To Zero, Coulomb & Viscous Friction, Rate Limiter and
        // the Dynamic blocks define no such parameter, and `set_param` on a
        // parameter a block does not define is a HARD ERROR in MATLAB, not a
        // warning. An entry that sets this false keeps its rate on the ICore side
        // instead of emitting a set_param that would break the whole script.
        //
        // What the writer then SAYS about it depends on the value, because the
        // config exists on every block whether the user set it or not: a rate of
        // <= 0 is ICore's "inherit", which a parameterless counterpart does
        // anyway, so it is dropped in silence; a rate > 0 is a real setting that
        // could not cross, and is reported both into the exchange report and into
        // the generated .m itself (a `% ICORE-WARNING:` comment plus an executable
        // `warning('ICore:SampleTime', ...)`).
        //
        // Kept last in the struct so existing aggregate initializers are unaffected.
        bool hasSampleTimeParam = true;
        // The Simulink parameter that carries the rate, for the counterparts that
        // HAVE one but do not call it "SampleTime". Tapped Delay is the case: it is
        // a masked S-Function whose rate parameter is `samptime`, so emitting the
        // standard name would be the same hard set_param error hasSampleTimeParam
        // exists to avoid -- but suppressing the rate entirely would throw away a
        // mapping that genuinely works. Empty means the standard name; ignored
        // when hasSampleTimeParam is false. Also kept last, for the same reason.
        const char* sampleTimeParamName = "";
        // The Simulink counterpart derives its PORTS from a SymbolSpec object
        // (C Function, Python Code) instead of a port-count parameter: a fresh
        // block has zero ports, so an add_line to it is a hard error. Entries
        // that set this make the .m writer emit addSymbol statements deriving
        // u1..uN inputs / y1..yM outputs from the ICore port list, and the
        // reader fold the same statements back into port counts. SymbolSpec is
        // not settable through set_param (the parameter is read-only) — only
        // the live-object addSymbol form works, which is what the codec speaks.
        // Also kept last, for the same aggregate-initializer reason.
        bool symbolSpecPorts = false;
        // The Simulink parameter that carries this block's OUTPUT signal type,
        // for the counterparts that let a block choose one: `OutDataTypeStr` on
        // Constant and Data Type Conversion, and nothing at all on the great
        // majority, whose output type is inferred from their inputs. Empty means
        // the block has no such parameter, and the writer then emits no
        // set_param for it -- which matters, because set_param on a parameter a
        // block does not define is a HARD ERROR in MATLAB, exactly as it is for
        // SampleTime above.
        //
        // Also kept last, for the same aggregate-initializer reason.
        const char* outputTypeParam = "";
        // ICore configs that must hold these values for the block to cross at all:
        // (config name, chosen value). The Simulink counterpart has no parameter for
        // them because it behaves as if they held exactly these values -- Unit
        // Conversion's two units at `inherit`, which is what Simulink's Unit
        // Conversion is (it reads both units off the signals). EXPORT refuses, and
        // reports, a block whose config holds anything else, including a config left
        // at a default that differs; IMPORT sets them. List the configs in
        // ignoredParams too, so export does not call them unmapped.
        //
        // Also kept last, for the same aggregate-initializer reason.
        std::vector<std::pair<const char*, const char*>> icoreFixedConfigs;
    };

    // Called from a block .cpp's static initializer (the same `registered`
    // lambda that registers the solver environment and icon). The entry's
    // icoreFullType must match the type the block registers with
    // ICoreBlockFactory.
    static void registerEntry(Entry entry);

    static const std::vector<Entry>& entries();

    // Exact full-type lookup, or nullptr.
    static const Entry* findByICoreType(const std::string& fullType);
    // Whether `icoreConfig` on the block type `fullType` maps to a parameter
    // that names a block (ParamRule::namesBlock).
    static bool namesBlock(const std::string& fullType, const ICoreString& icoreConfig);
    // Lookup by Simulink library path ("simulink/Sources/Step"), or nullptr.
    static const Entry* findBySimulinkPath(const std::string& simulinkPath);

    // An If's or a Switch Case's port counts (PortsParam::ActionDriverPorts), from
    // its ICore configs as `setting(name)` gives them ("" when unset, a combo's
    // options text or its bare choice): false for any other type.
    static bool actionDriverPortCounts(const std::string& fullType,
                                       const std::function<std::string(const char*)>& setting,
                                       int& inputs, int& outputs);

    // ---- The per-block rate, handled globally ----
    // EVERY ICore block carries a "Sampling Time (s)" config (created by
    // ICoreBlockSolverEnvironment), and every Simulink block has SampleTime, with
    // the same convention: <= 0 inherits, > 0 is an explicit period. So the pair is
    // mapped here for all block types instead of being repeated in each entry —
    // which is also why no entry lists it in params or ignoredParams.
    static ICoreString icoreSamplingTimeConfigName();          // "Sampling Time (s)"
    static const char* simulinkSampleTimeParamName();      // "SampleTime"
    // Simulink writes SampleTime as a plain number, as "[period offset]", or as
    // "inf". ICore stores a single double. Returns an empty string when the value
    // has no single-period meaning (caller reports it and leaves the default).
    static ICoreString simulinkSampleTimeToICore(const ICoreString& simulinkValue);

    // Enum-value translation per the ParamRule convention above. Returns the
    // input unchanged when the rule has no enum table; returns an empty string
    // when the table exists but the value is not in it (caller reports it).
    // ---- Signal types across the bridge ----
    // The Simulink class name for an ICore signal type id: "double", "int32",
    // "boolean", "string". A bus is the one that is not a plain class -- Simulink
    // spells it "Bus: <object name>", and the object name belongs to the diagram,
    // so this returns the "Bus: " PREFIX and the caller appends the name.
    // Returns an empty string for an id this bridge does not know.
    static std::string simulinkClassForSignalType(const std::string& signalTypeId);
    // The reverse, for import: "int32" -> "ICoreInt32". An unrecognised Simulink
    // class returns empty, which the reader reports rather than guessing at.
    static std::string signalTypeForSimulinkClass(const std::string& simulinkClass);
    // The ICore config variable that carries a block's chosen output type -- the
    // counterpart of the Simulink parameter an entry names in `outputTypeParam`,
    // and the name the READER writes an imported OutDataTypeStr into. Stated
    // here for the same reason icoreSamplingTimeConfigName() is: it is one
    // string shared by the bridge and by every block that offers the choice, and
    // a block that spells it differently imports as a silent no-op. Its VALUE is
    // a signal-type id ("ICoreInt32"), not a Simulink class.
    static ICoreString icoreOutputTypeConfigName();            // "Output data type"

    static ICoreString icoreToSimulinkParamValue(const ParamRule& rule, const ICoreString& icoreValue);
    static ICoreString simulinkToICoreParamValue(const ParamRule& rule, const ICoreString& simulinkValue);
};
};

ICoreSimulinkBridge.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBridge.h

ICoreSimulinkBridge#

ICoreSimulinkBridge.h:26 · class · nested Result · 2 declaration(s)

Facade over the Simulink exchange pipeline.

class ICoreSimulinkBridge {
public:
    struct Result {
        bool ok = false;
        ICoreString failureReason;   // set when ok == false
        ICoreStringList warnings;    // user-facing notes about skipped content
        int blockCount = 0;      // blocks that made it across
        int linkCount = 0;       // connections that made it across
        // Export only: the path the script was written to, which is NOT
        // necessarily the one that was asked for — the stem is folded to a
        // MATLAB identifier and ".m" is appended. Report THIS to the user, not
        // the requested path, or the success message names a file that is not
        // on disk.
        ICoreString filePath;
    };

    // Export the CONTENTS of `node` (the diagram level currently on screen) as
    // a Simulink model-construction script at `filePath`, whose LAST component
    // is normalized first: the stem is folded to a MATLAB identifier (spaces
    // and punctuation to '_', a leading non-letter prefixed with 'M') and ".m"
    // is appended. A script MATLAB cannot name is a script MATLAB cannot run,
    // and a subsystem's display name is under no obligation to be an
    // identifier. Result::filePath carries what was actually written.
    // Nested subsystems export recursively: each becomes a
    // 'built-in/Subsystem' whose gate ports come out as Inport/Outport blocks.
    static Result exportToSimulinkScript(ICoreSubsystemTreeNode* node, const ICoreString& filePath);

    // Import the Simulink model script at `filePath` INTO `node` (the diagram
    // level currently on screen). The whole import is captured as ONE undo
    // step (action logging is paused during the replay, then logged once).
    // Lines the interpreter refuses (e.g. a rename colliding with an existing
    // block's name) fail individually into `warnings`; the rest of the model
    // still lands.
    static Result importFromSimulinkScript(ICoreSubsystemTreeNode* node, const ICoreString& filePath);
};
};

ICoreSimulinkExchangeModel.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkExchangeModel.h

ICoreSimulinkExchangeBlock#

ICoreSimulinkExchangeModel.h:21 · struct · 4 declaration(s)

The neutral in-memory model both directions of the Simulink bridge meet at: recipe text -> ICoreSimulinkRecipeCodec::read -> [this] -> ICoreSimulinkMCodec::write -> .m text .m text -> ICoreSimulink...

struct ICoreSimulinkExchangeBlock {
public:
    ICoreString handle;         // recipe handle (unique across the script)
    ICoreString name;           // display name; defaults to the handle until a rename() is seen
    ICoreString icoreType;      // full ICore type path ("Control_Systems/Base_Blocks/Gain")
    double x = 0, y = 0;    // origin-anchored position, +y up (recipe move() coordinates)
    double w = 70, h = 70;  // block UI size (recipe resize() arguments)
    // Port counts as replayed by the recipe (clearPorts()/addPort()). -1 means the
    // recipe never edited that list (private/fixed list, e.g. Gain), so the block
    // type's own default applies.
    int inPortCount = -1;
    int outPortCount = -1;
    // Input-port description labels, indexed like the input port list and only
    // as long as the labels that were actually seen (entries may be empty).
    // Labels are presentation-only for most types, but on Sum they carry the
    // per-input sign, which is the whole content of Simulink's `Inputs`.
    ICoreStringList inPortDescs;
    ICoreSortedMap<ICoreString, ICoreString> params;  // ICore config-variable name -> raw value text
    // ⚠ A param that NAMES ANOTHER BLOCK (ICoreSimulinkBlockCatalog::ParamRule::
    // namesBlock) holds that block's path RELATIVE TO THE ROOT SYSTEM exchanged,
    // "Plant/Gain", never an ICore path: the recipe codec takes "Home/..." off
    // on read and puts the target level's path back on write, and the .m codec
    // writes it as [model '/Plant/Gain'] -- one spelling both directions share.
    // A subsystem FACE: this block stands for a nested system, and childSystem
    // indexes into the owning ICoreSimulinkExchangeSystem's `children`. The
    // face is how parent-level links address the subsystem (its icoreType is
    // the Subsystem type); the nested contents, gates included, live in the
    // child system. -1 for ordinary blocks.
    int childSystem = -1;
    // Per-port signal type ids exactly as the recipe spells them
    // (`addPort(out, ICoreInt32)`), indexed like the port lists and only as long
    // as what was actually seen. An empty entry means the recipe named no type,
    // which means ICoreDouble -- the same default the recipe interpreter applies.
    //
    // Only the OUTPUT side crosses to Simulink: there, a block's output type is a
    // parameter (`OutDataTypeStr` and its relatives) while its input types are
    // inferred from whatever feeds them. The input list is carried anyway, so an
    // ICore -> exchange -> ICore round trip cannot quietly retype an editable
    // input port back to double.
    //
    // Kept last in the struct, like the fields above it, so existing aggregate
    // initializers are unaffected.
    ICoreStringList inPortTypes;
    ICoreStringList outPortTypes;
    // Linearization analysis points, per OUTPUT port: 1 input, 2 output, 4 open loop
    // (Simulink's LinearAnalysisInput/Output/OpenLoop port parameters). Empty: none.
    ICoreList<int> outPortLinearization;
};
};

ICoreSimulinkExchangeModel.h:69 · struct · 0 declaration(s)

struct ICoreSimulinkExchangeLink {
public:
    int srcBlock = -1;  // index into ICoreSimulinkExchangeSystem::blocks
    int srcPort  = 0;   // 0-based OUTPUT port index on srcBlock
    int dstBlock = -1;
    int dstPort  = 0;   // 0-based INPUT port index on dstBlock
    // The line's name, when an imported script names it (`h = add_line(...);
    // set_param(h, 'Name', ...)`); empty otherwise. Import only: every ICore link
    // carries a generated default name, so export writes none. Last, so
    // existing aggregate initializers are unaffected.
    ICoreString name;
};
};

ICoreSimulinkExchangeSystem#

ICoreSimulinkExchangeModel.h:81 · struct · 1 declaration(s)

struct ICoreSimulinkExchangeSystem {
public:
    ICoreString name;                    // model name ("Home", a subsystem name, ...)
    ICoreList<ICoreSimulinkExchangeBlock> blocks;
    ICoreList<ICoreSimulinkExchangeLink> links;
    // Nested subsystems, each referenced by exactly one face block above
    // (std::vector because it may hold its own incomplete element type).
    // ICore gate blocks live in the child's `blocks` like any other block;
    // the Simulink codec is what expands them to Inport/Outport blocks.
    std::vector<ICoreSimulinkExchangeSystem> children;
    // Global-variable declares ("k = 2") carried through verbatim: valid MATLAB
    // and valid recipe alike, and block params may reference the names.
    // Only meaningful on the root system.
    ICoreStringList preludeAssignments;
    // The global solver configuration, as (property, value) pairs in the
    // vocabulary of `setModelConfig <property> <value>` (the table in
    // ICoreModelConfigCommands), in that table's order. A recipe carries them as
    // those lines; a Simulink script carries the ones Simulink understands as
    // set_param(model, ...) and the rest as `% ICORE-CONFIG:` comments
    // (ICoreSimulinkSolverCodec). Empty when the text said nothing about the
    // solver, in which case the receiving side keeps what it has. Only
    // meaningful on the root system.
    ICoreList<std::pair<ICoreString, ICoreString>> solverConfig;
};
};

ICoreSimulinkExchangeReport#

ICoreSimulinkExchangeModel.h:108 · struct · 0 declaration(s)

Shared by every bridge stage; the facade merges these into what the notification center shows.

struct ICoreSimulinkExchangeReport {
public:
    bool ok = true;
    ICoreString failureReason;  // set when ok == false
    ICoreStringList warnings;
};
};

ICoreSimulinkMCodec.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkMCodec.h

ICoreSimulinkMCodec#

ICoreSimulinkMCodec.h:24 · class · 5 declaration(s)

The Simulink side of the bridge: translates between the exchange model and a MATLAB model-construction script built from the common Simulink commands — new_system / add_block / set_param / add_line...

class ICoreSimulinkMCodec {
public:
    static ICoreString write(const ICoreSimulinkExchangeSystem& system,
                         ICoreSimulinkExchangeReport& report);

    static ICoreSimulinkExchangeSystem read(const ICoreString& mText,
                                            ICoreSimulinkExchangeReport& report);

    // The recipe.m contract, for user-facing instructions (the import dialog):
    // the statements read() APPLIES, one display line each ("add_block(source,
    // target, 'Param', value, ...)").
    static ICoreStringList supportedImportCommands();
    // Statement names read() recognizes and deliberately IGNORES without a
    // warning — session/console noise a generated script usually carries
    // (save_system, close_system, sim, disp/fprintf prints, clc, ...). Both
    // the call form ("disp(x)") and MATLAB's command form ("close all") are
    // matched against these names.
    static ICoreStringList ignoredImportCommands();

    // `raw` folded to a valid MATLAB identifier: every character outside
    // [A-Za-z0-9_] becomes '_', and a name that would not start with a letter
    // gains an 'M'. This is what write() names the model, and it is also the
    // only legal spelling for the SCRIPT FILE's stem — MATLAB reaches a script
    // by its name, so `My Model.m` is a file it cannot run. Callers that choose
    // a destination path pass the stem through here (see ICoreSimulinkBridge).
    // A Simulink.IntEnumType classdef file's text (`classdef Name < Simulink.IntEnumType`,
    // its `enumeration` block and an optional getDefaultValue), as the recipe's
    // `defineEnum(...)` statement (FEATURES_TO_ADD.md BF19.5). Empty, with `why`
    // set, when the text is not one.
    static ICoreString enumClassdefAsDefineEnum(const ICoreString& classdefText, ICoreString* why = nullptr);

    // The enumerated types `system` (and its children) names in an "Enum: <type>"
    // output type without defining it in its prelude.
    static ICoreStringList undefinedEnumTypes(const ICoreSimulinkExchangeSystem& system);

    static ICoreString modelIdentifier(const ICoreString& raw);
};
};

ICoreSimulinkRecipeCodec.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkRecipeCodec.h

ICoreSimulinkRecipeCodec#

ICoreSimulinkRecipeCodec.h:24 · class · 1 declaration(s)

The recipe-text side of the Simulink bridge.

class ICoreSimulinkRecipeCodec {
public:
    // `rootPath` is the diagram level the recipe was serialized from ("" is
    // Home): a config that names a block (ParamRule::namesBlock) is made
    // relative to it, and one naming a block outside it cannot cross -- it is
    // reported and left out (FEATURES_TO_ADD.md BF11.6).
    static ICoreSimulinkExchangeSystem read(const ICoreString& recipeText,
                                            ICoreSimulinkExchangeReport& report,
                                            const ICoreString& rootPath = ICoreString());

    // A config that names a block is written back under `parentToken` (Home
    // when empty), each segment renamed exactly as the block it names is.
    static ICoreString write(const ICoreSimulinkExchangeSystem& system,
                         const ICoreString& parentToken,
                         ICoreSimulinkExchangeReport& report);
};
};

ICoreSimulinkSolverCodec.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkSolverCodec.h

ICoreSimulinkSolverCodec#

ICoreSimulinkSolverCodec.h:39 · class · 8 declaration(s)

The solver-configuration half of the Simulink bridge.

class ICoreSimulinkSolverCodec {
public:
    using Config = ICoreList<std::pair<ICoreString, ICoreString>>;

    // Every property of the configurator as it stands, in table order.
    static Config snapshot();

    // The .m lines for `config` (see the header comment): the set_param call,
    // then the ICORE-CONFIG comments. Settings Simulink cannot express are
    // said in the report.
    static ICoreStringList write(const Config& config, ICoreSimulinkExchangeReport& report);

    // The reverse of the set_param call: model-level parameters as
    // (Simulink name, unquoted value) pairs, in script order, translated to
    // config entries. A parameter with no counterpart, or a value that is an
    // expression rather than a literal, is named in the report and skipped.
    static Config readModelParams(const ICoreList<std::pair<ICoreString, ICoreString>>& params,
                                  ICoreSimulinkExchangeReport& report);
    // A `% ICORE-CONFIG: property = value` comment line, into `entry`; false
    // when `rawLine` is not one.
    static bool readConfigComment(const ICoreString& rawLine, std::pair<ICoreString, ICoreString>& entry);
    // set_param-derived entries first, comment entries only where no
    // set_param entry named the property; the result in table order.
    static Config merge(const Config& fromParams, const Config& fromComments);

    // The recipe form: `setModelConfig <property> <value>` lines.
    static ICoreStringList recipeLines(const Config& config);
    static bool readRecipeLine(const ICoreString& line, std::pair<ICoreString, ICoreString>& entry);

    // The Simulink model parameters the writer fills and the reader maps, for
    // the import dialog's instructions.
    static ICoreStringList simulinkParameterNames();

    // The Simulink solver one ICore stepping type (or the Discrete solver,
    // passed as ICoreModelConfigurator::SOLVER_DISCRETE) maps to, and whether
    // it is a fixed-step one; empty for a stepping type with no counterpart.
    // The one map the writer uses, exposed for the parity suites' generated
    // MATLAB helpers so they spell a solver the way the bridge does.
    static ICoreString simulinkSolverName(const ICoreString& steppingTypeOrDiscrete, bool& fixedStep);
};
};

ICoreTemplateCommands.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/TemplateLibrary/ICoreTemplateCommands.h

ICoreTemplateCommands#

ICoreTemplateCommands.h:19 · class · 1 declaration(s)

The template catalog (ICoreTemplateLibrary) on the console: templates [category|kind] list what is available useTemplate <id> [parent] [dx] [dy] insert a SNIPPET into a diagram level (default Home,...

class ICoreTemplateCommands {
public:
    static void registerAll();
};
};

ICoreTemplateLibrary.h#

src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/TemplateLibrary/ICoreTemplateLibrary.h

ICoreTemplateLibrary#

ICoreTemplateLibrary.h:62 · class · nested Entry · 10 declaration(s)

The catalog of ready-to-use block diagrams: the ones shipped with the app and the ones a user drops in themselves.

class ICoreTemplateLibrary {
public:
    // Subsystem inserts into an existing diagram; Example and Starter are both
    // bundles and differ only in how they are presented - an Example is
    // something to read, a Starter something to build on.
    enum class Kind { Subsystem, Example, Starter };

    struct Entry {
        ICoreString id;          // stable address: "subsystems/pi-controller", "user/examples/rig"
        ICoreString title;       // @title, defaulting to a prettified file stem
        ICoreString category;    // @category, defaulting to "Uncategorized"
        ICoreString summary;     // @summary; may be empty
        Kind    kind    = Kind::Subsystem;
        bool    builtIn = true;   // false => came from the user templates folder
        ICoreString recipePath;       // the .icore (subsystem) or .iproj (bundle)
        ICoreString bundleFolder;     // bundles only: the folder to copy out

        // True when there is a project FOLDER to copy out — which is what both
        // callers actually need, and is NOT the same question as what @kind
        // claims. `kind` comes from the file's own metadata header, and §3 of
        // ADDING_NEW_TEMPLATES.md lets a single .icore declare
        // "@kind: example" (it is how a project template is iterated before it
        // is moved into Templates/Examples/). Keyed off `kind`, such a file
        // reported isBundle() == true with bundleFolder EMPTY, and
        // materializeAsProject() then handed that empty path to
        // copyFolderRecursively() — where ICoreDir("") resolves to the
        // process's WORKING DIRECTORY and the copy succeeds. One openTemplate
        // on such a template copied the whole repository, build trees included
        // (9.5 GB), and only then failed on the rename.
        //
        // Keyed off the folder, a single-file "example" falls to the branch
        // that is already correct for it: the recipe is written as the new
        // project's .iproj.
        [[nodiscard]] bool isBundle() const;
    };

    // Every template found, built-ins first, each kind in title order. The
    // filesystem is rescanned on every call: the sets are tiny, and a cache
    // would go stale the moment a user drops a file into the templates folder
    // with the app already running.
    static ICoreList<Entry> all();

    // Look up one entry by its id. False (leaving `out` untouched) when there
    // is no such template.
    static bool find(const ICoreString& id, Entry& out);

    // The categories present across the catalog, sorted, for grouping a listing.
    static ICoreStringList categories();

    // Insert a template into `parent`: creates a subsystem there, named after
    // the template (uniquified against its siblings), positioned at `position`
    // (+y up, like move()), and replays the template's contents inside it.
    //
    // Takes ANY entry, not just subsystem kind: a bundle's .iproj is the same
    // recipe text an .icore holds (solver settings travel beside it and are
    // simply not part of an insert), so an example project drops into a diagram
    // as one subsystem holding its whole Home. The catalog is one merged set,
    // and every surface offers every template - what differs per kind is only
    // the default gesture, not what is possible.
    //
    // Creating the container and filling it are ONE statement stream, so the
    // whole insert is one undo step rather than a subsystem the user has to
    // undo separately from its contents.
    //
    // `createdName` receives the name the subsystem actually got, which is not
    // always the template's title: a level that already holds one of these gets
    // a numbered sibling instead, and the caller wants to say which one it made.
    static ICoreRecipeFileTransfer::Result insertAsSubsystem(
        ICoreSubsystemTreeNode* parent, const Entry& entry,
        const ICorePoint& position = ICorePoint(), ICoreString* createdName = nullptr);

    // The inverse: write `node`'s contents to the user templates folder as a
    // SUBSYSTEM template, so what a user built by hand becomes something they
    // can insert again. `title` names it and, sanitized, becomes the file stem;
    // `category` and `summary` are optional and only affect how it lists.
    //
    // Returns the file written, or an empty path with `err` filled. An existing
    // template of the same stem is overwritten only when `overwrite` is set,
    // which is what lets a caller ask first.
    static std::filesystem::path saveSubsystemAsTemplate(
        ICoreSubsystemTreeNode* node, const ICoreString& title, const ICoreString& category,
        const ICoreString& summary, bool overwrite, ICoreString& err);

    // True when saveSubsystemAsTemplate would refuse for want of `overwrite` -
    // i.e. a user template with this title's stem already exists.
    static bool userTemplateExists(const ICoreString& title);

    // Turn ANY template into a real project at `destinationFolder`. A bundle is
    // copied out whole, its .iproj renamed to match the folder
    // (ICoreProjectSession::isValidProjectFolder is what requires the stem and
    // the folder agree). A subsystem template has no folder to copy, so the
    // project is MADE instead: its recipe text - retargeted to bare Home-form
    // statements - becomes the new .iproj, and the diagram loads as the
    // project's Home content. Returns the folder, or an empty path with `err`
    // filled - notably when the destination already exists, which is never
    // overwritten.
    //
    // The full destination is the caller's to choose, because the two callers
    // disagree: the console puts a template beside the current project, while
    // the New Project form puts it wherever the user pointed the location field.
    //
    // The caller opens it: ICoreProjectSession::switchToProjectFolder(parent,
    // <returned path>, false).
    static std::filesystem::path materializeAsProject(const Entry& entry,
                                                      const std::filesystem::path& destinationFolder,
                                                      ICoreString& err);

    // Called with the new folder after every successful materializeAsProject(),
    // before either caller opens it. A seam rather than a call because what
    // runs there belongs to a higher layer: Studio installs the agent-guide
    // writer (the agent-bridge board, AB.2), so a project made from a template gets
    // the coding-agent guides like one made empty. Null (the default) does nothing.
    static void setProjectMaterializedHook(std::function<void(const std::filesystem::path&)> hook);

    // "<application home>/Templates", holding Subsystems/ and Examples/ in the
    // same layout as the built-in resource. The application home is used rather
    // than the active project folder: templates outlive any one project.
    // Created on first look so there is somewhere to drop a file.
    static std::filesystem::path userTemplatesFolderPath();

    // "<application home>/Templates/.builtins" — the on-disk mirror of the
    // built-in resource tree (":/Templates"), wiped and rewritten from the
    // binary at every startup by Initialization (the layer that owns Qt). The
    // catalog reads built-ins from HERE and never from ":/" directly: the
    // filesystem wrappers underneath the scan are std::filesystem since the
    // Qt-free swap, and std::filesystem cannot see into the Qt resource
    // system. Dot-named so a directory listing of the user templates folder
    // (which skips hidden entries) never offers the mirror as user content.
    static std::filesystem::path builtInTemplatesMirrorPath();

    // Display name for a kind ("subsystem" / "example" / "starter").
    static ICoreString kindName(Kind kind);

    // The drag-and-drop format a library entry carries and the canvas accepts.
    // Its payload is an Entry::id. Declared here, next to the ids it quotes, so
    // the dragging end and the dropping end cannot drift apart -- the block
    // library's own "application/x-syntra-block" is spelled out at both ends,
    // which is exactly the arrangement this avoids.
    static const char* dragMimeType();
};
};