test_eval.jl (16167B)
1 @testset "C API Evaluation" begin 2 @testset "Primitive types" begin 3 # Integers 4 @test nickel_eval("42") === Int64(42) 5 @test nickel_eval("-42") === Int64(-42) 6 @test nickel_eval("0") === Int64(0) 7 @test nickel_eval("1000000000000") === Int64(1000000000000) 8 9 # Floats (only true decimals) 10 @test nickel_eval("3.14") ≈ 3.14 11 @test nickel_eval("-2.718") ≈ -2.718 12 @test nickel_eval("0.5") ≈ 0.5 13 @test typeof(nickel_eval("3.14")) == Float64 14 15 # Booleans 16 @test nickel_eval("true") === true 17 @test nickel_eval("false") === false 18 19 # Null 20 @test nickel_eval("null") === nothing 21 22 # Strings 23 @test nickel_eval("\"hello\"") == "hello" 24 @test nickel_eval("\"\"") == "" 25 @test nickel_eval("\"hello 世界\"") == "hello 世界" 26 end 27 28 @testset "Arrays" begin 29 @test nickel_eval("[]") == Any[] 30 @test nickel_eval("[1, 2, 3]") == Any[1, 2, 3] 31 @test nickel_eval("[true, false]") == Any[true, false] 32 @test nickel_eval("[\"a\", \"b\"]") == Any["a", "b"] 33 34 # Nested arrays 35 result = nickel_eval("[[1, 2], [3, 4]]") 36 @test result == Any[Any[1, 2], Any[3, 4]] 37 38 # Mixed types 39 result = nickel_eval("[1, \"two\", true, null]") 40 @test result == Any[1, "two", true, nothing] 41 end 42 43 @testset "Records" begin 44 result = nickel_eval("{ x = 1 }") 45 @test result isa Dict{String, Any} 46 @test result["x"] === Int64(1) 47 48 result = nickel_eval("{ name = \"test\", count = 42 }") 49 @test result["name"] == "test" 50 @test result["count"] === Int64(42) 51 52 # Empty record 53 @test nickel_eval("{}") == Dict{String, Any}() 54 55 # Nested records 56 result = nickel_eval("{ outer = { inner = 42 } }") 57 @test result["outer"]["inner"] === Int64(42) 58 end 59 60 @testset "Type preservation" begin 61 @test typeof(nickel_eval("42")) == Int64 62 @test typeof(nickel_eval("42.5")) == Float64 63 @test typeof(nickel_eval("42.0")) == Int64 # whole numbers -> Int64 64 end 65 66 @testset "Computed values" begin 67 @test nickel_eval("1 + 2") === Int64(3) 68 @test nickel_eval("10 - 3") === Int64(7) 69 @test nickel_eval("let x = 10 in x * 2") === Int64(20) 70 @test nickel_eval("let add = fun x y => x + y in add 3 4") === Int64(7) 71 end 72 73 @testset "Record operations" begin 74 result = nickel_eval("{ a = 1 } & { b = 2 }") 75 @test result["a"] === Int64(1) 76 @test result["b"] === Int64(2) 77 end 78 79 @testset "Array operations" begin 80 result = nickel_eval("[1, 2, 3] |> std.array.map (fun x => x * 2)") 81 @test result == Any[2, 4, 6] 82 end 83 84 @testset "Enums - Simple (no argument)" begin 85 result = nickel_eval("let x = 'Foo in x") 86 @test result isa NickelEnum 87 @test result.tag == :Foo 88 @test result.arg === nothing 89 90 # Convenience comparison 91 @test result == :Foo 92 @test :Foo == result 93 @test result != :Bar 94 95 @test nickel_eval("let x = 'None in x").tag == :None 96 @test nickel_eval("let x = 'True in x").tag == :True 97 @test nickel_eval("let x = 'False in x").tag == :False 98 @test nickel_eval("let x = 'Pending in x").tag == :Pending 99 @test nickel_eval("let x = 'Red in x").tag == :Red 100 end 101 102 @testset "Enums - With primitive arguments" begin 103 # Integer argument 104 result = nickel_eval("let x = 'Count 42 in x") 105 @test result.tag == :Count 106 @test result.arg === Int64(42) 107 108 # Negative integer (needs parentheses in Nickel) 109 result = nickel_eval("let x = 'Offset (-100) in x") 110 @test result.arg === Int64(-100) 111 112 # Float argument 113 result = nickel_eval("let x = 'Temperature 98.6 in x") 114 @test result.tag == :Temperature 115 @test result.arg ≈ 98.6 116 117 # String argument 118 result = nickel_eval("let x = 'Message \"hello world\" in x") 119 @test result.tag == :Message 120 @test result.arg == "hello world" 121 122 # Empty string argument 123 result = nickel_eval("let x = 'Empty \"\" in x") 124 @test result.arg == "" 125 126 # Boolean arguments 127 result = nickel_eval("let x = 'Flag true in x") 128 @test result.arg === true 129 result = nickel_eval("let x = 'Flag false in x") 130 @test result.arg === false 131 132 # Null argument 133 result = nickel_eval("let x = 'Nullable null in x") 134 @test result.arg === nothing 135 end 136 137 @testset "Enums - With record arguments" begin 138 # Simple record argument 139 result = nickel_eval("let x = 'Ok { value = 123 } in x") 140 @test result.tag == :Ok 141 @test result.arg isa Dict{String, Any} 142 @test result.arg["value"] === Int64(123) 143 144 # Record with multiple fields 145 code = """ 146 let result = 'Ok { value = 123, message = "success" } in result 147 """ 148 result = nickel_eval(code) 149 @test result.arg["value"] === Int64(123) 150 @test result.arg["message"] == "success" 151 152 # Error with details 153 code = """ 154 let err = 'Error { code = 404, reason = "not found" } in err 155 """ 156 result = nickel_eval(code) 157 @test result.tag == :Error 158 @test result.arg["code"] === Int64(404) 159 @test result.arg["reason"] == "not found" 160 161 # Nested record in enum 162 code = """ 163 let x = 'Data { outer = { inner = 42 } } in x 164 """ 165 result = nickel_eval(code) 166 @test result.arg["outer"]["inner"] === Int64(42) 167 end 168 169 @testset "Enums - With array arguments" begin 170 # Array of integers 171 result = nickel_eval("let x = 'Batch [1, 2, 3, 4, 5] in x") 172 @test result.tag == :Batch 173 @test result.arg == Any[1, 2, 3, 4, 5] 174 175 # Empty array 176 result = nickel_eval("let x = 'Empty [] in x") 177 @test result.arg == Any[] 178 179 # Array of strings 180 result = nickel_eval("let x = 'Names [\"alice\", \"bob\"] in x") 181 @test result.arg == Any["alice", "bob"] 182 183 # Array of records 184 code = """ 185 let x = 'Users [{ name = "alice" }, { name = "bob" }] in x 186 """ 187 result = nickel_eval(code) 188 @test result.arg[1]["name"] == "alice" 189 @test result.arg[2]["name"] == "bob" 190 end 191 192 @testset "Enums - Nested enums" begin 193 # Enum inside record inside enum 194 code = """ 195 let outer = 'Container { inner = 'Value 42 } in outer 196 """ 197 result = nickel_eval(code) 198 @test result.tag == :Container 199 @test result.arg["inner"] isa NickelEnum 200 @test result.arg["inner"].tag == :Value 201 @test result.arg["inner"].arg === Int64(42) 202 203 # Array of enums inside enum 204 code = """ 205 let items = 'List ['Some 1, 'None, 'Some 3] in items 206 """ 207 result = nickel_eval(code) 208 @test result.tag == :List 209 @test length(result.arg) == 3 210 @test result.arg[1].tag == :Some 211 @test result.arg[1].arg === Int64(1) 212 @test result.arg[2].tag == :None 213 @test result.arg[2].arg === nothing 214 @test result.arg[3].tag == :Some 215 @test result.arg[3].arg === Int64(3) 216 217 # Deeply nested enums 218 code = """ 219 let x = 'L1 { a = 'L2 { b = 'L3 42 } } in x 220 """ 221 result = nickel_eval(code) 222 @test result.arg["a"].arg["b"].arg === Int64(42) 223 end 224 225 @testset "Enums - Pattern matching" begin 226 # Match resolves to extracted value 227 code = """ 228 let x = 'Some 42 in 229 x |> match { 230 'Some v => v, 231 'None => 0 232 } 233 """ 234 result = nickel_eval(code) 235 @test result === Int64(42) 236 237 # Match with record destructuring 238 code = """ 239 let result = 'Ok { value = 100 } in 240 result |> match { 241 'Ok r => r.value, 242 'Error _ => -1 243 } 244 """ 245 result = nickel_eval(code) 246 @test result === Int64(100) 247 248 # Match returning enum 249 code = """ 250 let x = 'Some 42 in 251 x |> match { 252 'Some v => 'Doubled (v * 2), 253 'None => 'Zero 0 254 } 255 """ 256 result = nickel_eval(code) 257 @test result.tag == :Doubled 258 @test result.arg === Int64(84) 259 end 260 261 @testset "Enums - Pretty printing" begin 262 # Simple enum 263 @test repr(nickel_eval("let x = 'None in x")) == "'None" 264 @test repr(nickel_eval("let x = 'Foo in x")) == "'Foo" 265 266 # Enum with simple argument 267 @test repr(nickel_eval("let x = 'Some 42 in x")) == "'Some 42" 268 269 # Enum with string argument 270 result = nickel_eval("let x = 'Msg \"hi\" in x") 271 @test startswith(repr(result), "'Msg") 272 end 273 274 @testset "Enums - Real-world patterns" begin 275 # Result type pattern 276 code = """ 277 let divide = fun a b => 278 if b == 0 then 279 'Err "division by zero" 280 else 281 'Ok (a / b) 282 in 283 divide 10 2 284 """ 285 result = nickel_eval(code) 286 @test result == :Ok 287 @test result.arg === Int64(5) 288 289 # Option type pattern 290 code = """ 291 let find = fun arr pred => 292 let matches = std.array.filter pred arr in 293 if std.array.length matches == 0 then 294 'None 295 else 296 'Some (std.array.first matches) 297 in 298 find [1, 2, 3, 4] (fun x => x > 2) 299 """ 300 result = nickel_eval(code) 301 @test result == :Some 302 @test result.arg === Int64(3) 303 304 # State machine pattern 305 code = """ 306 let state = 'Running { progress = 75, task = "downloading" } in state 307 """ 308 result = nickel_eval(code) 309 @test result.tag == :Running 310 @test result.arg["progress"] === Int64(75) 311 @test result.arg["task"] == "downloading" 312 end 313 314 @testset "Deeply nested structures" begin 315 # Deep nesting 316 result = nickel_eval("{ a = { b = { c = { d = 42 } } } }") 317 @test result["a"]["b"]["c"]["d"] === Int64(42) 318 319 # Array of records 320 result = nickel_eval("[{ x = 1 }, { x = 2 }, { x = 3 }]") 321 @test length(result) == 3 322 @test result[1]["x"] === Int64(1) 323 @test result[3]["x"] === Int64(3) 324 325 # Records containing arrays 326 result = nickel_eval("{ items = [1, 2, 3], name = \"test\" }") 327 @test result["items"] == Any[1, 2, 3] 328 @test result["name"] == "test" 329 330 # Mixed deep nesting 331 result = nickel_eval("{ data = [{ a = 1 }, { b = [true, false] }] }") 332 @test result["data"][1]["a"] === Int64(1) 333 @test result["data"][2]["b"] == Any[true, false] 334 end 335 336 @testset "Typed evaluation - primitives" begin 337 @test nickel_eval("42", Int) === 42 338 @test nickel_eval("3.14", Float64) === 3.14 339 @test nickel_eval("\"hello\"", String) == "hello" 340 @test nickel_eval("true", Bool) === true 341 end 342 343 @testset "Typed evaluation - Dict{String, V}" begin 344 result = nickel_eval("{ a = 1, b = 2 }", Dict{String, Int}) 345 @test result isa Dict{String, Int} 346 @test result["a"] === 1 347 @test result["b"] === 2 348 end 349 350 @testset "Typed evaluation - Dict{Symbol, V}" begin 351 result = nickel_eval("{ x = 1.5, y = 2.5 }", Dict{Symbol, Float64}) 352 @test result isa Dict{Symbol, Float64} 353 @test result[:x] === 1.5 354 @test result[:y] === 2.5 355 end 356 357 @testset "Typed evaluation - Vector{T}" begin 358 result = nickel_eval("[1, 2, 3]", Vector{Int}) 359 @test result isa Vector{Int} 360 @test result == [1, 2, 3] 361 362 result = nickel_eval("[\"a\", \"b\", \"c\"]", Vector{String}) 363 @test result isa Vector{String} 364 @test result == ["a", "b", "c"] 365 end 366 367 @testset "Typed evaluation - NamedTuple" begin 368 result = nickel_eval("{ host = \"localhost\", port = 8080 }", 369 @NamedTuple{host::String, port::Int}) 370 @test result isa NamedTuple{(:host, :port), Tuple{String, Int}} 371 @test result.host == "localhost" 372 @test result.port === 8080 373 end 374 375 @testset "String macro" begin 376 @test ncl"42" === Int64(42) 377 @test ncl"1 + 1" == 2 378 @test ncl"true" === true 379 @test ncl"{ x = 10 }"["x"] === Int64(10) 380 end 381 382 @testset "check_ffi_available" begin 383 @test check_ffi_available() === true 384 end 385 386 @testset "Error handling" begin 387 # Undefined variable 388 @test_throws NickelError nickel_eval("undefined_variable") 389 # Syntax error 390 @test_throws NickelError nickel_eval("{ x = }") 391 end 392 end 393 394 @testset "File Evaluation" begin 395 mktempdir() do dir 396 # Simple file 397 f = joinpath(dir, "test.ncl") 398 write(f, "{ x = 42 }") 399 result = nickel_eval_file(f) 400 @test result["x"] === Int64(42) 401 402 # File returning a primitive 403 f2 = joinpath(dir, "prim.ncl") 404 write(f2, "1 + 2") 405 @test nickel_eval_file(f2) === Int64(3) 406 407 # File with import 408 shared = joinpath(dir, "shared.ncl") 409 write(shared, """ 410 { 411 project_name = "TestProject", 412 version = "1.0.0" 413 } 414 """) 415 main = joinpath(dir, "main.ncl") 416 write(main, """ 417 let shared = import "shared.ncl" in 418 { 419 name = shared.project_name, 420 version = shared.version, 421 extra = "main-specific" 422 } 423 """) 424 result = nickel_eval_file(main) 425 @test result isa Dict{String, Any} 426 @test result["name"] == "TestProject" 427 @test result["version"] == "1.0.0" 428 @test result["extra"] == "main-specific" 429 430 # Nested imports 431 utils_file = joinpath(dir, "utils.ncl") 432 write(utils_file, """ 433 { 434 helper = fun x => x * 2 435 } 436 """) 437 438 complex_file = joinpath(dir, "complex.ncl") 439 write(complex_file, """ 440 let shared = import "shared.ncl" in 441 let utils = import "utils.ncl" in 442 { 443 project = shared.project_name, 444 doubled_value = utils.helper 21 445 } 446 """) 447 result = nickel_eval_file(complex_file) 448 @test result["project"] == "TestProject" 449 @test result["doubled_value"] === Int64(42) 450 451 # File evaluation with enums 452 enum_file = joinpath(dir, "enum_config.ncl") 453 write(enum_file, """ 454 { 455 status = 'Active, 456 result = 'Ok 42 457 } 458 """) 459 460 result = nickel_eval_file(enum_file) 461 @test result["status"] isa NickelEnum 462 @test result["status"] == :Active 463 @test result["result"].tag == :Ok 464 @test result["result"].arg === Int64(42) 465 466 # Subdirectory imports 467 subdir = joinpath(dir, "lib") 468 mkdir(subdir) 469 lib_file = joinpath(subdir, "library.ncl") 470 write(lib_file, """ 471 { 472 lib_version = "2.0" 473 } 474 """) 475 476 with_subdir_file = joinpath(dir, "use_lib.ncl") 477 write(with_subdir_file, """ 478 let lib = import "lib/library.ncl" in 479 { 480 using = lib.lib_version 481 } 482 """) 483 result = nickel_eval_file(with_subdir_file) 484 @test result["using"] == "2.0" 485 end 486 487 # Non-existent file 488 @test_throws NickelError nickel_eval_file("/nonexistent/path/file.ncl") 489 490 # Import not found 491 mktempdir() do dir 492 bad_import = joinpath(dir, "bad_import.ncl") 493 write(bad_import, """ 494 let missing = import "not_there.ncl" in 495 missing 496 """) 497 @test_throws NickelError nickel_eval_file(bad_import) 498 end 499 end 500 501 @testset "Export formats" begin 502 json = nickel_to_json("{ a = 1 }") 503 @test occursin("\"a\"", json) 504 @test occursin("1", json) 505 506 yaml = nickel_to_yaml("{ a = 1 }") 507 @test occursin("a:", yaml) 508 509 toml = nickel_to_toml("{ a = 1 }") 510 @test occursin("a = 1", toml) 511 512 # Export more complex structures 513 json2 = nickel_to_json("{ name = \"test\", values = [1, 2, 3] }") 514 @test occursin("\"name\"", json2) 515 @test occursin("\"test\"", json2) 516 517 # Export error: expression that can't be evaluated 518 @test_throws NickelError nickel_to_json("undefined_variable") 519 end