User manual › The command window — the command engine for a user
kind: manual#console#command-window#quick-code#variables#expressions#recipe#history#completion#manual

The command window — the command engine for a user#

The command window is the text way into ICoreBlocks. One line typed at its prompt can be a calculator expression over matrices and transfer functions (y = x*[1 2; 3 4]), a recipe statement that creates, wires or configures a block on the open diagram (g = block(Gain)), or a named command (modelConfig, templates). The same evaluator runs a line headlessly: ICoreBlocks --console "<line>" prints the result and exits 0 or 1. This page explains the engine — how a line is read, where variables live, what Tab and the arrow keys do, and how the console reaches the canvas. The roster of what you can type is Command glossary — console commands, verbs, functions; the recipe language, .icore files and templates are on Recipes and templates — recording, replaying and reusing diagrams; words such as handle, recipe and subsystem are defined in User glossary — the vocabulary of using ICoreBlocks.

Where it is#

  • In the editor: the left panel page Quick Code (menu Code Engine → Scripting → Quick Code, or the panel strip). It is a transcript on top, a status row (A- 13pt A+ text-size control, ● ready / ● busy — or ● for … end (block open, 1 deep) while a for/if/while/switch/ function/try block typed over several lines is waiting for its end; the transcript echoes those held lines with ... instead of >>>) and the prompt below, placeholder text Enter Command. Ctrl+C — or Esc — abandons whatever is open: the held lines are dropped without running, and the transcript says which of the three things went (an open block, a ... continuation, or a %{ … %} comment). It is the only way out of a block typed wrong, because typing end to close one would run it. With nothing open it just clears the half-typed line; Ctrl+C is left alone as Copy whenever the prompt has a selection, so the shortcut never costs you a copy. The transcript is also reachable as View → Loggers → Command History.
  • Headless: ICoreBlocks --console "<line>". The line runs 1.5 s after startup through the very same function the panel calls (ICoreConsoleInterpreter::evaluateLine, wired in src/ICoreBlocks/Shell/Application.cpp); output goes to stdout on success, stderr on failure, and the process exit code is 0/1 by the line's verdict. A bare --console with no line never exits.
  • The ICore Script IDE (Code Engine → Scripting → ICore Script IDE) feeds a whole .icore file to the same evaluator one line at a time — see below.

The rules the engine enforces#

  • A line that starts with a block keyword is held, not run — if, for, while, switch, function and try open a block; the prompt (and the ICore Script IDE) buffers every line up to the end that closes the outermost block, then hands the whole block over at once. end with nothing open is an error. A one-line block (if 1, x = 2, end) is complete on its own and needs no buffering — it is the same block, and runs the same way.
  • if, for and while run. A condition holds when it is non-empty and every element is non-zero, which is MATLAB's rule and a rule about numbers rather than about a true/false type: if [1 1] runs its body and if [1 0] does not. for name = expr walks the columns of expr, so a range gives one number per pass, a matrix gives one column per pass, and the loop variable keeps its last value when the loop ends. break and continue belong to the innermost for/while body; return ends the running script without failing it, and does nothing at a prompt because there is nothing to end.
  • A while loop has a ceiling of 200,000 passes. MATLAB does not need one because you can interrupt it; this console runs your line on the one thread it draws with, so a runaway loop would take the window with it. On reaching the ceiling the loop stops and says so — and that stop is the one failure try cannot catch, because a try that swallowed it would hand the runaway straight back.
  • A loop saves the project once, not once per pass. Assigning a variable tells the editor the variables space changed, and the editor answers by autosaving — which is right for a line you typed and was the entire cost of a loop when it happened on every pass: the whole project, recipe and sidecars and every .ini, written to disk twice per iteration. Since 2026-09-03 a block and a script hold that one notification and send it when they finish. Nothing you can observe moved — the value is in the store the instant it is assigned, the Variables panel still refreshes on every write, and a block that fails still saves the writes it made before it failed. What changed is the clock: see (g) below.
  • switch runs. switch expr … case value … otherwise … end takes the first arm that matches and does not fall through to the next one. The expression is a single number or a string — a matrix is refused, as MATLAB refuses it — and a case matches a number by equality and a string by its text. case {1, 2} is a list of alternatives and matches if any of them does; the braces are syntax here, not a value, because the console has no cell arrays. A break inside a switch belongs to the loop around it: the switch does not consume one.
  • try … catch … end runs. The body's failure is caught, whatever it printed before failing is kept, and the statements after the failing one never run. A try with no catch arm swallows the failure and carries on. catch err binds the failure to err, and because this console has no struct type an exception is three names rather than one object: err is the message, err.message is the same text under MATLAB's own spelling, and err.identifier is the identifier an error(id, msg) carried (empty when the console itself raised the failure).
  • A script can define functions. function y = twice(x) … end registers a name for the session, and the name is then callable anywhere an expression is: twice(21), z = twice(4) + 1, [s, d] = pair(7, 2). Definitions are registered before the script runs, so a call may sit above the definition — which is the only order MATLAB allows in a script, and the reason a MATLAB script's shape works here unchanged.
    • A function has its own workspace. It sees the arguments it was passed and the names it assigns, and nothing else: not the caller's variables, not the prompt's. Assigning inside it changes nothing outside. nargin and nargout say how many arguments came in and how many values the caller asked for.
    • An output the body never assigned is an error naming that output; too many arguments in or out is an error naming the count; and return inside a function returns from the function, leaving the script running.
    • Two limits are this console's rather than MATLAB's. A call may nest 200 deep and then stops and says so — the same one-thread argument as the loop ceiling. And a function may not take the name of a console command, which is refused when you define it rather than leaving a name that can never be reached.
  • [] is the empty matrix, and there is more than one of them. [] itself is 0×0; zeros(0, 3) is 0×3 and ones(3, 0) is 3×0, and size is how you tell them apart — every empty prints as []. isempty(x) asks the question. An empty drops out of a concatenation ([[], 5] is 5), an element-wise operation with a scalar answers an empty, and MATLAB's own answers for a 0×0 do not agree with each other: sum([]) is 0, prod([]) is 1, mean([]) is NaN, max([]) is [], any([]) is false and all([]) is true. det([]) is 1. Those are measured, not derived.
    • A condition that is empty is false — MATLAB's truth test is "non-empty and every element non-zero", so if [] does not run its body.
    • An index that selects nothing answers an empty rather than failing, and which empty depends on how you asked: v([]) is 0×0 while v(v > 9) on a row vector is 1×0. A subscript of 0 is still an error, as it is in MATLAB — an empty subscript is how "select nothing" is written.
    • A(...) = [] deletes. A(:) = [] leaves 0×0; deleting every column of a 2×2 leaves 2×0.
  • error, warning and assert are statements. error("boom") fails the line with that message; error("Comp:id", "boom") also sets the identifier a catch can read; warning(...) prints beside the statement and carries on; assert(cond) does nothing when the condition holds and fails with Assertion failed. when it does not, or with your own message as a second argument. The forms that substitute arguments into a format string (error("x is %d", n)) are refused by name until the console has sprintf — a message that printed its own format string would be the one answer that cannot be right. warning('off', id) is refused too: it switches a warning state this console does not keep.
  • Some MATLAB names are refused on purpose, each saying why, and ten of them because of how this console runs. pause, input, keyboard and dbstop — there is no interactive input, and the console runs your line on the thread it draws with. eval, evalin, evalc and assignin — code held in a string cannot be translated to MATLAB and back, which every console line must be. global and persistent — a script-defined function gets its own workspace and no way to share one. They are names and not reserved words: input = [7 8 9] defines a variable and input(2) then indexes it, which is the same shadowing rule that lets a variable win over a function.
  • A line is split into statements on top-level ; and , — a separator inside (...), [...] or <...> belongs to the literal or the call, which is what lets [1 2; 3 4] stay one matrix literal and kron(A, B) stay one call. Statements run in order and stop at the first failure. , is the one exception to "a command name is never special": a line whose first word is a registered command keeps its commas, because a command's arguments are free text (fromMatlab a = 1, b = 2 hands the importer MATLAB whose commas are the point). ; splits a command line as it always has.
  • A statement ending in ; is silent when it succeeds; one ending in , is not (MATLAB's rule). a = 1, b = 2 prints both, a = 1; b = 2 prints one. Failures are always printed; silencing never hides an error.
  • % starts a comment, to the end of the line — MATLAB's spelling, cut before the statement is read, so x = 5 % five assigns 5. The console's own # is whole-line only: # is already a value in console text — a #rrggbb colour in setColor, a #12 in a command's free-text argument — and a mid-line rule would eat both. A % inside "..." or inside brackets is not a comment either, so setText(50% done) keeps its text.
  • ... continues a statement onto the next line, and %{ … %} comments out a run of them — both are rules about physical lines, so they work wherever lines arrive one at a time: the prompt (which shows ... while the statement is unfinished), a .icore, and the ICore Script IDE. The two markers of a block comment must stand alone on their lines, as they do in MATLAB. A script that ends mid-continuation, or with a block comment still open, fails — it dropped a statement on the floor, and reporting success would hide that.
  • [ ... ] holds expressions, and whitespace separates them. A bracket that is one of the three literal spellings the console accepts (MATLAB [1 2; 3 4], Python [[1, 2], [3, 4]], C++ {{1, 2}, {3, 4}}) is still a literal and behaves exactly as it always did. Anything else is a concatenation: elements fold side by side, ; and a newline stack rows, and an element may be any expression ([A, A], [sin(0) cos(0)], [x x+1]). Inside a bracket the whitespace rule is MATLAB's, and it is the one thing worth reading twice: a space before a unary sign with nothing after the sign starts a new element, a space on both sides keeps one going, and an element that ends in a binary operator is unfinished. So [1 -2] is two elements, [1 - 2] is one (and is −1), [1+ 2] is one (and is 3), and [1 -2 + 3] is two. An unclosed [ holds the line the way ... does, so a bracket may span lines with each line a row.
  • The operators are MATLAB's, and so is their precedence. Arithmetic (+ - * / \ ^ and the element-wise .* ./ .\ .^), the six comparisons (== ~= < > <= >=), the logical operators (& | ~ and the short-circuit && ||), the range a:b / a:s:b, and transpose (', .'). Loosest first: ||, &&, |, &, the comparisons, :, + -, * /, unary + - ~, then ^ — so 1:2+3 is 1:5, 0 | 1 & 0 is 0, and ~1 + 1 is 1. A comparison answers 1 or 0 per element ([1 2 3] >= 2 is [0, 1, 1]), == is exact (0.1 + 0.2 == 0.3 is 0), and &&/|| take one number each and never evaluate their right side once the left has decided it.
  • | and & are logical OR and AND — they used to be concatenation. Before 2026-09-02 this console spelled "side by side" A | B and "stacked" A & B. Those spellings now mean what they mean in MATLAB, and the bracket is the only concatenation: write [A, B] and [A; B]. A script written against the old meaning keeps running and silently computes a logical, so it is worth grepping an old .icore for | and &.
  • A(i), A(i, j), A(:, j), A(v), A(mask) and A(:) index a variable, and end is the last position inside a subscript (v(end - 1), A(end, :), A(2:end, 1)). One subscript counts down the COLUMNS, so A(3) on a 2×2 is row 1 of column 2 and A(:) is every entry as a column — MATLAB's order, whatever the matrix stores internally. A variable shadows a function of the same name, so sum = [7 8 9]; sum(2) is
    1. An index that selects nothing answers an empty matrix rather than failing: v = [3 4 5]; v(v > 9) is [].
  • A(i, j) = x writes into a variable, A(:, 2) = [] deletes. A position past the end GROWS the array and fills the gap with zeros — x = [1 2 3]; x(5) = 7 leaves [1, 2, 3, 0, 7], and a name that does not exist yet is built the same way. A scalar right-hand side fills every selected entry (A(A > 2) = 0), and one with several entries fills them in order. Deleting takes one non-colon subscript: A(:, 2) = [] drops a column, A(1, :) = [] a row.
  • sum, mean, max and min reduce down the COLUMNS, as they do in MATLAB: sum(A) on a matrix is a row of column sums, on a vector a single number. sum(A, 2) goes the other way, sum(A, "all") adds every element — which is what sum(A) itself meant on this console before 2026-09-02 — and sum(A, "omitnan") skips a NaN where the default lets one poison its column. max and min take the same forms with MATLAB's placeholder (max(A, [], 2)), read a second array element-wise (max(A, B)), and hand back where the winner was as a second value ([m, i] = max(A)). They also IGNORE a NaN, where sum and mean propagate one — that difference is MATLAB's, per function rather than per family. The console's own axis names (sumrows, sumcols, meanrows, …) still work and still mean what they say.
  • ans holds the last unassigned result. A bare expression sets it, a suppressed one included (6 * 7; sets ans to 42 and prints nothing); an assignment does not, because it named where its value goes, and neither does reading a variable. It is an ordinary entry in the variables space: it shows in the Variables panel and ans = 9 overwrites it. Reading a constant by name (pi) does set it, because that is a call rather than a variable read.
  • Each statement is classified in this order, first match wins:
    1. a recipe statement — block(...), subsystem(...), connect(...), area/image/textbox(...), plot/semilogx/semilogy/loglog(...) and the other chart kinds (bar, stem, scatter, …), the figure verbs (figure, hold on, xlabel(...), subplot(...), …) in either of MATLAB's spellings, selectionModel(), clearDiagram(...), handle.method(...), or a bare handle name;
    2. an indexed assignment name(subscripts) = rhs, including the name(subscripts) = [] deletion;
    3. an assignment name = rhs — the = that assigns, never the one inside == <= >= ~=;
    4. a bare name that is a defined variable (echoes it), or — failing that — a name the math grammar itself knows, which today means the seven constants below;
    5. an expression — a number, a [...] literal, or anything carrying an operator (+ - * / \ ^ < > = ~ : | & ' ( ));
    6. a registered command: first word is the command, the rest are its arguments; name() is accepted as name. Nothing matched → unknown command: <first word> and the line fails.
  • Seven constants are readable by name: pi, Inf (also inf), NaN (also nan), eps, realmax, realmin and flintmax. Inf and NaN also take a size — Inf(3) is 3×3, Inf(2, 3) is 2×3 — and eps(x) is the distance from x to the next double, so eps(100) is 1.42e-14 while eps alone is 2.22e-16. They are looked up last, after the command engine and the variables space, so pi = 3 shadows the constant until you clear it — MATLAB's own rule.
  • A command name is never shadowed. A variable named like a command is not echoed by its bare name, and an expression whose first word is a command (reverse a+b, clear()) is dispatched as that command.
  • A block handle beats a variable of the same name in name.method(...); a name that is only a variable falls through to the math grammar (h.values(), G.step(10)), which is how time series and transfer functions get their members.
  • Results are annotated with their type — 7 # Integer, 0.333333 # Double, [[3, 6], [9, 12]] # Matrix of Double, and likewise # Logical, # Matrix of Logical, # Polynomial, # Transfer Function, # State Space, # Zero Pole Gain, # Struct, # Time Series, # Sym, # Curve Fit, # String. The annotation is the variable system's real type name; a scalar whose value is whole prints as Integer. A logical prints its numbers and says its class in the tag, which is how 1 < 2 (a logical 1) is told apart from 1 (a double).
  • Inf and NaN are spelled MATLAB's way, in the echo, inside a matrix and in the variables panel alike.
  • Only one line runs at a time. While a line runs the prompt is disabled and the status shows ● busy; a second line cannot slip in underneath.

The variables space#

  • Create: x = 3, A = [1 2; 3 4], label = hello, G = tf([1],[1,2,1]). The right-hand side is either a literal (stored as typed), a bare name (copies an existing variable, else a constant such as pi, else stores the word as a string), or an expression (evaluated, then stored). The reply echoes the stored value: x = 3 # Integer.
  • Types are derived from the stored text: Integer, Double, Matrix of Double, Logical, Matrix of Logical, String, Transfer Function, State Space, Zero Pole Gain, Polynomial, Struct, Time Series, Sym, Curve Fit. A Sym is a symbolic expression — what syms x declares and what x^2 + 1 then is: the expression itself, carried rather than evaluated, so it can be differentiated, integrated, factored and substituted into. It becomes a number only when you ask (double(f), vpa(f)), and everything it answers is on Symbolic math in the command window. It survives the variables space as its own text inside a sym(...) call, which is what tells it apart from a string holding the same characters. A Struct is MATLAB's scalar struct as this console admits it: a fixed, ordered, named list of numbers, read as r.fieldname — MATLAB's own spelling, and the one member access here that takes no parentheses. It is what [x, fval, exitflag, output] = fminunc(...) answers in its fourth place. There is no field assignment and no nesting: a record is read, not built. A field MATLAB carries as TEXT — output.algorithm, output.message — refuses by name and says so, rather than being quietly absent; output.exitflag is the same verdict as a number. A Curve Fit is what fit(x, y, model) answers: the fitted model itself, printed the way MATLAB prints a cfit — the model's name, its formula, and each coefficient with its confidence bounds. It is CALLABLE, so f(3) evaluates the fit at 3, and coeffvalues(f), confint(f) and f.p1 read it apart. Like the other model kinds it survives the variables space as its own text; Curve fitting in the command window is the page for what it can do. Matrices print in Python list form; the stored form is MATLAB syntax. A logical is stored as the WORDS — true, [true false], which is what mat2str writes for one — and that is what carries its class from one line to the next: a stored [1 0] would read back as an ordinary double row.
  • format long shows more digits, format short (the default) fewer. It is display only: the value never changes, so pi == 3.141592653589793 is true in both.
  • Read: the bare name echoes it; use it in any expression.
  • List: who prints the names in the store, and whos adds each one's size, byte count and class. The left panel page Variables is the table view of the same store.
  • Clear: clear (or clearvars) removes variables — clear x y only those two — and clearVariablesSpace empties the store; clearAll also wipes the logs and the recipe handles.
  • One store, project-wide. There is exactly one variables space; blocks in every subsystem see it. Its variables are saved with the project: the .iproj file is recipe text and its first lines are name = value; declarations (generateRecipe shows exactly what will be written; see the run below). A headless --console process starts with an empty store and an unsaved project, so nothing carries from one invocation to the next.
  • A block can read a console variable. Type a variable's name into a block parameter (Gain Value = K). When the run starts the block's configuration is loaded and every parameter whose text is not itself a number or matrix is looked up by name in the variables space; a numeric hit supplies the matrix, a miss leaves the text as a string (ICoreBlockSolverEnvironment::loadBlockConfig, src/ICoreBlocks/ICoreModel/SolverEnvironments/). The lookup happens at run start, so change K and re-run; the block does not track it live.
  • v = handle.get(prop) / v = handle.getConfig(name) store a numeric snapshot as an ordinary variable — later block edits do not update v, and text properties refuse to bind.

true and false#

The console has MATLAB's logical class, and it is a class rather than a different number: a logical array holds ones and zeros like any other, and what differs is what it reports and what the operations that read a class do with it.

  • What answers a logical: every comparison (1 < 2), every connective (&, |, ~, xor), any/all, every is* question (isnan, isempty, isequal, ischar, …), the strcmp family, contains, startsWith, endsWith, and the constants true and false — including their sized forms true(n), true(m, n), false(m, n).
  • What leaves it behind: arithmetic. true + 1 is the double 2, -true is -1, and sum([true true]) is 2 — MATLAB does not carry the class through a sum, and neither does this console.
  • A bracket stays logical only when every element is one: [true false] is a logical row, [true 1] a double one.
  • logical(A) converts — every non-zero entry becomes 1 — and refuses a NaN rather than converting it, which is MATLAB's own rule and its own sentence. double(A) is the way back. islogical(x) asks, and class(x) answers logical.
  • MATLAB does not count a logical as numeric: isnumeric(true) is 0, and so is isa(true, "numeric").

Strings#

The console has one string kind and it is a MATLAB char array — not MATLAB's newer string object, and not a free-text blob. Everything below was checked against MATLAB R2026a rather than reasoned about.

  • Both literal spellings work: 'hello' and "hello". Inside a '...' literal, '' is one quote ('it''s'), and there are no backslash escapes — sprintf is what turns \t into a tab, exactly as in MATLAB.
  • A ' transposes only when it continues a value with no space before it. a', [1 2]', f(x)' are transposes; 'abc', f('abc'), x = 'abc' are literals. That is MATLAB's own rule, and it is why ['a' 'b'] is ab (two literals) while [a' b'] is two transposes. MATLAB reads [a '] as an unterminated character vector, and so does this console.
  • Arithmetic on a string is arithmetic on its code points: 'a' + 1 is 98, double('abc') is [97 98 99], -'a' is -97. To join text use [s1 s2] or strcat(...) — and they differ: strcat drops each argument's trailing whitespace (strcat('a ', 'b') is ab) while the bracket keeps it (['a ' 'b'] is a b). A number in a bracket beside a string becomes a character: ['ab' 66] is abB.
  • Size: length, numel and strlength all answer the character count, size('abc') is [1 3], and isempty('') is 1.

What is here, by name:

formatsprintf(fmt, ...)
number ↔ textnum2str int2str mat2str str2num str2double string char double
join and comparestrcat · strcmp strcmpi strncmp strncmpi
case and trimmingupper lower strtrim deblank blanks newline
search and replacestrfind strrep replace contains startsWith endsWith extractBefore extractAfter
sizelength numel strlength isempty size
patternsregexp regexpi regexprep
printingdisp display fprintf

Three behaviours are worth knowing before they surprise you, because each is MATLAB's and each is the opposite of the obvious rule:

  • sprintf's format cycles until the arguments run out and then stops at the conversion it cannot fill, keeping the text before it: sprintf('%d-%d,', [1 2 3]) is 1-2,3-. An integer conversion handed a non-integer switches to %e: sprintf('%d', 1.5) is 1.500000e+00.
  • strrep and replace disagree on overlapping matches, and neither is wrong: strrep('aaaa','aa','b') is bbb (it fires at every position strfind reports), replace('aaaa','aa','b') is bb (it scans past what it matched).
  • num2str picks its field width from the data, so num2str([1 2.5]) is 1 followed by nine spaces and 2.5. Use mat2str when you want text that reads back as the value — its default is 15 significant digits where num2str's is five.

Regular expressions use ECMAScript syntax, not MATLAB's own. The common subset — classes, quantifiers, anchors, groups, alternation, \d \w \s, and $1 in a replacement — means the same thing in both; $0 (the whole match) is translated for you. A pattern this engine cannot read is refused with its reason rather than matched differently. regexp(s, pat) gives the start of every match, "end" the ends, "once" the first, and "once", "match" the matched text; the modes that answer a list of every match need a cell array, which this console does not have.

fprintf prints exactly what sprintf builds — the same format, the same cycling — so anything true of one is true of the other; the difference is that sprintf hands you the text and fprintf writes it. fprintf(1, ...) and fprintf(2, ...) are MATLAB's stdout and stderr and are one stream here. In MATLAB fprintf also returns a byte count; here it returns nothing, so n = fprintf(...) is refused — sprintf is what answers.

Not here, and each refuses by name rather than approximating: strsplit, split, strjoin and join (they need a cell array or a string array, and this console has neither); regexp's "match" and "tokens" modes without "once", for the same reason; num2str, char or double of something that would be a char matrix; and the date family.

Things that surprise users#

(More symptoms, beyond the console, are collected on Troubleshooting — the messages you will meet and what to do.)

  • unknown command: x for a variable you just made — each --console process is fresh, and clearVariablesSpace/clearAll empty the store; a bare name that is not a variable, a handle, a constant or a command falls all the way through to the command dispatcher, whose message this is. Same cause for a typo in a variable name.
  • A variable named pi really does hide the constant — the seven constants below are looked up after the command engine and the variables space, so pi = 3 makes pi three until you clear it. That is MATLAB's rule, not an accident.
  • 2 ^ 3 ^ 2 is 64, not 512 — ^ is left-associative here, as it is in MATLAB and unlike most languages, so it reads (2^3)^2. In the same family: -2^2 is -(2^2) = −4, because unary minus binds looser than ^, while the exponent may still carry its own sign (2^-1 is 0.5). Bounds and precision of the evaluator: Numerics — what the solver will and will not do.
  • A^0.5 and A^B are refused on matrices — MATLAB answers those with a matrix function (sqrtm, funm); the console says so rather than approximating. Write sqrtm(A). A^n for a square A and an integer n — including a negative one, which goes through the inverse — is fine.
  • A chained line printed only its last result — every statement you closed with ; was silent by design; only the unterminated tail speaks.
  • A failing statement stops the rest of the line — statements after the failing one do not run; the exit code is 1. Dividing by zero is not one of those failures: 1/0 is Inf, 0/0 is NaN, exactly as in MATLAB.
  • A variable named like a command will not echo — clear, help, echo and every other registered name are dispatched as commands first; such a variable is reachable only inside an expression. Pick another name.
  • Tab completed nothing — completion only offers at a statement head, right after =, or right after a . (ICoreCompletion::tokenStart); mid-argument it stays quiet, and it completes names from the glossary only (commands, matrix functions, recipe verbs) — not your variables, not block types.
  • The suggestion popup ate my Enter — with the popup open, Enter on an entry you arrowed onto takes the entry; Enter with nothing highlighted closes the list and submits the line exactly as typed.
  • Up arrow recalls last week's commands — history is on disk (below).
  • The .iproj grew a K = 2.5; line — that is the variables space being saved with the project. It is intended.

Tab, the popup, and history#

  • Tab completes the current token from the glossary (case-insensitive prefix match): the longest common prefix is filled in, and if more than one name is still open the popup lists them (at most 8 visible, scrolling). If an entry is already highlighted, Tab takes it exactly as Enter does.
  • The popup appears as you type at a completable position and filters by prefix. Up/Down move inside it; Enter takes the highlighted entry; clicking an entry does the same.
  • Up/Down with the popup closed walk the submitted-line history (newest first); the half-typed draft is kept and comes back at the bottom.
  • History is persisted in commandHistory.txt inside the app's data folder (on macOS ~/Library/Application Support/ICore Blocks/), appended on every submit — a crash loses nothing. Settings → Editor → Command Console → Command History Kept sets how many lines Up can reach (default 500, max 5000; 0 stops recording without erasing). clearCommandHistory deletes the file and empties the in-memory list.
  • glossary prints the full catalog the popup draws from; help lists the registered commands only.
  • help <name> describes one name — command, function, keyword or recipe verb — and, for a function MATLAB keeps in one of its toolboxes, says which toolbox the name comes from. That line is worth reading before you copy a call into MATLAB: it is what tells you the other side needs a licence for it. When it ends in extra, the name is this console's own rather than MATLAB's and keeps its own arguments, so a call written for MATLAB is not the same call:

    ``` > help pole pole - function pole(G|sys) Poles of a transfer function or state-space model toolbox: Control System Toolbox

    > help butterlp butterlp - function butterlp(order,cutoffHz,fsHz) Discrete Butterworth lowpass transfer function toolbox: Signal Processing Toolbox (extra: the console's own name, not MATLAB's -- it keeps its own signature, so a call written for MATLAB is not this one) ```

    Most names have no toolbox line at all: they are core MATLAB, which needs no licence beyond MATLAB itself.

  • Ctrl+wheel / Ctrl+plus/minus / Ctrl+0 zoom the transcript; cls or clc wipe it; clearAllLogs wipes every output log.

Scripts#

A script is a plain-text file with the extension .icore, one console line per line; blank lines and comment lines (# or %) are skipped, ... continues a statement onto the next line, and %{ … %} comments out a run of lines. It may contain anything the prompt accepts — recipe statements, assignments, expressions, commands. Scripts live in <project folder>/scripts and are run from the ICore Script IDE panel (Run for the open script, Run All for every script in the folder, name order); a script stops at its first failing line and the panel reports which line. A block spanning several lines runs as one unit when its end arrives, and a failure inside it names the statement that failed, not the line that closed the block. A script that ends inside a block fails at the line that opened it, with 'for' block opened on line N is still open at the end of the script (missing 'end'). The recorded form of a diagram is itself an .icore (Import Diagram, generateRecipe, saveTemplate/useTemplate) — the whole story is on Recipes and templates — recording, replaying and reusing diagrams. From the prompt or from another script, run <name> runs <project>/scripts/<name>.icore in the same workspace (a script in a subfolder is run tools/fit); a failure inside it reads <name>.icore:<line>: …, one prefix per script it passed through, and a script that runs itself — directly or round a loop — is refused by name.

The ICore Script IDE is also where scripts are written and fixed: an editor that lays out blocks, pairs brackets, underlines problems before a run, completes the script's own names and goes to definitions, with Problems and Variables panes and Run Line. It has its own page, The ICore Script IDE — writing, running, debugging and fixing .icore scripts.

Bringing a MATLAB .m in#

The ICore Script IDE's Import MATLAB button (and the console's matlabImportScript <absolute .m path> [script name] [--overwrite]) translates a MATLAB file's math statements into a new .icore. It is the mirror of matlabExportScript, and it runs the same dictionary backwards, so what crosses is exactly what the console can say: assignments, matrix literals, operators, and the functions the bridge's catalog names. Two rules make the result trustworthy rather than convenient:

  • Nothing is guessed. A statement crosses only when the mapping is a catalog row or a composition of catalog rows. Anything else is refused with a reason and kept as a # ICORE-NOT-IMPORTED(reason): <the MATLAB line> comment, so the script you land in accounts for the whole .m. The refusals you will meet first are control flow (if, for, while, switch, function: the console runs them, but the import reads one statement at a time and a block spans several), and a call to a name that is neither in the catalog nor assigned earlier in the file — q = A(1, 2) with no A = … above it reads as a call to a function A.
  • Nothing is overwritten silently. The target is <project>/scripts/<the .m file's stem>.icore; if it already exists the panel asks before replacing it and the command refuses without --overwrite.

Session noise with no console form — format long, for one — is dropped rather than refused, and counted in the summary so the drop is never silent. clc, clear, close all, figure and xlabel have console forms, and cross as themselves.

Importing the ten-statement example below, run against the build named under Real runs:

>>> matlabImportScript /tmp/mbimp/example.m
10 of 11 statement(s) translated to <project>/scripts/example.icore
  line 'if det(A) > 0, disp(x), end' skipped: 'if' -- the console runs control flow, but this import reads one statement at a time and a block spans several
exit=0
the .mthe .icore it wrote
clc; clear all;clc · clear all
A = [4 -2; 1 3];A = [4, -2; 1, 3];
b = [1; 2];b = [1; 2];
x = A \ b;x = solve(A, b);
[Q, R] = qr(A);[Q, R] = qr(A);
s = sum(A); % column sumss = sum(A); · # column sums
t = 0:0.25:1;t = 0:0.25:1;
Z = zeros(2, 3);Z = zeros(2, 3);
r = A(1, :);r = A(1, :);
if det(A) > 0, disp(x), end# ICORE-NOT-IMPORTED('if' -- the console runs control flow, but this import reads one statement at a time and a block spans several): if det(A) > 0, disp(x), end

Four of those lines crossed as something else earlier in 2026 and cross as themselves now, which is the direction this dictionary keeps moving in: the factorization statement, the range, the index and sum all mean on this console exactly what they mean in MATLAB, so there is nothing left to translate in them — zeros was the last of the nine to move, and it moved on 2026-09-02: it named transmission zeros on this console, so an imported zeros(2, 3) had to be spelled repmat(0, 2, 3) to keep it away from that meaning. Transmission zeros are zero(G) now, which is what MATLAB calls them.

The dictionary still exists for the entries that have not converged, and its job is exactly this: where a name means two things, the import writes the console function that means what the MATLAB line meant, rather than the one that merely looks like it. fromMatlab <one MATLAB statement> prints a single line's translation without writing a file. Paste the statement without its trailing ;: the console reads ; before any command does, so fromMatlab x = 5; is a silenced command that prints nothing, and fromMatlab a = 1; b = 2 translates the first statement and runs the second. A whole .m file, semicolons and all, is what matlabImportScript and the Import MATLAB button are for.

The console and the canvas#

Recipe statements act on the open project: block(Type, Parent) creates a block in a subsystem (parent defaults to Home), connect(a<1>, b<0>) links output port 1 of a to input port 0 of b, .setConfig(name, value), .move, .rename, .delete edit it, .info and .listConfig report it, and generateRecipe [path] prints the recipe that reproduces a whole level. Handles (g, s) are names for live objects and are listed in the Objects Tracker panel; .delete deletes outright rather than moving to the Trash. Every statement that edits the diagram is one step of the editor's Undo, so Ctrl+Z in the editor takes back a console edit. Headlessly "the open diagram" is the empty, unsaved project the app starts with — the run below creates a block at ICore Blocks/Home/Gain — so a --console recipe is a way to test a line, not a way to edit a saved project (open it in the editor for that, or use the ICore Script IDE). Verbs are listed under Recipe verbs in Command glossary — console commands, verbs, functions.

Real runs#

Binary ICoreBlocks.app built 2026-09-25 23:58 (source at approximately commit 1dac24c5), re-run 2026-09-26. Every run below is one process, HOME=<scratch> ICoreBlocks --console "<line>", with the startup lines removed; exit= is the process exit code.

(a) Arithmetic and variables

>>> x = 3; y = x*[1 2; 3 4]; y
y = [[3, 6], [9, 12]]  # Matrix of Double
exit=0

>>> A = [1 2; 3 4]; [A', A]
[[1, 3, 1, 2], [2, 4, 3, 4]]  # Matrix of Double
exit=0

>>> A = [1 2; 3 4]; A' | A
[[1, 1], [1, 1]]  # Matrix of Logical
exit=0

>>> label = hello; label
label = 'hello'  # String
exit=0

>>> x = 3; clearVariablesSpace; x
unknown command: x
exit=1

(' is transpose. The two lines are the same characters read two ways: the bracket concatenates, and | is logical OR — every entry of A and A' is non-zero, so the OR is all ones. A' | A was the concatenation until 2026-09-02; the grammar is in src/ICoreBlocks/ICoreMath/ICoreExpressionEvaluator.cpp.)

(b) An unknown command

>>> frobnicate 3
unknown command: frobnicate
exit=1

(c) Recipe verbs on the headless diagram

>>> K = 2.5; g = block(Gain); g.setConfig(Gain Value, K); g.listConfig; generateRecipe
K = 2.5;
Gain = block(Control_Systems/Base_Blocks/Gain);
Gain.rename(Gain);
Gain.move(0, 0);
Gain.resize(70, 70);
Gain.setConfig(Sampling Time (s), -1);
Gain.setConfig(Gain Value, K);
Gain.setConfig(Multiplication Type, Element-wise (K.*u)%~%Matrix (K*u)%~%Matrix (u*K)%~%Matrix (K*u) (u vector)~~Element-wise (K.*u));
subsystemTimes(ICore Blocks/Home, 1790448230285, 1790448232624);
exit=0

>>> g = block(Gain); s = block(Step); connect(s<0>, g<0>); g.getConfig(Gain Value)
Gain Value = 1
exit=0

g.info on the same block printed the full property sheet (name, type, fullType: Control_Systems/Base_Blocks/Gain, path: ICore Blocks/Home/Gain, size, rotation, ports and the block's description) — 75 lines, not repeated here. Note the first line of the recipe: the console variable K is part of what the project saves.

(d) A statement chain with and without the trailing ;

>>> x = 3; y = x*2
y = 6  # Integer
exit=0

>>> x = 3; y = x*2;

exit=0

>>> x = 3; x; x*2; 1/3
0.333333  # Double
exit=0

>>> x = 3; y = inv([1 2 3]); y
error: inv() needs a square matrix
exit=1

>>> x = 3; y = x/0; y
y = Inf  # Double
exit=0

(e) The operators MATLAB users reach for first

>>> [-2 ^ 2, 2 ^ 3 ^ 2, 2 ^ -1]
[[-4, 64, 0.5]]  # Matrix of Double
exit=0

>>> [1 2; 3 4] \ [1; 2]
[[4.96507e-16], [0.5]]  # Matrix of Double
exit=0

(f) Comparing, testing, ranging and indexing

>>> [1 2 3] >= 2
[[0, 1, 1]]  # Matrix of Logical
exit=0

>>> 0.1 + 0.2 == 0.3
0  # Logical
exit=0

>>> (3 > 2) && (2 > 1)
1  # Logical
exit=0

>>> 0:0.25:1
[[0, 0.25, 0.5, 0.75, 1]]  # Matrix of Double
exit=0

>>> v = [3 4 5]; v(end - 1)
4  # Integer
exit=0

>>> v = [3 4 5]; v(v > 3)
[[4, 5]]  # Matrix of Double
exit=0

>>> A = [1 2; 3 4]; A(:)
[[1], [3], [2], [4]]  # Matrix of Double
exit=0

>>> x = [1 2 3]; x(5) = 7; x
x = [[1, 2, 3, 0, 7]]  # Matrix of Double
exit=0

>>> A = [1 2; 3 4]; A(:, 2) = []; A
A = [[1], [3]]  # Matrix of Double
exit=0

Blocks (a) to (f) are all from the run named at the top of this section. \ is least squares when the matrix is taller than it is wide, so the residual noise in (e)'s second line is the QR's, not a wrong answer. 0.1 + 0.2 == 0.3 really is 0 — == compares exactly, and those two doubles are not that double.

(g) A 100,000-iteration loop, before and after the block hold

Both binaries built from commit b94aeadfa in the same build tree, on the same machine, differing only by the change described above; each line is one --console process timed whole, and loop is that total minus the startup floor measured the same way (1+1).

                             process    loop
  1+1                          2.199s      —     (startup floor, before)
  1+1                          1.794s      —     (startup floor, after)

  s = 0; for i = 1:100000, s = s + i; end; s
    before                   123.886s  121.69s   1.22 ms per iteration
    after                      8.633s    6.84s   68 us per iteration

17.8x, and the answer is the same on both sides (s = 5.00005e+09). The 121.69 s was never arithmetic: it was 100,000 iterations, two variable writes each, and one whole-project save per write. A loop is still not the fast way to do arithmetic here — 68 us a pass is the cost of reading each line as a statement, so prefer a whole-matrix expression (sum(1:100000)) when one says what you mean — but a loop of this size is now something you can run at the prompt and wait for.

For contributors#

The evaluator's order of resolution is ICoreConsoleInterpreter::evaluateLine (src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/; the window in src/ICoreBlocks/ICoreStudio/StudioObjects/Panels/CommandWindow/ forwards to it and feeds an interpreter instance so a block is held until its end), and the console-facing invariants — result handlers, main-thread only, the ; terminator the serializer emits, --console exit codes — are stated on the contributor page for the command system (icorecoder), with the expression grammar on icoremath and the --console hook on shell.