Skip to content

Releases: Sydney680928/MOGWAI

MOGWAI v8.16.0

Choose a tag to compare

@Sydney680928 Sydney680928 released this 17 Aug 13:16

Added

  • cond primitive — evaluates a boolean expression written in standard infix notation and pushes a boolean result onto the stack, ready for use with if/then/else. Internally uses Dijkstra's Shunting-yard algorithm (via BoolLexer and BoolShuntingYard in MOGWAI.Engine). Supports arithmetic and comparison operators (+ - * / < > <= >= == !=), boolean keywords (and, or, xor), unary not(...), 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)
    
  • SugarBehavior property on MogwaiEngine — 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 (AllowToDo and its WithDo/ParamsDo/ReturnsDo variants), store operators (AllowSto, AllowStoPlus, AllowStoSubstract, AllowStoMultiply, AllowStoDivide), and misc sugar (AllowTask, AllowClassDo, AllowAfterDo, AllowPost, AllowDeclare, AllowPipeRef, AllowOnEventDo, AllowTimerDo). All flags default to true, preserving existing behavior. When a flag is disabled, the parser raises a MogwaiParseErrorException if 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

Choose a tag to compare

@Sydney680928 Sydney680928 released this 04 Jul 18:24

Added

  • regex.isMatch primitive — tests whether a string matches a regex pattern. Takes input, pattern and an optional timeout (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.match primitive — finds the first match of a regex pattern in a string. Same parameters as regex.isMatch. Returns a record with success:, and on success value:, index:, length:, groups: (named capture groups, as a record) and groupsByIndex: (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.matches primitive — finds all matches of a regex pattern in a string. Takes input, pattern, an optional timeout (defaults to 1000ms) and an optional maxResults (defaults to 1000, must be greater than 0 if provided). Returns a record with matches: (a list of records, each shaped like regex.match's output) and truncated: (true if maxResults was reached before exhausting all matches). Raises MW.100/MW.101, or MW.22 (bad argument value) if maxResults is not greater than 0.

    "cat dog cat" "cat" regex.matches -> 'result'
    result->matches: count ?    # → 2
    
  • regex.replace primitive — replaces all matches of a regex pattern in a string. Takes input, pattern, replacement and an optional timeout (defaults to 1000ms). Supports native .NET backreference syntax in replacement ($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.split primitive — splits a string on every match of a regex pattern. Takes input, pattern and an optional timeout (defaults to 1000ms). Returns a list of the pieces between matches. Unlike .NET's native Regex.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). Invalid maxResults values in regex.matches reuse 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

Choose a tag to compare

@Sydney680928 Sydney680928 released this 01 Jul 17:06

Added

  • http.head primitive — sends an HTTP HEAD request. Identical to http.get but 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 with uri: (mandatory) and requestHeaders: (optional). Returns state:, statusCode:, responseHeaders: and, on failure, error:. The response: 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.put primitive — sends an HTTP PUT request. Takes a record with uri: (mandatory), content: (mandatory, a data), requestHeaders: (optional record) and contentHeaders: (optional record). Returns a record with state:, statusCode:, response: (the response body as data), 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.patch primitive — sends an HTTP PATCH request. Same parameters and response shape as http.post/http.put. Unlike http.put, which replaces a resource entirely, http.patch is 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.delete primitive — sends an HTTP DELETE request. Takes a record with uri: (mandatory) and requestHeaders: (optional record). No request body is sent. Returns the same output record shape as http.put/http.get/http.post. A successful deletion often yields an empty response: (HTTP 204 No Content), which is a valid empty data, not an error.

    [ uri: "https://api.example.com/items/42" ] http.delete -> 'result'
    
  • udp.send primitive — sends a UDP datagram to a host/port. Takes a record with host: (mandatory), port: (mandatory), data: (mandatory) and localPort: (optional, ephemeral port if absent). Returns state: true on success, or state: false with error: on failure.

    [
        host: "127.0.0.1"
        port: 5000
        data: {! "Hello from MOGWAI" ->utf8 }
    ] udp.send -> 'result'
    
  • udp.receive primitive — listens on a local UDP port and waits for an incoming datagram. Takes a record with localPort: (mandatory) and timeout: (mandatory, in ms). Returns state: true with data:, remoteHost: and remotePort: on success, or state: false with error: "timeout" if no datagram was received within the timeout.

    [
        localPort: 5001
        timeout: 3000
    ] udp.receive -> 'result'
    
  • udp.sendReceive primitive — sends a UDP datagram and waits for a response in a single operation. Takes a record with host: (mandatory), port: (mandatory), data: (mandatory), timeout: (mandatory, in ms) and localPort: (optional, ephemeral port if absent). Returns the same output shape as udp.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.post internals — both primitives now share their HttpClient instance with http.put, http.patch and http.delete at the runtime level (one instance per MOGWAI runtime, created lazily on first use), instead of instantiating a new HttpClient per 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 recordhttp.get and http.post now always read the response body and populate responseHeaders:, even on HTTP error status codes (4xx/5xx), so scripts can inspect server-provided error details (e.g. a JSON error payload) instead of only state: 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 malformed HttpRequestException with a null StatusCode (frequent on DNS/connection failures) could previously cause a NullReferenceException that masked the original error. statusCode: is now only populated when actually available.
  • sum — calling sum on an empty list () previously raised MW.22 (bad argument value) instead of returning 0. 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). sum on () now returns 0. A list containing non-number elements (e.g. (1 2 "E")) still raises MW.22, unchanged.

MOGWAI v8.13.0

Choose a tag to compare

@Sydney680928 Sydney680928 released this 26 Jun 11:04

Added

  • setRandomSeed primitive — sets the seed of the random number generator, making subsequent random operations deterministic and reproducible. Takes an integer seed. Passing null or empty clears 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.primitiveInfo primitive — returns a record with information about a given primitive. Takes a name and pushes a record containing the primitive's name: and its birth: (the MOGWAI version it was introduced in, as a string). Raises MW.22 (bad argument value) if name does not match a known primitive.

    'calc' mogwai.primitiveInfo ?   # → [name: 'calc' birth: "8.12.0"]
    
  • insert primitive — inserts an element at a given position in a list or a data. Takes the value to insert, the target list/data, and a zero-based index; an index equal to the collection's size appends at the end. Also works on references (&var) to a list or data variable, mutating it in place.

    For list, any value can be inserted. For data, the inserted value must be a byte (0255); 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)
    
  • sort primitive — 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, .key or .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 \n inside a string produced the two characters \ and n, not a newline). Supported sequences:

    Sequence Character
    \" Double quote
    \\ Backslash
    \0 Null
    \a Alert (bell)
    \b Backspace
    \f Form feed
    \n Newline
    \r Carriage return
    \t Horizontal tab
    \v Vertical tab

    Escaping is resolved in a single left-to-right pass, so consecutive backslashes are handled correctly (\\n produces a literal backslash followed by n, 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.Birth property — every MOGPrimitive now exposes a Birth property of type Version, recording the MOGWAI version in which it was introduced. Defaults to 8.0.0. All existing primitives have been updated with their correct Birth value.

MOGWAI v8.12.0

Choose a tag to compare

@Sydney680928 Sydney680928 released this 17 Jun 15:07

Added

  • calc primitive — 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

Choose a tag to compare

@Sydney680928 Sydney680928 released this 16 Jun 14:34

Added

  • Hyperbolic functions — six new primitives mirroring the existing trigonometric set (sin, cos, tan, asin, acos, atan). All map directly to their Math.* counterparts in .NET.

    Primitive Description
    sinh Hyperbolic sine. Mirrors Math.Sinh().
    cosh Hyperbolic cosine. Mirrors Math.Cosh().
    tanh Hyperbolic tangent. Mirrors Math.Tanh().
    asinh Inverse hyperbolic sine. Mirrors Math.Asinh().
    acosh Inverse hyperbolic cosine. Mirrors Math.Acosh().
    atanh Inverse 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.repeat primitive — 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 of 0 returns 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.Version format: "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 true if the string is a valid version, false otherwise. Never raises an error.
    ver> Returns true if a > b.
    ver< Returns true if a < b.
    ver>= Returns true if a >= b.
    ver<= Returns true if a <= b.
    ver== Returns true if a == b.
    ver!= Returns true if a != 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.indexOf string search str.indexOf Returns the zero-based index of the first occurrence of search in string, or -1 if not found. Case-sensitive.
    str.startsWith string prefix str.startsWith Returns true if string starts with prefix. Case-sensitive.
    str.endsWith string suffix str.endsWith Returns true if string ends with suffix. Case-sensitive.

    Transformation

    Primitive Signature Description
    str.replace string old new str.replace Replaces all occurrences of old with new in string. Case-sensitive.
    str.trim string str.trim Removes leading and trailing whitespace characters (spaces, tabs, \r, \n).
    str.trimStart string str.trimStart Removes leading whitespace characters only.
    str.trimEnd string str.trimEnd Removes trailing whitespace characters only.
    str.padLeft string width str.padLeft Pads string on the left with spaces to reach width. Returns string unchanged if already at or above width.
    str.padRight string width str.padRight Pads string on the right with spaces to reach width. Returns string unchanged if already at or above width.
    str.insert string insertion index str.insert Inserts insertion into string at zero-based index. Raises MW.22 if index < 0 or index > size of string.
    str.remove string start count str.remove Removes count characters from string starting at zero-based index start. Raises MW.22 if start or count are invalid.

    Encoding

    Primitive Signature Description
    ->urlDecode string ->urlDecode Decodes 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

  • timer syntax sugar — timer body left on the stack after parsing. When a timer was defined using the timer '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

Choose a tag to compare

@Sydney680928 Sydney680928 released this 10 Jun 12:03

Added

  • round primitive — rounds a decimal number to the specified number of decimal places.
    When n is 0, returns a whole number (no decimal point).

    5.78934 3 round ?    # → 5.789
    45.324322 0 round ?  # → 45
    
  • log primitive — returns the natural logarithm (base e) of a number. Mirrors Math.Log() in C#.

    40 log ?   # → 3.6888794541139363
    
  • log10 primitive — returns the base-10 logarithm of a number. Mirrors Math.Log10() in C#.

    34 log10 ?   # → 1.5314789170422551
    
  • exp primitive — returns e raised to the specified power. Mirrors Math.Exp() in C#.

    23 exp ?   # → 9744803446.248903
    
  • E primitive — pushes the value of Euler's number (e = 2.718…) onto the stack.
    Complements the existing PI primitive.

    E ?   # → 2.718281828459045
    
  • gcd primitive — 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
    
  • lcm primitive — returns the least common multiple of two integers. Both values are
    taken as absolute integers. Returns 0 if either argument is 0.

    345 4 lcm ?   # → 1380
    

MOGWAI v8.9.1

Choose a tag to compare

@Sydney680928 Sydney680928 released this 08 Jun 10:17

Added

  • task.start primitive — launches a task without parameters. Complements task 'name' start with for tasks that require no input.

    task 'T1' do
    {
        # no parameter expected
        "Working..." ?
        true task.setResult
    }
    
    'T1' task.start
    'T1' task.wait
    

    Previously, launching a parameterless task required passing a dummy value (null or empty) and discarding it inside the task with clear or drop. task.start eliminates this workaround entirely.

Changed

  • Error identifiers — corrected misspelled names. Several public Error constants carried spelling mistakes (Encounted, Unabled) or a grammatical slip (DoesNotExists) in their C# identifiers. They have been renamed for correctness:

    • HaltEncountedErrorHaltEncounteredError
    • UnabledToFireEventErrorUnableToFireEventError
    • UnabledToWriteValueErrorUnableToWriteValueError
    • UnabledToWriteValueInUndeclaredVarErrorUnableToWriteValueInUndeclaredVarError
    • UnabledToStartTaskErrorUnableToStartTaskError
    • PathDoesNotExistsErrorPathDoesNotExistError

    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 (encountedencountered), MW.6 / MW.47 / MW.48 / MW.61 (unabledunable), MW.41 (exitsexists) and MW.71 (does not existsdoes not exist). MW.47 and MW.48 now also end with error, consistent with every other message.

MOGWAI v8.8.2

Choose a tag to compare

@Sydney680928 Sydney680928 released this 05 Jun 14:22

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

Choose a tag to compare

@Sydney680928 Sydney680928 released this 03 Jun 22:33

Fixed

  • ->json — null value serialized as null! instead of null. 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 as null.

  • 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.