From f5eb75372e08d5382464af11bb26a3cbacd7f448 Mon Sep 17 00:00:00 2001 From: refaktor Date: Wed, 19 Aug 2026 17:27:22 +0200 Subject: [PATCH] making builtins tests work and adding test sections, so more function reference is now available --- batteries/builtins_encoding.go | 72 +-- batteries/builtins_sqlite.go | 2 +- batteries/builtins_structures.go | 1 - batteries/register.go | 2 +- evaldo/builtins_base_mth.go | 3 + evaldo/builtins_error_handling.go | 20 +- tests/base.info.rye | 789 +++++++++++++++++++++++++++++- tests/formats.info.rye | 328 +++++++++++++ tests/io.info.rye | 2 +- tests/regen | 8 +- 10 files changed, 1173 insertions(+), 54 deletions(-) diff --git a/batteries/builtins_encoding.go b/batteries/builtins_encoding.go index 5a25edf1..1724cfee 100644 --- a/batteries/builtins_encoding.go +++ b/batteries/builtins_encoding.go @@ -19,9 +19,9 @@ var Builtins_encoding = map[string]*env.Builtin{ // // Tests: - // equal { "48656c6c6f20576f726c64" |hex\\decode-string } "Hello World" - // equal { "48656c6c6f20576f726c64" |hex\\decode-string |type? } 'string - // equal { "invalid" |hex\\decode-string |disarm |type? } 'error + // equal { "48656c6c6f20576f726c64" |decode\hex-string } "Hello World" + // equal { "48656c6c6f20576f726c64" |decode\hex-string |type? } 'string + // equal { "invalid" |decode\hex-string |disarm |type? } 'error // Args: // * hex-string: hexadecimal string to decode // Returns: @@ -36,20 +36,20 @@ var Builtins_encoding = map[string]*env.Builtin{ decoded, err := hex.DecodeString(hexStr.Value) if err != nil { ps.FailureFlag = true - return evaldo.MakeBuiltinError(ps, "Failed to decode hex string: "+err.Error(), "hex\\decode-string") + return evaldo.MakeBuiltinError(ps, "Failed to decode hex string: "+err.Error(), "decode\\hex-string") } return *env.NewString(string(decoded)) default: ps.FailureFlag = true - return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "hex\\decode-string") + return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "decode\\hex-string") } }, }, // Tests: - // equal { "Hello World" |hex\\encode-string } "48656c6c6f20576f726c64" - // equal { "Hello World" |hex\\encode-string |type? } 'string - // equal { "" |hex\\encode-string } "" + // equal { "Hello World" |encode\hex-string } "48656c6c6f20576f726c64" + // equal { "Hello World" |encode\hex-string |type? } 'string + // equal { "" |encode\hex-string } "" // Args: // * string: string to encode // Returns: @@ -65,15 +65,15 @@ var Builtins_encoding = map[string]*env.Builtin{ return *env.NewString(encoded) default: ps.FailureFlag = true - return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "hex\\encode-string") + return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "encode\\hex-string") } }, }, // Tests: - // equal { " 70 44 01 4b \n 55 50 4e 51 " |hex\\clean-string } "7044014b55504e51" - // equal { "70 44 01 4b\t55 50 4e 51\r\n52 0a" |hex\\clean-string } "7044014b55504e51520a" - // equal { "7044014b55504e51520a" |hex\\clean-string } "7044014b55504e51520a" + // equal { " 70 44 01 4b \n 55 50 4e 51 " |clean\hex-string } "7044014b55504e51" + // equal { "70 44 01 4b\t55 50 4e 51\r\n52 0a" |clean\hex-string } "7044014b55504e51520a" + // equal { "7044014b55504e51520a" |clean\hex-string } "7044014b55504e51520a" // Args: // * hex-string: raw hex string with possible whitespace // Returns: @@ -100,14 +100,14 @@ var Builtins_encoding = map[string]*env.Builtin{ return *env.NewString(result) default: ps.FailureFlag = true - return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "hex\\clean-string") + return evaldo.MakeArgError(ps, 1, []env.Type{env.StringType}, "clean\\hex-string") } }, }, // Tests: - // equal { charmap\\windows-1250 |type? } 'native - // equal { charmap\\windows-1250 |kind? } 'charmap-encoding + // equal { charmap\windows-1250 |type? } 'native + // equal { charmap\windows-1250 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -122,8 +122,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\iso-8859-1 |type? } 'native - // equal { charmap\\iso-8859-1 |kind? } 'charmap-encoding + // equal { charmap\iso-8859-1 |type? } 'native + // equal { charmap\iso-8859-1 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -138,8 +138,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\iso-8859-2 |type? } 'native - // equal { charmap\\iso-8859-2 |kind? } 'charmap-encoding + // equal { charmap\iso-8859-2 |type? } 'native + // equal { charmap\iso-8859-2 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -154,8 +154,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\windows-1252 |type? } 'native - // equal { charmap\\windows-1252 |kind? } 'charmap-encoding + // equal { charmap\windows-1252 |type? } 'native + // equal { charmap\windows-1252 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -170,8 +170,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\code-page-437 |type? } 'native - // equal { charmap\\code-page-437 |kind? } 'charmap-encoding + // equal { charmap\code-page-437 |type? } 'native + // equal { charmap\code-page-437 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -186,8 +186,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\code-page-850 |type? } 'native - // equal { charmap\\code-page-850 |kind? } 'charmap-encoding + // equal { charmap\code-page-850 |type? } 'native + // equal { charmap\code-page-850 |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -202,8 +202,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\koi8r |type? } 'native - // equal { charmap\\koi8r |kind? } 'charmap-encoding + // equal { charmap\koi8r |type? } 'native + // equal { charmap\koi8r |kind? } 'charmap-encoding // Args: // * none // Returns: @@ -218,8 +218,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\windows-1250 |charmap-encoding\\new-decoder |type? } 'native - // equal { charmap\\windows-1250 |charmap-encoding\\new-decoder |kind? } 'text-decoder + // equal { charmap\windows-1250 |Decoder |type? } 'native + // equal { charmap\windows-1250 |Decoder |kind? } 'text-decoder // Args: // * encoding: charmap encoding as a native value // Returns: @@ -246,8 +246,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\windows-1250 |charmap-encoding\\new-encoder |type? } 'native - // equal { charmap\\windows-1250 |charmap-encoding\\new-encoder |kind? } 'text-encoder + // equal { charmap\windows-1250 |Encoder |type? } 'native + // equal { charmap\windows-1250 |Encoder |kind? } 'text-encoder // Args: // * encoding: charmap encoding as a native value // Returns: @@ -274,9 +274,9 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\windows-1250 |charmap-encoding\\new-decoder "Plačilo računa" |text-decoder\\string |contains? "č" } 1 - // equal { charmap\\windows-1250 |charmap-encoding\\new-decoder "Hello" |text-decoder\\string } "Hello" - // equal { charmap\\windows-1250 |charmap-encoding\\new-decoder "" |text-decoder\\string } "" + // equal { charmap\windows-1250 |Decoder |Decode "Plačilo računa" |contains? "č" } 1 + // equal { charmap\windows-1250 |Decoder |Decode "Hello" } "Hello" + // equal { charmap\windows-1250 |Decoder |Decode "" } "" // Args: // * decoder: text decoder as a native value // * input: string to decode @@ -314,8 +314,8 @@ var Builtins_encoding = map[string]*env.Builtin{ }, // Tests: - // equal { charmap\\windows-1250 |charmap-encoding\\new-encoder "Hello" |text-encoder\\string } "Hello" - // equal { charmap\\windows-1250 |charmap-encoding\\new-encoder "Plačilo" |text-encoder\\string |hex\\encode-string } "506c61e8696c6f" + // equal { charmap\windows-1250 |Encoder |Encode "Hello" } "Hello" + // equal { charmap\windows-1250 |Encoder |Encode "Plačilo" |encode\hex-string } "506c61e8696c6f" // Args: // * encoder: text encoder as a native value // * input: string to encode diff --git a/batteries/builtins_sqlite.go b/batteries/builtins_sqlite.go index edc04def..50f20b53 100644 --- a/batteries/builtins_sqlite.go +++ b/batteries/builtins_sqlite.go @@ -124,7 +124,7 @@ var Builtins_sqlite = map[string]*env.Builtin{ // Tests: // equal { Open sqlite://test.db |Query "SELECT * FROM test" |type? } 'table - // Also supports: db .Query { "SELECT * FROM test WHERE id = ?" 123 } + // ; Also supports: db .Query { "SELECT * FROM test WHERE id = ?" 123 } // Args: // * db: SQLite database connection // * sql: SQL query as string, SQL-DSL block, or a block where first value is SQL string and the rest are parameters diff --git a/batteries/builtins_structures.go b/batteries/builtins_structures.go index 39a90590..ab6912a6 100644 --- a/batteries/builtins_structures.go +++ b/batteries/builtins_structures.go @@ -665,7 +665,6 @@ var Builtins_structures = map[string]*env.Builtin{ // // ##### Structures ##### "Functions for converting between Rye and Go data structures" // - // Example: dict [ "name" "John" "age" 30 ] |dict->struct my-struct-ptr // Args: // * dict: Rye Dict containing field names and values diff --git a/batteries/register.go b/batteries/register.go index 7b065774..626f1148 100644 --- a/batteries/register.go +++ b/batteries/register.go @@ -66,7 +66,7 @@ func RegisterBatteries(ps *env.ProgramState) { evaldo.RegisterBuiltins2(Builtins_bcrypt, ps, "bcrypt") evaldo.RegisterBuiltins2(Builtins_console, ps, "console") evaldo.RegisterBuiltinsInContext(Builtins_crypto, ps, "crypto") - evaldo.RegisterBuiltinsInContext(Builtins_encoding, ps, "encoding") + evaldo.RegisterBuiltins2(Builtins_encoding, ps, "encoding") evaldo.RegisterBuiltinsInContext(Builtins_math, ps, "math") evaldo.RegisterBuiltinsInContext(Builtins_os, ps, "os") evaldo.RegisterBuiltinsInContext(Builtins_pipes, ps, "pipes") diff --git a/evaldo/builtins_base_mth.go b/evaldo/builtins_base_mth.go index d1182034..e624baf6 100644 --- a/evaldo/builtins_base_mth.go +++ b/evaldo/builtins_base_mth.go @@ -398,6 +398,9 @@ func Mth_EvalDirect(ps *env.ProgramState) env.Object { // --------------------------------------------------------------------------- var builtins_mth = map[string]*env.Builtin{ + // + // ##### Fast Math ##### "Fast infix math evaluator" + // // Tests: // equal { mth { 1 + 2 } } 3 // equal { mth { 2 + 3 * 4 } } 14 diff --git a/evaldo/builtins_error_handling.go b/evaldo/builtins_error_handling.go index 7b4436f6..b795470c 100644 --- a/evaldo/builtins_error_handling.go +++ b/evaldo/builtins_error_handling.go @@ -12,7 +12,7 @@ import ( // Error Creation Functions var ErrorCreationBuiltins = map[string]*env.Builtin{ // - // ##### Failure ###### "Handling failures" + // ##### Failure ##### "Handling failures" // // Tests: // equal { try { fail "error message" } |type? } 'error @@ -794,8 +794,8 @@ var ErrorHandlingBuiltins = map[string]*env.Builtin{ }, // Tests: - // equal { fix\\either failure "error" { "fixed" } { "not fixed" } } "fixed" - // equal { fix\\either 5 { "fixed" } { "not fixed" } } "not fixed" + // equal { fix\either failure "error" { "fixed" } { "not fixed" } } "fixed" + // equal { fix\either 5 { "fixed" } { "not fixed" } } "not fixed" // Args: // * value: Value to check for failure state // * error_handler: Block to execute if value is in failure state @@ -840,8 +840,8 @@ var ErrorHandlingBuiltins = map[string]*env.Builtin{ }, // Tests: - // equal { 5 |fix\\else { "not fixed" } } "not fixed" - // equal { try { fail "error" |fix\\else { "not fixed" } } |message? } "error" + // equal { 5 |fix\else { "not fixed" } } "not fixed" + // equal { try { fail "error" |fix\else { "not fixed" } } |message? } "error" // Args: // * value: Value to check for failure state // * success_handler: Block to execute if value is not in failure state @@ -874,8 +874,8 @@ var ErrorHandlingBuiltins = map[string]*env.Builtin{ }, // Tests: - // equal { 5 |fix\\continue { "error handler" } { "success handler" } } "success handler" - // equal { try { fail "error" |fix\\continue { "error handler" } { "success handler" } } } "error handler" + // equal { 5 |fix\continue { "error handler" } { "success handler" } } "success handler" + // equal { try { fail "error" |fix\continue { "error handler" } { "success handler" } } } "error handler" // Args: // * value: Value to check for failure state // * error_handler: Block to execute if value is in failure state @@ -953,9 +953,9 @@ var ErrorHandlingBuiltins = map[string]*env.Builtin{ }, // Tests: - // equal { ^fix\\match failure 404 { 404 { "Not Found" } 500 { "Server Error" } } } "Not Found" - // equal { ^fix\\match failure 500 { 404 { "Not Found" } 500 { "Server Error" } } } "Server Error" - // equal { ^fix\\match failure 403 { 404 { "Not Found" } 500 { "Server Error" } _ { "Unknown Error" } } } "Unknown Error" + // equal { ^fix\match failure 404 { 404 { "Not Found" } 500 { "Server Error" } } } "Not Found" + // equal { ^fix\match failure 500 { 404 { "Not Found" } 500 { "Server Error" } } } "Server Error" + // equal { ^fix\match failure 403 { 404 { "Not Found" } 500 { "Server Error" } _ { "Unknown Error" } } } "Unknown Error" // Args: // * error: Error object to match against // * cases: Block containing error codes and corresponding handler blocks diff --git a/tests/base.info.rye b/tests/base.info.rye index a235fd18..8aa4883e 100644 --- a/tests/base.info.rye +++ b/tests/base.info.rye @@ -7576,6 +7576,793 @@ section "Default" "" { section "Default" "" { } -section "Default" "" { +section "Failure " "Handling failures" { + group "fail" + "Creates an error and sets the failure flag, but continues execution (unlike ^fail)." + { + argsn 1 + arg `error_info: String message, Integer code, or block for multiple parameters` + returns `error object and sets the failure flag` + } + + { + equal { try { fail "error message" } |type? } 'error + equal { try { fail "error message" } |message? } "error message" + equal { try { fail 404 } |status? } 404 + } + + { + } + + group "^fail" + "Creates an error and immediately returns from the current function with failure state." + { + argsn 1 + arg `error_info: String message, Integer code, or block for multiple parameters` + returns `error object and sets both failure and return flags` + } + + { + equal { ff:: fn { } { ^fail "error message" } ff |disarm |type? } 'error + equal { ff:: fn { } { ^fail "error message" } ff |disarm |message? } "error message" + equal { ff:: fn { } { ^fail 404 } ff |disarm |status? } 404 + equal { ff:: fn { } { ^fail 'user-error } ff |disarm |kind? } 'user-error + } + + { + } + + group "refail" + "Re-raises an existing error with additional context. TODO -- duplicate of check" + { + argsn 2 + argtypes { + 1 [ Error ] + } + } + + { + } + + { + } + + group "failure" + "Creates an error object without setting any flags (unlike fail and ^fail)." + { + argsn 1 + arg `error_info: String message, Integer code, or block for multiple parameters` + returns `error object without setting any flags` + } + + { + equal { failure "error message" |type? } 'error + equal { failure "error message" |message? } "error message" + equal { failure 404 |status? } 404 + } + + { + } + + group "empty" + "Creates an 404 error object without setting any flags." + { + arg `error_info: String message, Integer code, or block for multiple parameters` + returns `error object without setting any flags` + } + + { + equal { failure "error message" |type? } 'error + equal { failure "error message" |message? } "error message" + equal { failure 404 |status? } 404 + } + + { + } + + group "failure\\wrap" + "Creates a new error that wraps an existing error, allowing for error chaining." + { + argsn 2 + argtypes { + 2 [ Error ] + } + arg `error_info: String message, Integer code, or block for multiple parameters` + arg `error: Error object to wrap` + returns `new error object that wraps the provided error` + } + + { + equal { failure\wrap "outer error" failure "inner error" |message? } "outer error" + equal { failure\wrap "outer error" failure "inner error" |type? } 'error + } + + { + } + + group "is-error" + "Returns true if the value is an error, false otherwise." + { + argsn 1 + arg `value: Any value to check` + returns `boolean true if the value is an error, false otherwise` + } + + { + equal { is-error failure "test" } true + equal { is-error 123 } false + } + + { + } + + group "error-kind?" + "Returns the kind of an error, or void if not an error." + { + argsn 1 + arg `value: Any value to check` + returns `the kind of the error as a word, or void if not an error` + } + + { + equal { error-kind? failure { 'syntax-error 404 "Syntax error" } } 'syntax-error + equal { error-kind? 123 } _ + } + + { + } + + group "is-error-of-kind" + "Returns true if the value is an error of the specified kind, false otherwise." + { + argsn 2 + argtypes { + 2 [ Word Tagword ] + } + arg `error: Error object to check` + arg `kind: Word or tagword representing the error kind to check against` + returns `boolean true if the error is of the specified kind, false otherwise` + } + + { + equal { is-error-of-kind failure { 'syntax-error 404 "Syntax error" } 'syntax-error } true + equal { is-error-of-kind failure { 'syntax-error 404 "Syntax error" } 'runtime-error } false + } + + { + } + + group "cause?" + "Extracts the root cause from an error chain by traversing the Parent references." + { + argsn 1 + argtypes { + 1 [ Error ] + } + arg `error: Error object to extract the root cause from` + returns `the root cause error from an error chain` + } + + { + equal { cause? failure\wrap "outer error" failure "inner error" |message? } "inner error" + equal { cause? failure "single error" |message? } "single error" + } + + { + } + + group "status?" + "Extracts the numeric status code from an error object." + { + argsn 1 + argtypes { + 1 [ Error ] + } + arg `error: Error object to extract status code from` + returns `integer status code of the error` + } + + { + equal { failure 404 |status? } 404 + equal { failure "message" |status? } 0 + error { "not an error" |status? } + } + + { + } + + group "message?" + "Extracts the message string from an error object." + { + argsn 1 + argtypes { + 1 [ Error ] + } + arg `error: Error object to extract message from` + returns `string message of the error` + } + + { + equal { failure "error message" |message? } "error message" + equal { failure 404 |message? } "" + error { "not an error" |message? } + } + + { + } + + group "details?" + "Extracts additional details from an error object as a dictionary." + { + argsn 1 + argtypes { + 1 [ Error ] + } + arg `error: Error object to extract additional details from` + returns `dictionary containing any additional values stored in the error` + } + + { + ; equal { failure { "code" 404 "info" "Not Found" } |details? |type? } 'dict + ; equal { failure { "code" 404 "info" "Not Found" } |details? .code } 404 + error { "not an error" |details? } + } + + { + } + + group "has-failed" + "Tests if a value is an error object, returning true for errors and false for non-errors." + { + argsn 1 + arg `value: Any value to check` + returns `boolean true if the value is an error, false otherwise` + } + + { + equal { has-failed failure "error" } true + equal { has-failed "not an error" } false + equal { has-failed 123 } false + equal { has-failed try { fail "error" } } true + } + + { + } + + group "is-failure" + "Checks if a value is a failure/error type. Unlike has-failed, this doesn't accept failures - the value must already be disarmed." + { + argsn 1 + arg `value: Any value to check (must already be disarmed if it was a failure)` + returns `boolean true if the value is an error type, false otherwise` + } + + { + equal { try { fail "error" } |disarm |is-failure } true + equal { failure "error" |is-failure } true + equal { 123 |is-failure } false + equal { "hello" |is-failure } false + } + + { + } + + group "is-success" + "Returns true for any value that is not a failure/error type. The opposite of is-failure." + { + argsn 1 + arg `value: Any value to check (must already be disarmed if it was a failure)` + returns `boolean true if the value is not an error type, false if it is an error` + } + + { + equal { 123 |is-success } true + equal { "hello" |is-success } true + equal { { 1 2 3 } |is-success } true + equal { failure "error" |is-success } false + equal { try { fail "error" } |disarm |is-success } false + } + + { + } + + group "disarm" + "Clears the failure flag while preserving the error object, allowing error inspection without propagation." + { + argsn 1 + arg `error: Error object to disarm` + returns `the original error object, but clears the failure flag` + } + + { + equal { try { fail "error" |disarm } |type? } 'error + equal { try { fail "error" |disarm |message? } } "error" + equal { try { fail "error" } |has-failed } true + equal { try { fail "error" |disarm } |has-failed } true + } + + { + } + + group "check" + "Checks if a value is in failure state and wraps it with a new error if so, otherwise returns the original value." + { + argsn 2 + arg `value: Value to check for failure state` + arg `error_info: Error information to use if value is in failure state` + returns `original value if not in failure state, or a new error wrapping the original error` + } + + { + equal { 5 |check "Value must be positive" } 5 + equal { try { fail "Original error" |check "Wrapped error" } |message? } "Wrapped error" + equal { try { fail "Original error" |check "Wrapped error" } |details? |type? } 'dict + } + + { + } + + group "^check" + "Like 'check' but also sets the return flag to immediately exit the current function." + { + argsn 2 + arg `value: Value to check for failure state` + arg `error_info: Error information to use if value is in failure state` + returns `original value if not in failure state, or immediately returns from function with a new error` + } + + { + equal { fn { x } { x |^check "Error in function" } |apply [ 5 ] } 5 + equal { ff:: fn { x } { fail "Original" |^check "Wrapped" } ff 5 |disarm |message? } "Wrapped" + } + + { + } + + group "^ensure" + "Checks if a value is truthy and returns it if so, otherwise creates an error and immediately returns from the function." + { + argsn 2 + arg `condition: Value to test for truthiness` + arg `error_info: Error information to use if condition is not truthy` + returns `condition value if truthy, or immediately returns from function with an error` + } + + { + equal { fn { x } { x |^ensure "Must be positive" } |apply [ 5 ] } true + equal { ff:: fn { x } { x > 0 |^ensure "Must be positive" } ff -1 |disarm |message? } "Must be positive" + } + + { + } + + group "ensure" + "Checks if a value is truthy and returns it if so, otherwise creates an error with the failure flag set." + { + argsn 2 + arg `condition: Value to test for truthiness` + arg `error_info: Error information to use if condition is not truthy` + returns `condition value if truthy, or creates an error with failure flag set` + } + + { + equal { 5 > 0 |ensure "Must be positive" } true + equal { try { -1 > 0 |ensure "Must be positive" } |message? } "Must be positive" + } + + { + } + + group "requires-one-of" + "Validates that a value matches one of the provided options, failing with a descriptive error if not." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check against valid options` + arg `options: Block containing valid values` + returns `original value if it matches one of the options, or creates an error with failure flag set` + } + + { + equal { "a" |requires-one-of { "a" "b" "c" } } "a" + equal { "b" |requires-one-of { "a" "b" "c" } } "b" + equal { try { "x" |requires-one-of { "a" "b" "c" } } |message? |contains "must be one of" } true + equal { try { "x" |requires-one-of { "a" "b" "c" } } |message? |contains "[String: x]" } true + equal { try { 5 |requires-one-of { 1 2 3 } } |message? |contains "5" } true + } + + { + } + + group "^requires-one-of" + "Validates that a value matches one of the provided options, failing with a descriptive error if not." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check against valid options` + arg `options: Block containing valid values` + returns `original value if it matches one of the options, or creates an error with failure flag set` + } + + { + equal { "a" |requires-one-of { "a" "b" "c" } } "a" + equal { "b" |requires-one-of { "a" "b" "c" } } "b" + equal { try { "x" |requires-one-of { "a" "b" "c" } } |message? |contains "must be one of" } true + equal { try { "x" |requires-one-of { "a" "b" "c" } } |message? |contains "[String: x]" } true + equal { try { 5 |requires-one-of { 1 2 3 } } |message? |contains "5" } true + } + + { + } + + group "ensure\\with" + "Injects a value into a validation block and returns the value if the block is truthy, otherwise creates an error." + { + argsn 3 + argtypes { + 2 [ Block ] + } + arg `value: Value to inject into the validation block` + arg `block: Block that receives the value and returns truthy/falsy result` + arg `error_info: Error information to use if block returns falsy` + returns `original value if block returns truthy, or creates an error with failure flag set` + } + + { + equal { dict { "status" 200 } |ensure\with { -> "status" = 200 } "status check failed" } dict { "status" 200 } + equal { try { dict { "status" 404 } |ensure\with { -> "status" = 200 } "status check failed" } |message? } "status check failed" + } + + { + } + + group "^ensure\\with" + "Like ensure\\with but also sets the return flag to immediately exit the current function." + { + argsn 3 + argtypes { + 2 [ Block ] + } + arg `value: Value to inject into the validation block` + arg `block: Block that receives the value and returns truthy/falsy result` + arg `error_info: Error information to use if block returns falsy` + returns `original value if block returns truthy, or immediately returns from function with an error` + } + + { + ; equal { fn { req } { req |^ensure\with { .Status? = 200 } "request failed" } |apply [ Request "GET" "http://example.com" ] } Request "GET" "http://example.com" + equal { ff:: fn { val } { val |^ensure\with { -> "status" = 200 } "status check failed" } ff dict { "status" 404 } |disarm |message? } "status check failed" + } + + { + } + + group "fix" + "Handles errors by executing a block if the value is in failure state, clearing the failure flag." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check for failure state` + arg `handler: Block to execute if value is in failure state` + returns `original value if not in failure state, or result of executing the handler block` + } + + { + equal { 5 |fix { + 10 } } 5 + equal { try { fail "error" |fix { "fixed" } } } "fixed" + equal { try { fail "error" |fix { fail "new error" } } |message? } "new error" + } + + { + } + + group "^fix" + "Like 'fix' but also sets the return flag to immediately exit the current function with the handler result." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check for failure state` + arg `handler: Block to execute if value is in failure state` + returns `original value if not in failure state, or immediately returns from function with handler result` + } + + { + equal { fn { x } { x |^fix { "fixed" } } |apply [ 5 ] } 5 + equal { ff:: fn { x } { fail "error" |^fix { "fixed" } } ff 5 } "fixed" + } + + { + } + + group "fix\\either" + "Executes one of two blocks depending on whether the value is in failure state." + { + argsn 3 + argtypes { + 2 [ Block ] + 3 [ Block ] + } + arg `value: Value to check for failure state` + arg `error_handler: Block to execute if value is in failure state` + arg `success_handler: Block to execute if value is not in failure state` + returns `result of executing the appropriate handler block` + } + + { + equal { fix\either failure "error" { "fixed" } { "not fixed" } } "fixed" + equal { fix\either 5 { "fixed" } { "not fixed" } } "not fixed" + } + + { + } + + group "fix\\else" + "Executes a block if the value is not in failure state, otherwise returns the original value." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check for failure state` + arg `success_handler: Block to execute if value is not in failure state` + returns `result of executing the success handler if value is not in failure state, or the original value` + } + + { + equal { 5 |fix\else { "not fixed" } } "not fixed" + equal { try { fail "error" |fix\else { "not fixed" } } |message? } "error" + } + + { + } + + group "fix\\continue" + "Executes one of two blocks depending on whether the value is in failure state, like an error-handling if/else." + { + argsn 3 + argtypes { + 2 [ Block ] + 3 [ Block ] + } + arg `value: Value to check for failure state` + arg `error_handler: Block to execute if value is in failure state` + arg `success_handler: Block to execute if value is not in failure state` + returns `result of executing the appropriate handler block` + } + + { + equal { 5 |fix\continue { "error handler" } { "success handler" } } "success handler" + equal { try { fail "error" |fix\continue { "error handler" } { "success handler" } } } "error handler" + } + + { + } + + group "continue" + "Executes a block only if the value is not in failure state, opposite of 'fix'." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `value: Value to check for failure state` + arg `block: Block to execute if value is not in failure state` + returns `result of executing the block if value is not in failure state, or the original value` + } + + { + equal { 5 |continue { + 10 } } 15 + equal { try { fail "error" |continue { + 10 } } } failure "error" + } + + { + } + + group "^fix\\match" + "Error handling switch that matches error codes with handler blocks and sets the return flag." + { + argsn 2 + argtypes { + 2 [ Block ] + } + arg `error: Error object to match against` + arg `cases: Block containing error codes and corresponding handler blocks` + returns `result of executing the matching handler block, or the original error if no match` + } + + { + equal { ^fix\match failure 404 { 404 { "Not Found" } 500 { "Server Error" } } } "Not Found" + equal { ^fix\match failure 500 { 404 { "Not Found" } 500 { "Server Error" } } } "Server Error" + equal { ^fix\match failure 403 { 404 { "Not Found" } 500 { "Server Error" } _ { "Unknown Error" } } } "Unknown Error" + } + + { + } + + group "try" + "Takes a block of code and does (runs) it." + { + argsn 1 + pure + argtypes { + 1 [ Block ] + } + } + + { + equal { try { 123 + 123 } } 246 + equal { try { 123 + "asd" } |type? } 'error + equal { try { 123 + } |type? } 'error + equal { try { fail "oops" } |message? } "oops" + } + + { + } + + group "try-all" + "Executes a block and returns a result tuple [success, result], where success is true if no error occurred." + { + argsn 1 + pure + argtypes { + 1 [ Block ] + } + arg `block: Block of code to execute` + returns `a block containing [success, result], where success is true if no error occurred` + } + + { + equal { try-all { 1 + 2 } } [ true 3 ] + equal { try-all { fail "custom error" } |first } false + } + + { + } + + group "try\\in" + "Takes a Context and a Block. It Does a block inside a given Context." + { + argsn 2 + argtypes { + 1 [ Context ] + 2 [ Block ] + } + } + + { + equal { c: context { x: 100 } try\in c { x * 9.99 } } 999.0 + equal { c: context { x:: 100 } try\in c { inc! 'x } } 101 + equal { c: context { x:: 100 } try\in c { x:: 200 , x } } 200 + equal { c: context { x:: 100 } try\in c { x:: 200 } c/x } 200 + equal { c: context { x: 100 } try\in c { inc! 'y } |type? } 'error + } + + { + } + + group "finally" + "Executes a block and ensures another block is executed afterward, regardless of errors." + { + argsn 2 + argtypes { + 1 [ Block ] + 2 [ Block ] + } + arg `main-block: Block of code to execute` + arg `finally-block: Block to execute afterward, regardless of errors` + returns `result of the main block, preserving any failure state` + } + + { + equal { finally { 1 + 2 } { print "cleanup" } } 3 + equal { try { finally { fail "error" } { print "cleanup" } } |message? } "error" + } + + { + } + + group "retry" + "Executes a block and retries it up to N times if it results in a failure." + { + argsn 2 + argtypes { + 1 [ Integer ] + 2 [ Block ] + } + arg `retries: Integer number of retries to attempt` + arg `block: Block of code to execute and potentially retry` + returns `result of the block if successful, or the last failure if all retries fail` + } + + { + ; equal { retry 3 { fail 101 } |disarm |type? } 'error + ; equal { retry 3 { fail 101 } |disarm |status? } 101 + equal { retry 3 { 10 + 1 } } 11 + } + + { + } + + group "persist" + "Executes a block repeatedly until it succeeds (no failure), then returns the successful result. Gives up after 1000 attempts." + { + argsn 1 + argtypes { + 1 [ Block ] + } + arg `block: Block of code to execute repeatedly until it succeeds` + returns `result of the block when it finally succeeds (no failure), or error if 1000 attempts exceeded` + } + + { + equal { persist { 10 + 1 } } 11 + ; equal { counter:: 0 persist { counter:: counter + 1 , either counter > 3 { counter } { fail "not ready" } } } 4 + error { persist { fail "always fails" } } + } + + { + } + + group "timeout" + "Executes a block of code with a timeout, failing if execution exceeds the specified duration in milliseconds." + { + argsn 2 + argtypes { + 1 [ Integer ] + 2 [ Block ] + } + arg `ms: Integer timeout duration in milliseconds` + arg `block: Block of code to execute with a timeout` + returns `result of the block if it completes within the timeout, or a timeout error` + } + + { + equal { timeout 5000 { "ok" } } "ok" + equal { try { timeout 100 { sleep 1000 , "ok" } } |message? |contains "timed out" } true + } + + { + } + +} + +section "Fast Math " "Fast infix math evaluator" { + group "mth" + "Fast infix math evaluator with proper operator precedence (+,-,*,/,//,%,<,>,=,<=,>=,!=). Single-pass Shunting-yard, direct Go arithmetic, no builtin lookup." + { + argsn 1 + pure + argtypes { + 1 [ Block ] + } + arg `block: Block containing an infix math expression` + returns `result of evaluating the expression with proper operator precedence` + } + + { + equal { mth { 1 + 2 } } 3 + equal { mth { 2 + 3 * 4 } } 14 + equal { mth { 10 - 5 - 2 } } 3 + equal { mth { 8 // 3 } } 2 + equal { mth { 8 % 3 } } 2 + equal { mth { 5 + 5 < 10 } } false + equal { mth { 5 + 5 <= 10 } } true + equal { mth { 5 + 4 < 10 } } true + equal { mth { 1 + 2 * 3 } } 7 + equal { a: 5 mth { a * 2 } } 10 + equal { a: 3 b: 4 mth { a * a + b * b } } 25 + } + + { + } + } diff --git a/tests/formats.info.rye b/tests/formats.info.rye index 1bfdce78..a8b273b6 100644 --- a/tests/formats.info.rye +++ b/tests/formats.info.rye @@ -505,3 +505,331 @@ section "Conversion " "Functions for converting between different types and kind } +section "Default" "" { + group "decode\\hex-string" + "Decodes a hexadecimal string to a UTF-8 string." + { + argsn 1 + pure + arg `hex-string: hexadecimal string to decode` + returns `string containing the decoded bytes as a UTF-8 string` + } + + { + equal { "48656c6c6f20576f726c64" |decode\hex-string } "Hello World" + equal { "48656c6c6f20576f726c64" |decode\hex-string |type? } 'string + equal { "invalid" |decode\hex-string |disarm |type? } 'error + } + + { + } + + group "encode\\hex-string" + "Encodes a string to its hexadecimal representation." + { + argsn 1 + pure + arg `string: string to encode` + returns `hexadecimal string representation` + } + + { + equal { "Hello World" |encode\hex-string } "48656c6c6f20576f726c64" + equal { "Hello World" |encode\hex-string |type? } 'string + equal { "" |encode\hex-string } "" + } + + { + } + + group "clean\\hex-string" + "Removes all whitespace from a hex string and ensures even length." + { + argsn 1 + pure + arg `hex-string: raw hex string with possible whitespace` + returns `cleaned hex string with whitespace removed and even length` + } + + { + equal { " 70 44 01 4b \n 55 50 4e 51 " |clean\hex-string } "7044014b55504e51" + equal { "70 44 01 4b\t55 50 4e 51\r\n52 0a" |clean\hex-string } "7044014b55504e51520a" + equal { "7044014b55504e51520a" |clean\hex-string } "7044014b55504e51520a" + } + + { + } + + group "charmap\\windows-1250" + "Returns the Windows-1250 (CP1250) character encoding for Central European languages." + { + pure + arg `none` + returns `Windows-1250 encoding as a native value` + } + + { + equal { charmap\windows-1250 |type? } 'native + equal { charmap\windows-1250 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\iso-8859-1" + "Returns the ISO-8859-1 (Latin-1) character encoding." + { + pure + arg `none` + returns `ISO-8859-1 encoding as a native value` + } + + { + equal { charmap\iso-8859-1 |type? } 'native + equal { charmap\iso-8859-1 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\iso-8859-2" + "Returns the ISO-8859-2 (Latin-2) character encoding for Central European languages." + { + pure + arg `none` + returns `ISO-8859-2 encoding as a native value` + } + + { + equal { charmap\iso-8859-2 |type? } 'native + equal { charmap\iso-8859-2 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\windows-1252" + "Returns the Windows-1252 character encoding for Western European languages." + { + pure + arg `none` + returns `Windows-1252 encoding as a native value` + } + + { + equal { charmap\windows-1252 |type? } 'native + equal { charmap\windows-1252 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\code-page-437" + "Returns the Code Page 437 (DOS/IBM PC) character encoding." + { + pure + arg `none` + returns `Code Page 437 encoding as a native value` + } + + { + equal { charmap\code-page-437 |type? } 'native + equal { charmap\code-page-437 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\code-page-850" + "Returns the Code Page 850 (DOS Latin-1) character encoding." + { + pure + arg `none` + returns `Code Page 850 encoding as a native value` + } + + { + equal { charmap\code-page-850 |type? } 'native + equal { charmap\code-page-850 |kind? } 'charmap-encoding + } + + { + } + + group "charmap\\koi8r" + "Returns the KOI8-R character encoding for Russian." + { + pure + arg `none` + returns `KOI8-R encoding as a native value` + } + + { + equal { charmap\koi8r |type? } 'native + equal { charmap\koi8r |kind? } 'charmap-encoding + } + + { + } + + group "charmap-encoding//Decoder" + "Creates a new text decoder for the given character encoding." + { + argsn 1 + pure + arg `encoding: charmap encoding as a native value` + returns `text decoder as a native value` + } + + { + equal { charmap\windows-1250 |Decoder |type? } 'native + equal { charmap\windows-1250 |Decoder |kind? } 'text-decoder + } + + { + } + + group "charmap-encoding//Encoder" + "Creates a new text encoder for the given character encoding." + { + argsn 1 + pure + arg `encoding: charmap encoding as a native value` + returns `text encoder as a native value` + } + + { + equal { charmap\windows-1250 |Encoder |type? } 'native + equal { charmap\windows-1250 |Encoder |kind? } 'text-encoder + } + + { + } + + group "text-decoder//Decode" + "Decodes a string using the given text decoder." + { + argsn 2 + pure + arg `decoder: text decoder as a native value` + arg `input: string to decode` + returns `decoded string` + } + + { + equal { charmap\windows-1250 |Decoder |Decode "Plačilo računa" |contains? "č" } 1 + equal { charmap\windows-1250 |Decoder |Decode "Hello" } "Hello" + equal { charmap\windows-1250 |Decoder |Decode "" } "" + } + + { + } + + group "text-encoder//Encode" + "Encodes a string using the given text encoder." + { + argsn 2 + pure + arg `encoder: text encoder as a native value` + arg `input: string to encode` + returns `encoded string` + } + + { + equal { charmap\windows-1250 |Encoder |Encode "Hello" } "Hello" + equal { charmap\windows-1250 |Encoder |Encode "Plačilo" |encode\hex-string } "506c61e8696c6f" + } + + { + } + + group "is-space" + "Checks if a single character string is a whitespace character." + { + argsn 1 + pure + arg `input: string to check (should be single character)` + returns `boolean true if character is whitespace, false otherwise` + } + + { + equal { " " |is-space } true + equal { "\t" |is-space } true + equal { "\n" |is-space } true + equal { "\r" |is-space } true + equal { "a" |is-space } false + equal { "A" |is-space } false + equal { "1" |is-space } false + } + + { + } + + group "remove-all-space" + "Removes all whitespace characters from a string." + { + argsn 1 + pure + arg `input: string to process` + returns `string with all whitespace characters removed` + } + + { + equal { "Hello World" |remove-all-space } "HelloWorld" + equal { " Hello World " |remove-all-space } "HelloWorld" + equal { "\t\n Hello \r\n World \t" |remove-all-space } "HelloWorld" + equal { "" |remove-all-space } "" + } + + { + } + +} + +section "Structures " "Functions for converting between Rye and Go data structures" { + group "dict->struct" + "Converts a Rye Dict to a Go struct using reflection. Takes a Dict and a Native containing a pointer to a struct." + { + argsn 2 + arg `dict: Rye Dict containing field names and values` + arg `struct-ptr: Native containing a pointer to a Go struct` + returns `native containing the populated struct` + } + + { + } + + { + } + + group "dict->new-struct" + "Creates a new Go struct type at runtime based on a Rye Dict and populates it with values. Takes a Dict and a string for the struct name." + { + argsn 2 + arg `dict: Rye Dict containing field names and values` + arg `name: String name for the new struct type` + returns `native containing the dynamically created struct` + } + + { + } + + { + } + + group "struct->dict" + "Converts a Go struct to a Rye Dict using reflection. Takes a Native containing a struct or pointer to a struct." + { + argsn 1 + arg `struct: Native containing a Go struct or pointer to struct` + returns `dict with struct field names as keys and field values as values` + } + + { + } + + { + } + +} + diff --git a/tests/io.info.rye b/tests/io.info.rye index 0ccabdcd..a3c3972e 100644 --- a/tests/io.info.rye +++ b/tests/io.info.rye @@ -1149,7 +1149,7 @@ db .Exec { INSERT INTO users ( name , age ) VALUES ( ?name , 42 ) } { equal { Open sqlite://test.db |Query "SELECT * FROM test" |type? } 'table - Also supports: db .Query { "SELECT * FROM test WHERE id = ?" 123 } + ; Also supports: db .Query { "SELECT * FROM test WHERE id = ?" 123 } } { diff --git a/tests/regen b/tests/regen index 69009a78..3a889494 100755 --- a/tests/regen +++ b/tests/regen @@ -14,7 +14,8 @@ ../cmd/rbit/rbit ../evaldo/builtins_base_combinators.go >> base.info.rye ../cmd/rbit/rbit ../evaldo/builtins_base_printing_not.go >> base.info.rye ../cmd/rbit/rbit ../baseio/builtins_printing.go >> base.info.rye -../cmd/rbit/rbit ../batteries/builtins_error_handling.go >> base.info.rye +../cmd/rbit/rbit ../evaldo/builtins_error_handling.go >> base.info.rye +../cmd/rbit/rbit ../evaldo/builtins_base_mth.go >> base.info.rye ../cmd/rbit/rbit ../evaldo/builtins_table.go > table.info.rye ../cmd/rbit/rbit ../batteries/builtins_regexp.go > formats.info.rye ../cmd/rbit/rbit ../batteries/builtins_json.go >> formats.info.rye @@ -23,6 +24,7 @@ ../cmd/rbit/rbit ../batteries/builtins_html.go >> formats.info.rye ../cmd/rbit/rbit ../batteries/builtins_markdown.go >> formats.info.rye ../cmd/rbit/rbit ../batteries/builtins_conversion.go >> formats.info.rye +../cmd/rbit/rbit ../batteries/builtins_encoding.go >> formats.info.rye ../cmd/rbit/rbit ../batteries/builtins_io.go > io.info.rye ../cmd/rbit/rbit ../baseio/builtins_baseio.go >> io.info.rye ../cmd/rbit/rbit ../batteries/builtins_cmd.go >> io.info.rye @@ -47,5 +49,5 @@ ../cmd/rbit/rbit ../batteries/builtins_goroutines.go >> system.info.rye ../cmd/rbit/rbit ../batteries/builtins_complex.go >> dialects.info.rye ../cmd/rbit/rbit ../batteries/builtins_pipes.go > pipes.info.rye -# ../cmd/rbit/rbit ../batteries/builtins_structures.go >> formats.info.rye -# ../cmd/rbit/rbit ../batteries/builtins_web.go > web.info.rye +../cmd/rbit/rbit ../batteries/builtins_structures.go >> formats.info.rye +../cmd/rbit/rbit ../batteries/builtins_web.go > web.info.rye