Nim Fusion

repository·master·Indexed 40 days ago

https://github.com/nim-lang/fusion

A curated collection of Nim modules providing an extended ecosystem between the core standard library and third-party Nimble packages. It features a powerful DSL for pattern matching against objects, tuples, sequences, tables, and sets, including specialized keywords for sequence matching and support for deep tree matching via block syntax.

Tokens
3.7K
Snippets
9
Records
16
Agent score
0%

What's inside Nim Fusion

  1. Overview of Nim Fusion

    master

    Nim Fusion is a collection of Nim modules designed to expand the Nim ecosystem. It serves as a repository for useful modules that are not part of the core standard library but are considered valuable for the community.

    Key characteristics include:

    • Ecosystem Growth: Provides candidates for potential future inclusion in the stdlib, though most modules are intended to remain in Fusion.
    • Broad Scope: Includes tools like HTML parsers or UI libraries that might not meet the strict cross-platform requirements of the core Nim standard library.
    • Immutability: Fusion aims to be an immutable repository; once a module is added, it stays. New modules are preferred to be small and focused.
    • Compatibility: It is distributed as a Nimble package and is compatible with Nim version 1 and the latest Nim releases.
  2. Match Option types

    master

    The library provides special handling for Option[T] types. The patterns Some(@x) and None() are rewritten internally to match the structure (isSome: true, get: @x) and (isNone: true).

    This works with any type that implements isSome(): bool, isNone(): bool, and get(): T.

  3. Match variant objects (AST nodes) using sugar

    master

    When matching on the .kind field of an object (common in AST manipulation), you can use concise syntax sugar. For example, ForStmt() is functionally equivalent to (kind: nnkForStmt).

    If your enum names follow nep1 conventions (starting with an underscore), the nnk prefix can often be omitted. You can also check against multiple kinds using the in operator: (kind: in {nnkForStmt, nnkWhileStmt}).

    # Concise syntax for matching AST kinds
    case node:
      of ForStmt([== ident("i"), .._]):
        # Only for loops with "i" as variable
      of ForStmt([@a is Ident(), .._]):
        # Capture loop variable
  4. How element access works in patterns

    master

    When matching, Nim Fusion generates an access path (a sequence of fields and [] operators).

    • Field access: (fld: "3") generates expr.fld == "3".
    • Index access: ["2"] generates expr[pos] == "2" where pos is the current integer index.
    • Table access: {"key": "val"} checks "key" in expr and then expr["key"] == "val". It fails the match if the key is missing rather than throwing an exception.
    • Mixed access: You can combine access types, e.g., (gg: _, [], {}).
    • Procedures: You can call functions within patterns, such as (len: _(it < 10)) or (len: in {0 .. 10}) to check sequence length.
  5. Match object fields and use predicates

    master

    Match object fields using the (fld: pattern) syntax.

    • Equality/Recursive Match: (fld: "value") matches if the field equals the value.
    • Infix Operators: You can use explicit operators like =~ from std/pegs for pattern matching. When using an infix operator, an implicitly injected matches variable becomes available in the branch.
    • Custom Predicates: Use _.predicate(args) to match using a custom function. If you need to capture the value being tested, replace _ with @capture.
    • Ranges: Use the in operator for set membership, e.g., (fld: in {1 .. 10}).
    # Using a custom predicate
    proc lenEq(s: openarray[int], value: int): bool = s.len == value
    
    case [1, 2]:
      of _.lenEq(2):
        # matches
    
    # Capturing with a predicate
    let arr = @[@[1, 2], @[2, 3], @[4]]
    # Note: capture is available if using @capture
  6. Match ref objects with the 'of' operator

    master

    To match derived ref objects and access fields specific to those types, use the of operator. This allows for runtime selection of types and performs an implicit isNil() check.

    • Use of Type(field: @capture) to match a specific derived type and capture its fields.
    • Use of BaseType(field: @default) to match all non-nil elements of a base type.
    • of can be used as a prefix operator: [any of Derived()] or {12 : of SubRoot(fld1: @fld1)}.
    type
      Base1 = ref object of RootObj
        fld: int
      First1 = ref object of Base1
        first: float
    
    let elem: Base1 = First1(fld: 456, first: 0.123)
    
    case elem:
      of of First1(fld: @capture1, first: @first):
        # Matches First1 and captures fields
        doAssert capture1 == 456
      of of Base1(fld: @default):
        # Matches non-nil Base1 elements
        doAssert default == 456
  7. Match tuples and use fallback values

    master

    You can match against tuples using standard pattern syntax. Elements can be matched using wildcards _, captures @capture, or specific values.

    Important: Backtracking is not performed. If a capture @a is bound to a value and a subsequent part of the pattern fails, the match fails immediately rather than trying to re-bind @a to a different value.

    To handle optional elements or provide fallback values during unpacking, use the opt @capture or "default" syntax.

    # Unpacking with fallback for optional elements
    (@a, (@b, _), _) := ("hello", ("world", 11), 0.2)
  8. Supported match elements in Nim Fusion

    master

    Nim Fusion supports matching the following collection types:

    • Sequences (seqs): Matched using [Patt1(), Patt2(), ..]. Requires len(): int and iterator items(): T.
    • Tuples: Matched using (Patt1(), Patt2(), ..).
    • Pairable (Tables/Key-Value): Matched using {Key: Patt()}. Requires [Key]: T. Note: Key is not a pattern; the whole collection is not searched for the key.
    • Sets: Matched using {Val1, Val2, .._}. Requires contains. If a variable is captured, Val1 must be comparable and the collection must implement items and incl.
    • Objects: Matched using (field: Val). Case objects are matched using Kind(field: Val). You can check against multiple kinds using (kind: in SomeSetOfKinds).
  9. Quick reference for Nim Fusion pattern matching

    master

    Nim Fusion provides a DSL for pattern matching against objects, tuples, sequences, tables, and sets. Use the following syntax for common patterns:

    PatternExplanation
    (fld: @val)Field fld into variable @val
    Kind()Object with .kind == Kind() (supports short enum names)
    of Derived()Match object of derived type
    (@val, _)First element in tuple in @val
    (@val, @val)Tuple with two equal elements
    {"key" : @val}Table with "key", capture into @val
    [_, _]Sequence with len == 2
    [_, .._]At least one element
    [_, all @val]All elements starting from index 1
    [until @val == "2", .._]Capture all elements until first "2"
    [until @val == 1, @val]All including first match
    [all @val == 12]All elements are == 12, capture into @val
    [some @val == 12]At least one is == 12, capture all matching into @val
  10. Perform deep tree matching with block syntax

    master

    For deeply nested structures like ASTs, you can use block syntax for patterns instead of long single-line expressions. This syntax is designed to be highly compatible with (and even copy-pastable from) the output of treeRepr for NimNode.

    # Using block syntax for complex patterns
    procDecl.assertMatch:
      ProcDef:
        @name is Ident() | Postfix[_, @name is Ident()]
        _
        _
        FormalParams:
          @returnType
          all IdentDefs[@trailArgsName, _, _]
        @pragmas
        _
        @implementation
  11. Match sequences with specialized keywords

    master

    Sequence matching uses [Patt1(), Patt2(), ..]. To match sequences of arbitrary length, you must include the trailing .._.

    Keywords

    • until <expr> (Non-greedy): Matches all elements until the first element that matches <expr>.
    • all <expr> (Greedy): Matches all elements that satisfy <expr>. Use all @val is <expr> to capture all matching elements into @val.
    • any <expr> (Greedy): Consumes elements until the end and succeeds if at least one element matches. Use any @val is <expr> to capture matching elements.
    • none <expr> (Greedy): Consumes elements until the end and succeeds only if no element matches.
    • opt @a (Optional): Matches an optional single element. If missing, @a is set to None(T) or a provided default (e.g., opt @a or "default").
    • [m .. n @capture]: Captures a slice of elements from index m to n.

    Examples

    Input SequencePatternResultComment
    [1,2,3,4,5,6,5,6][.._]Ok
    [1,2,3,4,5,6,5,6][@a, .._]Oka = 1
    [1,2,3,4,5,6,5,6][any @a(it < 10)]Oka = [1..6]
    [1,2,3,4,5,6,5,6][until @a == 6, .._]OkAll until first 6
    [1,2,3,4,5,6,5,6][all @a == 6, .._]Oka = [] (leading 6s)
    [1,2,3,4,5,6,5,6][any @a(it > 100)]FailNo elements > 100
    [1,2,3,4,5,6,5,6][none @a(it in {6 .. 10})]FailElement 6 exists
    [1,2,3,4,5,6,5,6][0 .. 2 is < 10, .._]OkFirst three elements < 10
    # Example: Capture all elements until first separator
    # [until @leading is "sep", @middle is "sep", all @trailing]
    
    # Example: Extract all conditions from an IfStmt AST
    # IfStmt([all ElseIf([@cond, _]), .._])
  12. Use pattern matching as a text scanner

    master

    Pattern matching can be used as a simple text scanner for arbitrary sequences (like tokens or strings), similar to strscans.

    Example of splitting a string and matching parts:

    func allIs(str: string, chars: set[char]): bool = str.allIt(it in chars)
    
    "2019-10-11 school start".split({'-', ' '}).assertMatch([
      pref @dateParts(it.allIs({'0' .. '9'})),
      pref _(it.allIs({' '})),
      all @text
    ])
    func allIs(str: string, chars: set[char]): bool = str.allIt(it in chars)
    
    "2019-10-11 school start".split({'-', ' '}).assertMatch([
      pref @dateParts(it.allIs({'0' .. '9'})),
      pref _(it.allIs({' '})),
      all @text
    ])
    
    doAssert dateParts == @["2019", "10", "11"]
    doAssert text == @["school", "start"]