Releases: Sydney680928/MOGWAI
Release list
MOGWAI v8.16.0
Added
-
condprimitive — evaluates a boolean expression written in standard infix notation and pushes a boolean result onto the stack, ready for use withif/then/else. Internally uses Dijkstra's Shunting-yard algorithm (viaBoolLexerandBoolShuntingYardinMOGWAI.Engine). Supports arithmetic and comparison operators (+ - * / < > <= >= == !=), boolean keywords (and,or,xor), unarynot(...), all MOGWAI sigils (@,&,!,$), primitives, constants, and variables.10 -> 'A' 25 -> 'B' if ("A < 20 and B > 10" cond) then { "yes" ? } else { "no" ? } # → yes if ("A * 2 < B + 10" cond) then { "yes" ? } else { "no" ? } # → yes (20 < 35) -
SugarBehaviorproperty onMogwaiEngine— exposes ~30 boolean flags to selectively enable or disable individual syntactic sugar constructs at parse time: loops (AllowForeverDo,AllowWhileDo,AllowDoWhile,AllowRepeat,AllowForDo,AllowForStepDo,AllowForeachDo/AllowForeachTransformDo/AllowForeachFilterDo), conditionals (AllowIfThen,AllowIfThenElse,AllowSwitch,AllowGuardElse,AllowTrap), function definitions (AllowToDoand itsWithDo/ParamsDo/ReturnsDovariants), store operators (AllowSto,AllowStoPlus,AllowStoSubstract,AllowStoMultiply,AllowStoDivide), and misc sugar (AllowTask,AllowClassDo,AllowAfterDo,AllowPost,AllowDeclare,AllowPipeRef,AllowOnEventDo,AllowTimerDo). All flags default totrue, preserving existing behavior. When a flag is disabled, the parser raises aMogwaiParseErrorExceptionif the corresponding construct is used.engine.SugarBehavior.AllowDuringDo = false; # Parsing "during 1000 do { ... }" now throws: # MogwaiParseErrorException: "Sugar DURING-DO is not allowed."
MOGWAI v8.15.0
Added
-
regex.isMatchprimitive — tests whether a string matches a regex pattern. Takesinput,patternand an optionaltimeout(in ms, defaults to 1000,0= no timeout). Returns a plain boolean. Inline .NET options ((?i),(?m),(?s),(?x)) are supported directly in the pattern. Raises MW.100 (invalid regex pattern) or MW.101 (timeout exceeded)."CAT" "(?i)cat" regex.isMatch ? # → true "dog" "cat" regex.isMatch ? # → false -
regex.matchprimitive — finds the first match of a regex pattern in a string. Same parameters asregex.isMatch. Returns a record withsuccess:, and on successvalue:,index:,length:,groups:(named capture groups, as a record) andgroupsByIndex:(all groups by position, position 0 = full match). Raises MW.100/MW.101 on error."2026-07-02" "^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$" regex.match -> 'result' result->groups: -> year: # → "2026" -
regex.matchesprimitive — finds all matches of a regex pattern in a string. Takesinput,pattern, an optionaltimeout(defaults to 1000ms) and an optionalmaxResults(defaults to 1000, must be greater than 0 if provided). Returns a record withmatches:(a list of records, each shaped likeregex.match's output) andtruncated:(trueifmaxResultswas reached before exhausting all matches). Raises MW.100/MW.101, or MW.22 (bad argument value) ifmaxResultsis not greater than 0."cat dog cat" "cat" regex.matches -> 'result' result->matches: count ? # → 2 -
regex.replaceprimitive — replaces all matches of a regex pattern in a string. Takesinput,pattern,replacementand an optionaltimeout(defaults to 1000ms). Supports native .NET backreference syntax inreplacement($1,${name}). Returns the resulting string. All matches are replaced, with no built-in limit on count. Raises MW.100/MW.101."2026-07-02" "(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})" "${day}/${month}/${year}" regex.replace ? # → "02/07/2026" -
regex.splitprimitive — splits a string on every match of a regex pattern. Takesinput,patternand an optionaltimeout(defaults to 1000ms). Returns a list of the pieces between matches. Unlike .NET's nativeRegex.Split, captured groups are never included in the result — only the split pieces themselves. Raises MW.100/MW.101."2026-07-02" "(-)" regex.split ? # → ("2026" "07" "02") -
New error codes MW.100 and MW.101 — reserved for the
regex.*primitive family: MW.100 (invalid regex pattern) and MW.101 (regex timeout exceeded). InvalidmaxResultsvalues inregex.matchesreuse the existing MW.22 (bad argument value).
Fixed
-
Parser — fixed a tokenizing bug where a string literal containing the same opening delimiter as the block currently being parsed (
(inside a list( ... ),[inside a record[ ... ],{inside a block{ ... },«inside« ... ») confused the parser's delimiter counting, causing it to expect an extra closing delimiter and fail to parse otherwise valid code. Characters inside string literals are no longer taken into account when matching delimiters.( "eee(ttt" ) # list containing a string with an unbalanced "(" [ x: "aaa[bbb" ] # record containing a string with an unbalanced "[" { "xxx{yyy" } # block containing a string with an unbalanced "{" « "xxx{yyy" » # function containing a string with an unbalanced "«" # all now parse correctly
MOGWAI v8.14.0
Added
-
http.headprimitive — sends an HTTP HEAD request. Identical tohttp.getbut no response body is returned by definition — only the headers. Useful for checking whether a resource exists or retrieving its metadata (size, type, last modified) without downloading its content. Takes a record withuri:(mandatory) andrequestHeaders:(optional). Returnsstate:,statusCode:,responseHeaders:and, on failure,error:. Theresponse:key is intentionally absent (HEAD never returns a body).[ uri: "https://api.example.com/resource" requestHeaders: [User-Agent: "MOGWAI"] ] http.head -> 'result' if (result->state:) then { "Content-Type: {! result->responseHeaders: Content-Type: get 0 get}" eval ? } else { "Failed - {! result->error:}" eval ? } -
http.putprimitive — sends an HTTP PUT request. Takes a record withuri:(mandatory),content:(mandatory, adata),requestHeaders:(optional record) andcontentHeaders:(optional record). Returns a record withstate:,statusCode:,response:(the response body asdata),responseHeaders:(a record mapping each header name to a list of values) and, on failure,error:.[ ! uri: "https://api.example.com/items/42" contentHeaders: [ Content-Type: "application/json" ] content: {! "{\"name\":\"updated\"}" ->utf8 } ] http.put -> 'result' if (result->state:) then { "OK - {! result->statusCode:}" eval ? } else { "Failed - {! result->error:}" eval ? } -
http.patchprimitive — sends an HTTP PATCH request. Same parameters and response shape ashttp.post/http.put. Unlikehttp.put, which replaces a resource entirely,http.patchis meant for partial updates (only the fields to change need to be sent).[ uri: "https://api.example.com/items/42" contentHeaders: [Content-Type: "application/json"] content: {! "{\"name\":\"updated\"}" ->utf8 } ] http.patch -> 'result' -
http.deleteprimitive — sends an HTTP DELETE request. Takes a record withuri:(mandatory) andrequestHeaders:(optional record). No request body is sent. Returns the same output record shape ashttp.put/http.get/http.post. A successful deletion often yields an emptyresponse:(HTTP 204 No Content), which is a valid emptydata, not an error.[ uri: "https://api.example.com/items/42" ] http.delete -> 'result' -
udp.sendprimitive — sends a UDP datagram to a host/port. Takes a record withhost:(mandatory),port:(mandatory),data:(mandatory) andlocalPort:(optional, ephemeral port if absent). Returnsstate: trueon success, orstate: falsewitherror:on failure.[ host: "127.0.0.1" port: 5000 data: {! "Hello from MOGWAI" ->utf8 } ] udp.send -> 'result' -
udp.receiveprimitive — listens on a local UDP port and waits for an incoming datagram. Takes a record withlocalPort:(mandatory) andtimeout:(mandatory, in ms). Returnsstate: truewithdata:,remoteHost:andremotePort:on success, orstate: falsewitherror: "timeout"if no datagram was received within the timeout.[ localPort: 5001 timeout: 3000 ] udp.receive -> 'result' -
udp.sendReceiveprimitive — sends a UDP datagram and waits for a response in a single operation. Takes a record withhost:(mandatory),port:(mandatory),data:(mandatory),timeout:(mandatory, in ms) andlocalPort:(optional, ephemeral port if absent). Returns the same output shape asudp.receive.[ host: "127.0.0.1" port: 5000 data: {! "Hello" ->utf8 } timeout: 3000 ] udp.sendReceive -> 'result' if (result->state:) then { result->data: ->utf8str ? } else { "Failed - {! result->error:}" eval ? }
Changed
http.get/http.postinternals — both primitives now share theirHttpClientinstance withhttp.put,http.patchandhttp.deleteat the runtime level (one instance per MOGWAI runtime, created lazily on first use), instead of instantiating a newHttpClientper call. This avoids socket exhaustion under sustained use. Request headers are now attached per-request (HttpRequestMessage.Headers) rather than on the shared client's default headers, keeping concurrent/successive calls isolated from each other.- HTTP response record —
http.getandhttp.postnow always read the response body and populateresponseHeaders:, even on HTTP error status codes (4xx/5xx), so scripts can inspect server-provided error details (e.g. a JSON error payload) instead of onlystate: false.responseHeaders:maps each header name to a list of values (never a bare string), since a header may legitimately appear multiple times. - HTTP error reporting — network failures now distinguish a request timeout (
error: "Request timed out") from other network errors (DNS, connection refused, TLS...) and from generic failures, instead of being collapsed into a single unspecific error.
Fixed
http.get— a malformedHttpRequestExceptionwith anullStatusCode(frequent on DNS/connection failures) could previously cause aNullReferenceExceptionthat masked the original error.statusCode:is now only populated when actually available.sum— callingsumon an empty list()previously raised MW.22 (bad argument value) instead of returning0. This broke natural aggregation patterns where a filtered or empty collection should sum to zero (e.g. summing download counts across a release's assets when a release has none).sumon()now returns0. A list containing non-number elements (e.g.(1 2 "E")) still raises MW.22, unchanged.
MOGWAI v8.13.0
Added
-
setRandomSeedprimitive — sets the seed of the random number generator, making subsequent random operations deterministic and reproducible. Takes an integer seed. Passingnulloremptyclears the seed, returning the generator to non-deterministic (time-based) behavior.234 setRandomSeed # subsequent random calls become deterministic null setRandomSeed # back to a non-deterministic seed empty setRandomSeed # same effect as null -
mogwai.primitiveInfoprimitive — returns a record with information about a given primitive. Takes anameand pushes a record containing the primitive'sname:and itsbirth:(the MOGWAI version it was introduced in, as a string). Raises MW.22 (bad argument value) ifnamedoes not match a known primitive.'calc' mogwai.primitiveInfo ? # → [name: 'calc' birth: "8.12.0"] -
insertprimitive — inserts an element at a given position in alistor adata. Takes the value to insert, the targetlist/data, and a zero-based index; an index equal to the collection's size appends at the end. Also works on references (&var) to alistordatavariable, mutating it in place.For
list, any value can be inserted. Fordata, the inserted value must be a byte (0–255); raises MW.22 if it isn't. In both cases, raises MW.22 if the index is out of range (negative or greater than the collection's size)."EEE" (1 2 3) 1 insert ? # → (1 "EEE" 2 3) 0xAA D:FFFFFFFF 1 insert ? # → D:FFAAFFFFFF (1 2 3) -> 'L' "EEE" &L 1 insert # L is now (1 "EEE" 2 3) -
sortprimitive — sorts a list in ascending order. Sorting only occurs if all elements of the list share the same type, and that type is one of.string,.number,.name,.keyor.word. Otherwise, the list is returned unchanged. Also works on a reference (&var) to a list variable, sorting it in place.(1 10 2 5) sort ? # → (1 2 5 10) -
Escape sequences in string literals — string literals now support escape sequences, which were previously taken literally (a
\ninside a string produced the two characters\andn, not a newline). Supported sequences:Sequence Character \"Double quote \\Backslash \0Null \aAlert (bell) \bBackspace \fForm feed \nNewline \rCarriage return \tHorizontal tab \vVertical tab Escaping is resolved in a single left-to-right pass, so consecutive backslashes are handled correctly (
\\nproduces a literal backslash followed byn, not a newline). An unrecognized escape sequence (e.g.\q) raises MW.22 (bad argument value)."Hello, \"World\" !" eval ? # → "Hello, "World" !" "Line1\nLine2" eval ? # → "Line1 # Line2" "C:\\Users\\test" eval ? # → "C:\Users\test"
Changed
MOGPrimitive.Birthproperty — everyMOGPrimitivenow exposes aBirthproperty of typeVersion, recording the MOGWAI version in which it was introduced. Defaults to8.0.0. All existing primitives have been updated with their correctBirthvalue.
MOGWAI v8.12.0
Added
-
calcprimitive — evaluates an infix mathematical expression given as a string and pushes the result onto the stack. Internally uses Dijkstra's Shunting-yard algorithm to convert the infix expression to RPN before execution. Supports the standard arithmetic operators (+,-,*,/), parentheses, all MOGWAI primitives and constants (sin,cos,sqrt,pow,PI,E, …), multi-argument functions (pow(2, 10)), local and global variables, and all MOGWAI sigils (@,&,!,$).500 -> 'X' 3.14 -> 'Y' "5 * X + (7 + sin(Y))" calc ? # → 2507.001... "sin(PI / 3)" calc ? # → 0.866... "pow(2, 10)" calc ? # → 1024
MOGWAI v8.11.0
Added
-
Hyperbolic functions — six new primitives mirroring the existing trigonometric set (
sin,cos,tan,asin,acos,atan). All map directly to theirMath.*counterparts in .NET.Primitive Description sinhHyperbolic sine. Mirrors Math.Sinh().coshHyperbolic cosine. Mirrors Math.Cosh().tanhHyperbolic tangent. Mirrors Math.Tanh().asinhInverse hyperbolic sine. Mirrors Math.Asinh().acoshInverse hyperbolic cosine. Mirrors Math.Acosh().atanhInverse hyperbolic tangent. Mirrors Math.Atanh().1.5 sinh ? # → 2.1292794550948173 1.5 cosh ? # → 2.352409615243247 0.9 tanh ? # → 0.7162978701990245 2.0 asinh ? # → 1.4436354751788103 2.0 acosh ? # → 1.3169578969248166 0.9 atanh ? # → 1.4721842907995872 -
str.repeatprimitive — builds a new string by repeating a source string a given number of times. Takes a string and a non-negative integer count; raises MW.22 if the count is negative. A count of0returns an empty string."E" 5 str.repeat ? # → "EEEEE" "ab" 3 str.repeat ? # → "ababab" "x" 0 str.repeat ? # → "" -
Version comparison primitives — seven new primitives for comparing version strings and validating version format. Version strings follow the
System.Versionformat:"major.minor","major.minor.revision", or"major.minor.revision.build". All comparison primitives take two version strings (a b) and push a boolean. If either argument is not a valid version string, MW.22 (bad argument value) is raised.Primitive Description ver?Returns trueif the string is a valid version,falseotherwise. Never raises an error.ver>Returns trueifa > b.ver<Returns trueifa < b.ver>=Returns trueifa >= b.ver<=Returns trueifa <= b.ver==Returns trueifa == b.ver!=Returns trueifa != b."8.10" ver? # → true "8" ver? # → false (major only not supported) "8.10.0.0" "8.2" ver> # → true "8.10.0.0" "8.10.0.0" ver== # → true mogwai.info->version: "8.10" ver>= # → true (typical runtime version check) -
String manipulation primitives — twelve new primitives covering search, transformation, padding, insertion, removal, and URL decoding.
Search & test
Primitive Signature Description str.indexOfstring search str.indexOfReturns the zero-based index of the first occurrence of searchinstring, or-1if not found. Case-sensitive.str.startsWithstring prefix str.startsWithReturns trueifstringstarts withprefix. Case-sensitive.str.endsWithstring suffix str.endsWithReturns trueifstringends withsuffix. Case-sensitive.Transformation
Primitive Signature Description str.replacestring old new str.replaceReplaces all occurrences of oldwithnewinstring. Case-sensitive.str.trimstring str.trimRemoves leading and trailing whitespace characters (spaces, tabs, \r,\n).str.trimStartstring str.trimStartRemoves leading whitespace characters only. str.trimEndstring str.trimEndRemoves trailing whitespace characters only. str.padLeftstring width str.padLeftPads stringon the left with spaces to reachwidth. Returnsstringunchanged if already at or abovewidth.str.padRightstring width str.padRightPads stringon the right with spaces to reachwidth. Returnsstringunchanged if already at or abovewidth.str.insertstring insertion index str.insertInserts insertionintostringat zero-basedindex. Raises MW.22 ifindex < 0orindex > size of string.str.removestring start count str.removeRemoves countcharacters fromstringstarting at zero-basedindexstart. Raises MW.22 ifstartorcountare invalid.Encoding
Primitive Signature Description ->urlDecodestring ->urlDecodeDecodes a URL-encoded string. Inverse of ->urlEncode."E;Y;5" ";" "--" str.replace ? # → "E--Y--5" "HELLO" "L" str.indexOf ? # → 2 "MOGWAI" "MO" str.startsWith ? # → true "MOGWAI" "WAI" str.endsWith ? # → true " MOGWAI " str.trim ? # → "MOGWAI" " MOGWAI " str.trimStart ? # → "MOGWAI " " MOGWAI " str.trimEnd ? # → " MOGWAI" "MOGWAI" 10 str.padLeft ? # → " MOGWAI" "HELLO LE MONDE" "-" 5 str.insert ? # → "HELLO- LE MONDE" "HELLO LE MONDE" 5 3 str.remove ? # → "HELLO MONDE" "Hello%20World" ->urlDecode ? # → "Hello World"
Changed
Fixed
timersyntax sugar — timer body left on the stack after parsing. When a timer was defined using thetimer 'name' every N do { ... }syntax, the parser correctly expanded the declaration but left the timer body on the stack as a residual value. This could silently corrupt subsequent stack operations. The residual value is now properly consumed by the parser.
MOGWAI v8.10.0
Added
-
roundprimitive — rounds a decimal number to the specified number of decimal places.
Whennis0, returns a whole number (no decimal point).5.78934 3 round ? # → 5.789 45.324322 0 round ? # → 45 -
logprimitive — returns the natural logarithm (base e) of a number. MirrorsMath.Log()in C#.40 log ? # → 3.6888794541139363 -
log10primitive — returns the base-10 logarithm of a number. MirrorsMath.Log10()in C#.34 log10 ? # → 1.5314789170422551 -
expprimitive — returns e raised to the specified power. MirrorsMath.Exp()in C#.23 exp ? # → 9744803446.248903 -
Eprimitive — pushes the value of Euler's number (e = 2.718…) onto the stack.
Complements the existingPIprimitive.E ? # → 2.718281828459045 -
gcdprimitive — returns the greatest common divisor of two integers, computed via
the Euclidean algorithm. Both values are taken as absolute integers before processing.345 4 gcd ? # → 1 -
lcmprimitive — returns the least common multiple of two integers. Both values are
taken as absolute integers. Returns0if either argument is0.345 4 lcm ? # → 1380
MOGWAI v8.9.1
Added
-
task.startprimitive — launches a task without parameters. Complementstask 'name' start withfor tasks that require no input.task 'T1' do { # no parameter expected "Working..." ? true task.setResult } 'T1' task.start 'T1' task.waitPreviously, launching a parameterless task required passing a dummy value (
nullorempty) and discarding it inside the task withclearordrop.task.starteliminates this workaround entirely.
Changed
-
Error identifiers — corrected misspelled names. Several public
Errorconstants carried spelling mistakes (Encounted,Unabled) or a grammatical slip (DoesNotExists) in their C# identifiers. They have been renamed for correctness:HaltEncountedError→HaltEncounteredErrorUnabledToFireEventError→UnableToFireEventErrorUnabledToWriteValueError→UnableToWriteValueErrorUnabledToWriteValueInUndeclaredVarError→UnableToWriteValueInUndeclaredVarErrorUnabledToStartTaskError→UnableToStartTaskErrorPathDoesNotExistsError→PathDoesNotExistError
Breaking (C# host code only): host applications that reference these error constants by name must update to the new identifiers. MOGWAI scripts are unaffected — they identify errors by code (
MW.x), never by constant name.
Fixed
-
Auto-evaluated records and lists —
!flag incorrectly retained after evaluation. When a record or list marked with!(auto-evaluation) was evaluated, the resulting object kept the auto-evaluation flag set. The final value was correct, but the engine was forced to re-evaluate the object on every subsequent access, incurring unnecessary overhead. The flag is now cleared on the evaluated result for both records and lists.[ ! x: rand y: rand ] # → evaluated record, ! flag cleared (! now 50 $X) # → evaluated list, ! flag cleared -
Error messages — corrected English wording. Several built-in error messages contained spelling or grammar mistakes: MW.2 (
encounted→encountered), MW.6 / MW.47 / MW.48 / MW.61 (unabled→unable), MW.41 (exits→exists) and MW.71 (does not exists→does not exist). MW.47 and MW.48 now also end witherror, consistent with every other message.
MOGWAI v8.8.2
Fixed
- String interpolation — quoted content in interpolated expressions caused premature truncation. When an interpolated expression (
{! ... }) contained double-quote characters, the string was incorrectly truncated at that point. Quoted content inside interpolated blocks is now handled correctly.
MOGWAI v8.8.1
Fixed
-
->json— null value serialized asnull!instead ofnull. When converting a null value to JSON via->json, the output contained a spurious!character (null!), producing invalid JSON. Null values are now correctly serialized asnull. -
KeepAlive mode — stack was cleared between operations. In KeepAlive mode, the stack must persist across successive operations, but it was being reset between each one, discarding any values left on the stack by previous operations. The stack is now correctly preserved between operations in KeepAlive mode.