NickelEval.jl

Julia FFI bindings for Nickel configuration language
Log | Files | Refs | README | LICENSE

detailed.md (5892B)


      1 # Detailed Examples
      2 
      3 ## Type Conversions
      4 
      5 ### Nickel to Julia Type Mapping
      6 
      7 | Nickel Type | Julia Type |
      8 |:------------|:-----------|
      9 | Null | `nothing` |
     10 | Bool | `Bool` |
     11 | Number (integer) | `Int64` |
     12 | Number (float) | `Float64` |
     13 | String | `String` |
     14 | Array | `Vector{Any}` |
     15 | Record | `Dict{String, Any}` |
     16 | Enum (tag only) | `NickelEnum(tag, nothing)` |
     17 | Enum (with argument) | `NickelEnum(tag, arg)` |
     18 
     19 ### Typed Evaluation
     20 
     21 Pass a type as the second argument to `nickel_eval` to convert the result:
     22 
     23 ```julia
     24 nickel_eval("42", Int)              # => 42::Int64
     25 nickel_eval("3.14", Float64)        # => 3.14::Float64
     26 nickel_eval("\"hello\"", String)    # => "hello"::String
     27 ```
     28 
     29 #### Typed Dicts
     30 
     31 ```julia
     32 nickel_eval("{ a = 1, b = 2 }", Dict{String, Int})
     33 # => Dict{String, Int64}("a" => 1, "b" => 2)
     34 
     35 nickel_eval("{ x = 1.5, y = 2.5 }", Dict{Symbol, Float64})
     36 # => Dict{Symbol, Float64}(:x => 1.5, :y => 2.5)
     37 ```
     38 
     39 #### Typed Vectors
     40 
     41 ```julia
     42 nickel_eval("[1, 2, 3]", Vector{Int})
     43 # => [1, 2, 3]::Vector{Int64}
     44 
     45 nickel_eval("[\"a\", \"b\", \"c\"]", Vector{String})
     46 # => ["a", "b", "c"]::Vector{String}
     47 ```
     48 
     49 #### NamedTuples
     50 
     51 Records can be converted to `NamedTuple` for convenient field access:
     52 
     53 ```julia
     54 server = nickel_eval(
     55     "{ host = \"localhost\", port = 8080 }",
     56     @NamedTuple{host::String, port::Int}
     57 )
     58 server.host  # => "localhost"
     59 server.port  # => 8080
     60 ```
     61 
     62 ## Enums — `NickelEnum`
     63 
     64 Nickel enums are represented as `NickelEnum`, a struct with two fields:
     65 - `tag::Symbol` — the variant name
     66 - `arg::Any` — the payload (`nothing` for bare tags)
     67 
     68 ### Simple Enum Tags
     69 
     70 ```julia
     71 result = nickel_eval("let x = 'Foo in x")
     72 result.tag   # => :Foo
     73 result.arg   # => nothing
     74 ```
     75 
     76 `NickelEnum` supports direct comparison with `Symbol`:
     77 
     78 ```julia
     79 result == :Foo   # => true
     80 result == :Bar   # => false
     81 ```
     82 
     83 ### Enums with Arguments
     84 
     85 Enum variants can carry a value of any type:
     86 
     87 ```julia
     88 # Integer payload
     89 result = nickel_eval("let x = 'Some 42 in x")
     90 result.tag   # => :Some
     91 result.arg   # => 42
     92 
     93 # String payload
     94 result = nickel_eval("let x = 'Message \"hello\" in x")
     95 result.tag   # => :Message
     96 result.arg   # => "hello"
     97 
     98 # Record payload
     99 result = nickel_eval("let x = 'Ok { value = 123, status = \"done\" } in x")
    100 result.tag                # => :Ok
    101 result.arg["value"]       # => 123
    102 result.arg["status"]      # => "done"
    103 
    104 # Array payload
    105 result = nickel_eval("let x = 'Batch [1, 2, 3] in x")
    106 result.arg                # => Any[1, 2, 3]
    107 ```
    108 
    109 ### Nested Enums
    110 
    111 Enums can appear inside records, arrays, or other enums:
    112 
    113 ```julia
    114 # Enum inside a record
    115 result = nickel_eval("{ status = 'Active, result = 'Ok 42 }")
    116 result["status"] == :Active        # => true
    117 result["result"].arg               # => 42
    118 
    119 # Enum inside another enum
    120 result = nickel_eval("let x = 'Container { inner = 'Value 42 } in x")
    121 result.tag                         # => :Container
    122 result.arg["inner"].tag            # => :Value
    123 result.arg["inner"].arg            # => 42
    124 
    125 # Array of enums
    126 result = nickel_eval("let x = 'List ['Some 1, 'None, 'Some 3] in x")
    127 result.arg[1].tag                  # => :Some
    128 result.arg[1].arg                  # => 1
    129 result.arg[2] == :None             # => true
    130 ```
    131 
    132 ### Pattern Matching
    133 
    134 Nickel's `match` resolves before reaching Julia — you get back the matched value:
    135 
    136 ```julia
    137 result = nickel_eval("""
    138 let x = 'Some 42 in
    139 x |> match {
    140   'Some v => v,
    141   'None => 0
    142 }
    143 """)
    144 # => 42
    145 
    146 result = nickel_eval("""
    147 let x = 'Some 42 in
    148 x |> match {
    149   'Some v => 'Doubled (v * 2),
    150   'None => 'Zero 0
    151 }
    152 """)
    153 result.tag   # => :Doubled
    154 result.arg   # => 84
    155 ```
    156 
    157 ### Pretty Printing
    158 
    159 `NickelEnum` displays in Nickel's own syntax:
    160 
    161 ```julia
    162 repr(nickel_eval("let x = 'None in x"))       # => "'None"
    163 repr(nickel_eval("let x = 'Some 42 in x"))     # => "'Some 42"
    164 repr(nickel_eval("let x = 'Msg \"hi\" in x"))  # => "'Msg \"hi\""
    165 ```
    166 
    167 ### Real-World Patterns
    168 
    169 #### Result Type
    170 
    171 ```julia
    172 code = """
    173 let divide = fun a b =>
    174   if b == 0 then
    175     'Err "division by zero"
    176   else
    177     'Ok (a / b)
    178 in
    179 divide 10 2
    180 """
    181 result = nickel_eval(code)
    182 result == :Ok    # => true
    183 result.arg       # => 5
    184 ```
    185 
    186 #### Option Type
    187 
    188 ```julia
    189 code = """
    190 let find = fun arr pred =>
    191   let matches = std.array.filter pred arr in
    192   if std.array.length matches == 0 then
    193     'None
    194   else
    195     'Some (std.array.first matches)
    196 in
    197 find [1, 2, 3, 4] (fun x => x > 2)
    198 """
    199 result = nickel_eval(code)
    200 result == :Some  # => true
    201 result.arg       # => 3
    202 ```
    203 
    204 #### State Machine
    205 
    206 ```julia
    207 result = nickel_eval("""
    208 let state = 'Running { progress = 75, task = "downloading" } in state
    209 """)
    210 result.tag               # => :Running
    211 result.arg["progress"]   # => 75
    212 result.arg["task"]       # => "downloading"
    213 ```
    214 
    215 ## Export Formats
    216 
    217 Evaluate Nickel code and serialize to JSON, TOML, or YAML:
    218 
    219 ```julia
    220 nickel_to_json("{ name = \"myapp\", version = \"1.0\" }")
    221 # => "{\n  \"name\": \"myapp\",\n  \"version\": \"1.0\"\n}"
    222 
    223 nickel_to_yaml("{ name = \"myapp\", version = \"1.0\" }")
    224 # => "name: myapp\nversion: '1.0'\n"
    225 
    226 nickel_to_toml("{ name = \"myapp\", version = \"1.0\" }")
    227 # => "name = \"myapp\"\nversion = \"1.0\"\n"
    228 ```
    229 
    230 ## File Evaluation with Imports
    231 
    232 Nickel files can import other Nickel files. `nickel_eval_file` resolves imports relative to the file's directory.
    233 
    234 Given these files:
    235 
    236 ```nickel
    237 # shared.ncl
    238 {
    239   project_name = "MyProject",
    240   version = "1.0.0"
    241 }
    242 ```
    243 
    244 ```nickel
    245 # config.ncl
    246 let shared = import "shared.ncl" in
    247 {
    248   name = shared.project_name,
    249   version = shared.version,
    250   debug = true
    251 }
    252 ```
    253 
    254 ```julia
    255 config = nickel_eval_file("config.ncl")
    256 config["name"]     # => "MyProject"
    257 config["version"]  # => "1.0.0"
    258 config["debug"]    # => true
    259 ```
    260 
    261 Subdirectory imports also work:
    262 
    263 ```nickel
    264 # lib/utils.ncl
    265 {
    266   helper = fun x => x * 2
    267 }
    268 ```
    269 
    270 ```nickel
    271 # main.ncl
    272 let utils = import "lib/utils.ncl" in
    273 { result = utils.helper 21 }
    274 ```
    275 
    276 ```julia
    277 nickel_eval_file("main.ncl")
    278 # => Dict("result" => 42)
    279 ```