Make docstring tests use an actual expression AST instead of text

matching
This commit is contained in:
Bruce Hill 2025-03-25 15:40:59 -04:00
parent d88d8648dc
commit 1f9147187d
26 changed files with 200 additions and 219 deletions

View File

@ -75,7 +75,7 @@ clean:
%: %.md %: %.md
pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@ pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@
examples: examples/commands/commands examples/base64/base64 examples/ini/ini examples/game/game \ examples: examples/base64/base64 examples/ini/ini examples/game/game \
examples/tomodeps/tomodeps examples/tomo-install/tomo-install examples/wrap/wrap examples/colorful/colorful examples/tomodeps/tomodeps examples/tomo-install/tomo-install examples/wrap/wrap examples/colorful/colorful
./build/tomo -qIL examples/commands examples/shell examples/base64 examples/log examples/ini examples/vectors examples/game \ ./build/tomo -qIL examples/commands examples/shell examples/base64 examples/log examples/ini examples/vectors examples/game \
examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap examples/pthreads examples/colorful examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap examples/pthreads examples/colorful

View File

@ -106,21 +106,21 @@ func main():
# Tables are efficient hash maps # Tables are efficient hash maps
table := {"one"=1, "two"=2} table := {"one"=1, "two"=2}
>> table["two"] >> table["two"]
= 2 : Int? = 2?
# The value returned is optional because none will be returned if the key # The value returned is optional because none will be returned if the key
# is not in the table: # is not in the table:
>> table["xxx"] >> table["xxx"]
= none : Int? = none : Int
# Optional values can be converted to regular values using `!` (which will # Optional values can be converted to regular values using `!` (which will
# create a runtime error if the value is null): # create a runtime error if the value is null):
>> table["two"]! >> table["two"]!
= 2 : Int = 2
# You can also use `or` to provide a fallback value to replace none: # You can also use `or` to provide a fallback value to replace none:
>> table["xxx"] or 0 >> table["xxx"] or 0
= 0 : Int = 0
# Empty tables require specifying the key and value types: # Empty tables require specifying the key and value types:
empty_table := {:Text,Int} empty_table := {:Text,Int}
@ -339,7 +339,7 @@ func demo_enums():
= yes = yes
>> {my_shape="nice"} >> {my_shape="nice"}
= {Circle(1)="nice"} = {Shape.Circle(1)="nice"}
func demo_lambdas(): func demo_lambdas():
# Lambdas, or anonymous functions, can be used like this: # Lambdas, or anonymous functions, can be used like this:

View File

@ -165,7 +165,7 @@ CORD ast_to_xml(ast_t *ast)
T(Optional, "<Optional>%r</Optional>", ast_to_xml(data.value)) T(Optional, "<Optional>%r</Optional>", ast_to_xml(data.value))
T(NonOptional, "<NonOptional>%r</NonOptional>", ast_to_xml(data.value)) T(NonOptional, "<NonOptional>%r</NonOptional>", ast_to_xml(data.value))
T(Moment, "<Moment/>") T(Moment, "<Moment/>")
T(DocTest, "<DocTest>%r<output>%r</output></DocTest>", optional_tagged("expression", data.expr), xml_escape(data.output)) T(DocTest, "<DocTest>%r%r</DocTest>", optional_tagged("expression", data.expr), optional_tagged("expected", data.expected))
T(Use, "<Use>%r%r</Use>", optional_tagged("var", data.var), xml_escape(data.path)) T(Use, "<Use>%r%r</Use>", optional_tagged("var", data.var), xml_escape(data.path))
T(InlineCCode, "<InlineCode>%r</InlineCode>", xml_escape(data.code)) T(InlineCCode, "<InlineCode>%r</InlineCode>", xml_escape(data.code))
T(Deserialize, "<Deserialize><type>%r</type>%r</Deserialize>", type_ast_to_xml(data.type), ast_to_xml(data.value)) T(Deserialize, "<Deserialize><type>%r</type>%r</Deserialize>", type_ast_to_xml(data.type), ast_to_xml(data.value))

View File

@ -323,8 +323,7 @@ struct ast_s {
Moment_t moment; Moment_t moment;
} Moment; } Moment;
struct { struct {
ast_t *expr; ast_t *expr, *expected;
const char *output;
bool skip_source:1; bool skip_source:1;
} DocTest; } DocTest;
struct { struct {

View File

@ -39,7 +39,7 @@ static CORD compile_string_literal(CORD literal);
CORD promote_to_optional(type_t *t, CORD code) CORD promote_to_optional(type_t *t, CORD code)
{ {
if (t == THREAD_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE || t->tag == MomentType) { if (t == THREAD_TYPE || t == PATH_TYPE || t == PATH_TYPE_TYPE || t == MATCH_TYPE || t->tag == MomentType) {
return code; return code;
} else if (t->tag == IntType) { } else if (t->tag == IntType) {
switch (Match(t, IntType)->bits) { switch (Match(t, IntType)->bits) {
@ -878,24 +878,13 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
if (!expr_t) if (!expr_t)
code_err(test->expr, "I couldn't figure out the type of this expression"); code_err(test->expr, "I couldn't figure out the type of this expression");
CORD output = CORD_EMPTY;
if (test->output) {
const uint8_t *raw = (const uint8_t*)CORD_to_const_char_star(test->output);
uint8_t buf[128] = {0};
size_t norm_len = sizeof(buf);
uint8_t *norm = u8_normalize(UNINORM_NFC, (uint8_t*)raw, strlen((char*)raw)+1, buf, &norm_len);
assert(norm[norm_len-1] == 0);
output = CORD_from_char_star((char*)norm);
if (norm && norm != buf) free(norm);
}
CORD setup = CORD_EMPTY; CORD setup = CORD_EMPTY;
CORD test_code; CORD test_code;
if (test->expr->tag == Declare) { if (test->expr->tag == Declare) {
auto decl = Match(test->expr, Declare); auto decl = Match(test->expr, Declare);
const char *varname = Match(decl->var, Var)->name; const char *varname = Match(decl->var, Var)->name;
if (streq(varname, "_")) if (streq(varname, "_"))
return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .output=output, .skip_source=test->skip_source)); return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .expected=test->expected, .skip_source=test->skip_source));
CORD var = CORD_all("_$", Match(decl->var, Var)->name); CORD var = CORD_all("_$", Match(decl->var, Var)->name);
type_t *t = get_type(env, decl->value); type_t *t = get_type(env, decl->value);
CORD val_code = compile_maybe_incref(env, decl->value, t); CORD val_code = compile_maybe_incref(env, decl->value, t);
@ -922,7 +911,7 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
expr_t = lhs_t; expr_t = lhs_t;
} else { } else {
// Multi-assign or assignment to potentially non-idempotent targets // Multi-assign or assignment to potentially non-idempotent targets
if (output && assign->targets->next) if (test->expected && assign->targets->next)
code_err(ast, "Sorry, but doctesting with '=' is not supported for multi-assignments"); code_err(ast, "Sorry, but doctesting with '=' is not supported for multi-assignments");
test_code = "({ // Assignment\n"; test_code = "({ // Assignment\n";
@ -969,12 +958,16 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
} else { } else {
test_code = compile(env, test->expr); test_code = compile(env, test->expr);
} }
if (test->output) { if (test->expected) {
type_t *expected_type = get_type(env, test->expected);
if (!type_eq(expr_t, expected_type))
code_err(ast, "The type on the top of this test (%T) is different from the type on the bottom (%T)",
expr_t, expected_type);
return CORD_asprintf( return CORD_asprintf(
"%rtest(%r, %r, %r, %ld, %ld);", "%rtest(%r, %r, %r, %ld, %ld);",
setup, test_code, setup, test_code,
compile(env, test->expected),
compile_type_info(expr_t), compile_type_info(expr_t),
compile_string_literal(output),
(int64_t)(test->expr->start - test->expr->file->text), (int64_t)(test->expr->start - test->expr->file->text),
(int64_t)(test->expr->end - test->expr->file->text)); (int64_t)(test->expr->end - test->expr->file->text));
} else { } else {
@ -1952,8 +1945,10 @@ CORD compile_int_to_type(env_t *env, ast_t *ast, type_t *target)
int64_t target_bits = (int64_t)Match(target, IntType)->bits; int64_t target_bits = (int64_t)Match(target, IntType)->bits;
switch (target_bits) { switch (target_bits) {
case TYPE_IBITS64: case TYPE_IBITS64:
if (mpz_cmp_si(i, INT64_MIN) == 0)
return "I64(INT64_MIN)";
if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0)
return CORD_asprintf("I64(%s)", c_literal); return CORD_asprintf("I64(%sL)", c_literal);
break; break;
case TYPE_IBITS32: case TYPE_IBITS32:
if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0) if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0)
@ -2204,6 +2199,7 @@ CORD compile_none(type_t *t)
if (t == THREAD_TYPE) return "NULL"; if (t == THREAD_TYPE) return "NULL";
else if (t == PATH_TYPE) return "NONE_PATH"; else if (t == PATH_TYPE) return "NONE_PATH";
else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})"; else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})";
else if (t == MATCH_TYPE) return "NONE_MATCH";
switch (t->tag) { switch (t->tag) {
case BigIntType: return "NONE_INT"; case BigIntType: return "NONE_INT";

View File

@ -2418,22 +2418,14 @@ PARSER(parse_doctest) {
spaces(&pos); spaces(&pos);
ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest"); ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest");
whitespace(&pos); whitespace(&pos);
const char* output = NULL; ast_t *expected = NULL;
if (match(&pos, "=")) { if (match(&pos, "=")) {
spaces(&pos); spaces(&pos);
const char *output_start = pos, expected = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expected expression here");
*output_end = pos + strcspn(pos, "\r\n");
if (output_end <= output_start)
parser_err(ctx, output_start, output_end, "You're missing expected output here");
int64_t trailing_spaces = 0;
while (output_end - trailing_spaces - 1 > output_start && (output_end[-trailing_spaces-1] == ' ' || output_end[-trailing_spaces-1] == '\t'))
++trailing_spaces;
output = GC_strndup(output_start, (size_t)((output_end - output_start) - trailing_spaces));
pos = output_end;
} else { } else {
pos = expr->end; pos = expr->end;
} }
return NewAST(ctx->file, start, pos, DocTest, .expr=expr, .output=output); return NewAST(ctx->file, start, pos, DocTest, .expr=expr, .expected=expected);
} }
PARSER(parse_say) { PARSER(parse_say) {

View File

@ -602,29 +602,19 @@ public void end_inspect(const void *expr, const TypeInfo_t *type)
} }
__attribute__((nonnull)) __attribute__((nonnull))
public void test_value(const void *expr, const TypeInfo_t *type, const char *expected) public void test_value(const void *expr, const void *expected, const TypeInfo_t *type)
{ {
Text_t expr_text = generic_as_text(expr, USE_COLOR, type); Text_t expr_text = generic_as_text(expr, USE_COLOR, type);
Text_t type_name = generic_as_text(NULL, false, type); Text_t expected_text = generic_as_text(expected, USE_COLOR, type);
Text_t expected_text = Text$from_str(expected);
Text_t expr_plain = USE_COLOR ? generic_as_text(expr, false, type) : expr_text;
bool success = Text$equal_values(expr_plain, expected_text);
if (!success) {
OptionalMatch_t colon = Text$find(expected_text, Text(":"), I_small(1));
if (colon.index.small) {
Text_t with_type = Text$concat(expr_plain, Text(" : "), type_name);
success = Text$equal_values(with_type, expected_text);
}
}
bool success = Text$equal_values(expr_text, expected_text);
if (!success) { if (!success) {
print_stack_trace(stderr, 2, 4); print_stack_trace(stderr, 2, 4);
fprintf(stderr, fprintf(stderr,
USE_COLOR USE_COLOR
? "\n\x1b[31;7m ==================== TEST FAILED ==================== \x1b[0;1m\n\nYou expected: \x1b[36;1m%s\x1b[0m\n\x1b[1m But I got:\x1b[m %k\n\n" ? "\n\x1b[31;7m ==================== TEST FAILED ==================== \x1b[0;1m\n\nYou expected: \x1b[m%k\x1b[0m\n\x1b[1m But I got:\x1b[m %k\n\n"
: "\n==================== TEST FAILED ====================\n\nYou expected: %s\n But I got: %k\n\n", : "\n==================== TEST FAILED ====================\n\nYou expected: %k\n But I got: %k\n\n",
expected, &expr_text); &expected_text, &expr_text);
fflush(stderr); fflush(stderr);
raise(SIGABRT); raise(SIGABRT);

View File

@ -39,10 +39,11 @@ void end_inspect(const void *expr, const TypeInfo_t *type);
end_inspect(&_expr, typeinfo); \ end_inspect(&_expr, typeinfo); \
} }
__attribute__((nonnull)) __attribute__((nonnull))
void test_value(const void *expr, const TypeInfo_t *type, const char *expected); void test_value(const void *expr, const void *expected, const TypeInfo_t *type);
#define test(expr, typeinfo, expected, start, end) {\ #define test(expr, expected, typeinfo, start, end) {\
auto _expr = expr; \ auto _expr = expr; \
test_value(&_expr, typeinfo, expected); \ auto _expected = expected; \
test_value(&_expr, &_expected, typeinfo); \
} }
void say(Text_t text, bool newline); void say(Text_t text, bool newline);

View File

@ -285,6 +285,7 @@ PUREFUNC bool has_heap_memory(type_t *t)
PUREFUNC bool has_stack_memory(type_t *t) PUREFUNC bool has_stack_memory(type_t *t)
{ {
if (!t) return false;
switch (t->tag) { switch (t->tag) {
case PointerType: return Match(t, PointerType)->is_stack; case PointerType: return Match(t, PointerType)->is_stack;
case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); case OptionalType: return has_stack_memory(Match(t, OptionalType)->type);

View File

@ -1,7 +1,7 @@
func main(): func main():
do: do:
>> [:Num32] >> [:Num32]
= [] : [Num32] = [:Num32]
do: do:
>> arr := [10, 20, 30] >> arr := [10, 20, 30]
@ -160,27 +160,27 @@ func main():
= [1, 2, 3, 4, 5] = [1, 2, 3, 4, 5]
>> ["a", "b", "c"]:find("b") >> ["a", "b", "c"]:find("b")
= 2 : Int? = 2?
>> ["a", "b", "c"]:find("XXX") >> ["a", "b", "c"]:find("XXX")
= none : Int? = none:Int
>> [10, 20]:first(func(i:&Int): i:is_prime()) >> [10, 20]:first(func(i:&Int): i:is_prime())
= none : Int? = none:Int
>> [4, 5, 6]:first(func(i:&Int): i:is_prime()) >> [4, 5, 6]:first(func(i:&Int): i:is_prime())
= 2 : Int? = 2?
do: do:
>> nums := &[10, 20, 30, 40, 50] >> nums := &[10, 20, 30, 40, 50]
>> nums:pop() >> nums:pop()
= 50 = 50?
>> nums >> nums
= &[10, 20, 30, 40] = &[10, 20, 30, 40]
>> nums:pop(2) >> nums:pop(2)
= 20 = 20?
>> nums >> nums
= &[10, 30, 40] = &[10, 30, 40]
>> nums:clear() >> nums:clear()
>> nums >> nums
= &[] = &[:Int]
>> nums:pop() >> nums:pop()
= none = none:Int

View File

@ -2,10 +2,10 @@
func main(): func main():
!! Test bytes: !! Test bytes:
>> Byte(100) >> Byte(100)
= 0x64 = Byte(0x64)
>> Byte(0xFF) >> Byte(0xFF)
= 0xFF = Byte(0xFF)
>> b := Byte(0x0F) >> b := Byte(0x0F)
>> b:hex() >> b:hex()

View File

@ -17,11 +17,11 @@ func choose_text(f:Foo->Text):
func main(): func main():
>> Foo.Zero >> Foo.Zero
= Zero = Foo.Zero
>> Foo.One(123) >> Foo.One(123)
= One(123) = Foo.One(123)
>> Foo.Two(123, 456) >> Foo.Two(123, 456)
= Two(x=123, y=456) = Foo.Two(x=123, y=456)
>> one := Foo.One(123) >> one := Foo.One(123)
>> one.One >> one.One

View File

@ -2,4 +2,4 @@ extern sqrt:func(n:Num->Num)
func main(): func main():
>> sqrt(4) >> sqrt(4)
= 2 = 2.

View File

@ -1,7 +1,7 @@
func main(): func main():
>> inline C:Int32 { int x = 1 + 2; x } >> inline C:Int32 { int x = 1 + 2; x }
= 3 = Int32(3)
>> inline C { >> inline C {
say(Text("Inline C code works!"), true); say(Text("Inline C code works!"), true);

View File

@ -12,20 +12,20 @@ func main():
= 10 = 10
>> Int8(1) + Int16(2) >> Int8(1) + Int16(2)
= 3 : Int16 = Int16(3)
>> 1 << 10 >> 1 << 10
= 1024 = 1024
!! Signed and unsigned bit shifting: !! Signed and unsigned bit shifting:
>> Int64(-2) << 1 >> Int64(-2) << 1
= -4 : Int64 = Int64(-4)
>> Int64(-2) <<< 1 >> Int64(-2) <<< 1
= -4 : Int64 = Int64(-4)
>> Int64(-2) >> 1 >> Int64(-2) >> 1
= -1 : Int64 = Int64(-1)
>> Int64(-2) >>> 1 >> Int64(-2) >>> 1
= 9223372036854775807 : Int64 = Int64(9223372036854775807)
>> 3 and 2 >> 3 and 2
= 2 = 2
@ -51,9 +51,9 @@ func main():
= "0o173" = "0o173"
>> Int64.min >> Int64.min
= -9223372036854775808 = Int64(-9223372036854775808)
>> Int64.max >> Int64.max
= 9223372036854775807 = Int64(9223372036854775807)
>> Int32(123):hex() >> Int32(123):hex()
@ -64,7 +64,7 @@ func main():
= "0x7B" = "0x7B"
>> Int(2.1, truncate=yes) >> Int(2.1, truncate=yes)
= 2 : Int = 2
do: do:
>> small_int := 1 >> small_int := 1
@ -127,9 +127,9 @@ func main():
= 0 = 0
>> Int64(yes) >> Int64(yes)
= 1 : Int64 = Int64(1)
>> Int64(no) >> Int64(no)
= 0 : Int64 = Int64(0)
>> 4:choose(2) >> 4:choose(2)
= 6 = 6

View File

@ -21,13 +21,13 @@ func main():
= "1 hour ago" = "1 hour ago"
>> t:seconds_till(t:after(minutes=2)) >> t:seconds_till(t:after(minutes=2))
= 120 = 120.
>> t:minutes_till(t:after(minutes=2)) >> t:minutes_till(t:after(minutes=2))
= 2 = 2.
>> t:hours_till(t:after(minutes=60)) >> t:hours_till(t:after(minutes=60))
= 1 = 1.
>> t:day_of_week() # 1 = Sun, 2 = Mon, 3 = Tue >> t:day_of_week() # 1 = Sun, 2 = Mon, 3 = Tue
= 3 = 3
@ -36,7 +36,7 @@ func main():
= "Tuesday" = "Tuesday"
>> t:unix_timestamp() >> t:unix_timestamp()
= 1704221100 : Int64 = Int64(1704221100)
>> t == Moment.from_unix_timestamp(1704221100) >> t == Moment.from_unix_timestamp(1704221100)
= yes = yes

View File

@ -3,13 +3,13 @@ func main():
= 1.5 = 1.5
>> n + n >> n + n
= 3 = 3.
>> n * 2 >> n * 2
= 3 = 3.
>> n - n >> n - n
= 0 = 0.
>> Num.PI >> Num.PI
= 3.141592653589793 = 3.141592653589793
@ -18,12 +18,12 @@ func main():
= "3.1415926536" = "3.1415926536"
>> Num.INF >> Num.INF
= inf = Num.INF
>> Num.INF:isinf() >> Num.INF:isinf()
= yes = yes
>> nan := none : Num >> nan := none : Num
= none : Num? = none:Num
>> nan == nan >> nan == nan
= yes = yes
>> nan < nan >> nan < nan
@ -33,7 +33,7 @@ func main():
>> nan != nan >> nan != nan
= no = no
>> nan <> nan >> nan <> nan
= 0 = Int32(0)
>> nan == 0.0 >> nan == 0.0
= no = no
>> nan < 0.0 >> nan < 0.0
@ -43,13 +43,13 @@ func main():
>> nan != 0.0 >> nan != 0.0
= yes = yes
>> nan <> 0.0 >> nan <> 0.0
= -1 = Int32(-1)
>> nan + 1 >> nan + 1
= none : Num? = none:Num
>> 0./0. >> 0./0.
= none : Num? = none:Num
>> Num.PI:cos()!:near(-1) >> Num.PI:cos()!:near(-1)
= yes = yes
@ -60,15 +60,15 @@ func main():
= no = no
>> Num32.sqrt(16) >> Num32.sqrt(16)
= 4 : Num32? = Num32(4)?
>> 0.25:mix(10, 20) >> 0.25:mix(10, 20)
= 12.5 = 12.5
>> 2.0:mix(10, 20) >> 2.0:mix(10, 20)
= 30 = 30.
>> Num(5) >> Num(5)
= 5 : Num = 5.
>> 0.5:percent() >> 0.5:percent()
= "50%" = "50%"

View File

@ -75,32 +75,32 @@ func maybe_mutexed(should_i:Bool->mutexed(Bool)?):
func main(): func main():
>> 5? >> 5?
= 5 : Int? = 5?
>> if no: >> if no:
none:Int none:Int
else: else:
5 5
= 5 : Int? = 5?
>> 5? or -1 >> 5? or -1
= 5 : Int = 5
>> 5? or fail("Non-null is falsey") >> 5? or fail("Non-null is falsey")
= 5 : Int = 5
>> 5? or exit("Non-null is falsey") >> 5? or exit("Non-null is falsey")
= 5 : Int = 5
>> (none:Int) or -1 >> (none:Int) or -1
= -1 : Int = -1
do: do:
!! Ints: !! Ints:
>> yep := maybe_int(yes) >> yep := maybe_int(yes)
= 123 : Int? = 123?
>> nope := maybe_int(no) >> nope := maybe_int(no)
= none : Int? = none:Int
>> if yep: >> if yep:
>> yep >> yep
= 123 = 123
@ -113,12 +113,12 @@ func main():
!! ... !! ...
!! Int64s: !! Int64s:
>> yep := maybe_int64(yes) >> yep := maybe_int64(yes)
= 123 : Int64? = Int64(123)?
>> nope := maybe_int64(no) >> nope := maybe_int64(no)
= none : Int64? = none:Int64
>> if yep: >> if yep:
>> yep >> yep
= 123 = Int64(123)
else: fail("Falsey: $yep") else: fail("Falsey: $yep")
>> if nope: >> if nope:
fail("Truthy: $nope") fail("Truthy: $nope")
@ -128,9 +128,9 @@ func main():
!! ... !! ...
!! Arrays: !! Arrays:
>> yep := maybe_array(yes) >> yep := maybe_array(yes)
= [10, 20, 30] : [Int]? = [10, 20, 30]?
>> nope := maybe_array(no) >> nope := maybe_array(no)
= none : [Int]? = none:[Int]
>> if yep: >> if yep:
>> yep >> yep
= [10, 20, 30] = [10, 20, 30]
@ -143,9 +143,9 @@ func main():
!! ... !! ...
!! Bools: !! Bools:
>> yep := maybe_bool(yes) >> yep := maybe_bool(yes)
= no : Bool? = no?
>> nope := maybe_bool(no) >> nope := maybe_bool(no)
= none : Bool? = none:Bool
>> if yep: >> if yep:
>> yep >> yep
= no = no
@ -158,9 +158,9 @@ func main():
!! ... !! ...
!! Text: !! Text:
>> yep := maybe_text(yes) >> yep := maybe_text(yes)
= "Hello" : Text? = "Hello"?
>> nope := maybe_text(no) >> nope := maybe_text(no)
= none : Text? = none:Text
>> if yep: >> if yep:
>> yep >> yep
= "Hello" = "Hello"
@ -173,9 +173,9 @@ func main():
!! ... !! ...
!! Nums: !! Nums:
>> yep := maybe_num(yes) >> yep := maybe_num(yes)
= 12.3 : Num? = 12.3?
>> nope := maybe_num(no) >> nope := maybe_num(no)
= none : Num? = none:Num
>> if yep: >> if yep:
>> yep >> yep
= 12.3 = 12.3
@ -187,14 +187,14 @@ func main():
do: do:
!! ... !! ...
!! Lambdas: !! Lambdas:
>> yep := maybe_lambda(yes) # >> yep := maybe_lambda(yes)
= func() [optionals.tm:54] : func()? # = func() [optionals.tm:54] : func()?
>> nope := maybe_lambda(no) >> nope := maybe_lambda(no)
= none : func()? = none : func()
>> if yep: # >> if yep:
>> yep # >> yep
= func() [optionals.tm:54] # = func() [optionals.tm:54]
else: fail("Falsey: $yep") # else: fail("Falsey: $yep")
>> if nope: >> if nope:
fail("Truthy: $nope") fail("Truthy: $nope")
else: !! Falsey: $nope else: !! Falsey: $nope
@ -203,9 +203,9 @@ func main():
!! ... !! ...
!! Structs: !! Structs:
>> yep := Struct.maybe(yes) >> yep := Struct.maybe(yes)
= Struct(x=123, y="hello") : Struct? = Struct(x=123, y="hello")?
>> nope := Struct.maybe(no) >> nope := Struct.maybe(no)
= none : Struct? = none:Struct
>> if yep: >> if yep:
>> yep >> yep
= Struct(x=123, y="hello") = Struct(x=123, y="hello")
@ -218,12 +218,12 @@ func main():
!! ... !! ...
!! Enums: !! Enums:
>> yep := Enum.maybe(yes) >> yep := Enum.maybe(yes)
= Y(123) : Enum? = Enum.Y(123)?
>> nope := Enum.maybe(no) >> nope := Enum.maybe(no)
= none : Enum? = none : Enum
>> if yep: >> if yep:
>> yep >> yep
= Y(123) = Enum.Y(123)
else: fail("Falsey: $yep") else: fail("Falsey: $yep")
>> if nope: >> if nope:
fail("Truthy: $nope") fail("Truthy: $nope")
@ -233,9 +233,9 @@ func main():
!! ... !! ...
!! C Strings: !! C Strings:
>> yep := maybe_c_string(yes) >> yep := maybe_c_string(yes)
= CString("hi") : CString? = CString("hi")?
>> nope := maybe_c_string(no) >> nope := maybe_c_string(no)
= none : CString? = none : CString
>> if yep: >> if yep:
>> yep >> yep
= CString("hi") = CString("hi")
@ -250,7 +250,7 @@ func main():
>> yep := maybe_thread(yes) >> yep := maybe_thread(yes)
# No "=" test here because threads use addresses in the text version # No "=" test here because threads use addresses in the text version
>> nope := maybe_thread(no) >> nope := maybe_thread(no)
= none : Thread? = none : Thread
>> if yep: >> yep >> if yep: >> yep
else: fail("Falsey: $yep") else: fail("Falsey: $yep")
>> if nope: >> if nope:
@ -263,7 +263,7 @@ func main():
>> yep := maybe_mutexed(yes) >> yep := maybe_mutexed(yes)
# No "=" test here because threads use addresses in the text version # No "=" test here because threads use addresses in the text version
>> nope := maybe_mutexed(no) >> nope := maybe_mutexed(no)
= none : mutexed(Bool)? = none : mutexed(Bool)
>> if yep: >> yep >> if yep: >> yep
else: fail("Falsey: $yep") else: fail("Falsey: $yep")
>> if nope: >> if nope:
@ -273,11 +273,11 @@ func main():
if yep := maybe_int(yes): if yep := maybe_int(yes):
>> yep >> yep
= 123 : Int = 123
else: fail("Unreachable") else: fail("Unreachable")
>> maybe_int(yes)! >> maybe_int(yes)!
= 123 : Int = 123
# Test comparisons, hashing, equality: # Test comparisons, hashing, equality:
>> (none:Int == 5?) >> (none:Int == 5?)
@ -285,11 +285,11 @@ func main():
>> (5? == 5?) >> (5? == 5?)
= yes = yes
>> {none:Int, none:Int} >> {none:Int, none:Int}
= {none} = {none:Int}
>> {:Int? none, none} >> {:Int? none, none}
= {none} = {none:Int}
>> [5?, none:Int, none:Int, 6?]:sorted() >> [5?, none:Int, none:Int, 6?]:sorted()
= [none, none, 5, 6] = [none:Int, none:Int, 5, 6]
do: do:
>> value := if var := 5?: >> value := if var := 5?:
@ -326,7 +326,7 @@ func main():
= yes = yes
>> [Struct(5,"A")?, Struct(6,"B"), Struct(7,"C")] >> [Struct(5,"A")?, Struct(6,"B"), Struct(7,"C")]
= [Struct(x=5, y="A"), Struct(x=6, y="B"), Struct(x=7, y="C")] = [Struct(x=5, y="A")?, Struct(x=6, y="B")?, Struct(x=7, y="C")?]
if 5? or no: if 5? or no:
say("Binary op 'or' works with optionals") say("Binary op 'or' works with optionals")

View File

@ -6,14 +6,14 @@ func main():
= yes = yes
>> (~/Downloads/file(1).txt) >> (~/Downloads/file(1).txt)
= ~/Downloads/file(1).txt = (~/Downloads/file(1).txt)
>> (/half\)paren) >> (/half\)paren)
= /half)paren = (/half\)paren)
>> filename := "example.txt" >> filename := "example.txt"
>> (~):child(filename) >> (~):child(filename)
= ~/example.txt = (~/example.txt)
>> tmpdir := (/tmp/tomo-test-path-XXXXXX):unique_directory() >> tmpdir := (/tmp/tomo-test-path-XXXXXX):unique_directory()
>> (/tmp):subdirectories():has(tmpdir) >> (/tmp):subdirectories():has(tmpdir)
@ -23,9 +23,9 @@ func main():
>> tmpfile:write("Hello world") >> tmpfile:write("Hello world")
>> tmpfile:append("!") >> tmpfile:append("!")
>> tmpfile:read() >> tmpfile:read()
= "Hello world!" : Text? = "Hello world!"?
>> tmpfile:read_bytes() >> tmpfile:read_bytes()
= [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21] : [Byte]? = [:Byte, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21]?
>> tmpdir:files():has(tmpfile) >> tmpdir:files():has(tmpfile)
= yes = yes
@ -36,9 +36,9 @@ func main():
fail("Couldn't read lines in $tmpfile") fail("Couldn't read lines in $tmpfile")
>> (./does-not-exist.xxx):read() >> (./does-not-exist.xxx):read()
= none : Text? = none : Text
>> (./does-not-exist.xxx):read_bytes() >> (./does-not-exist.xxx):read_bytes()
= none : [Byte]? = none : [Byte]
if lines := (./does-not-exist.xxx):by_line(): if lines := (./does-not-exist.xxx):by_line():
fail("I could read lines in a nonexistent file") fail("I could read lines in a nonexistent file")
else: else:
@ -55,7 +55,7 @@ func main():
>> p:base_name() >> p:base_name()
= "qux.tar.gz" = "qux.tar.gz"
>> p:parent() >> p:parent()
= /foo/baz.x = (/foo/baz.x)
>> p:extension() >> p:extension()
= "tar.gz" = "tar.gz"
>> p:extension(full=no) >> p:extension(full=no)
@ -69,62 +69,62 @@ func main():
= "baz.qux" = "baz.qux"
>> (/):parent() >> (/):parent()
= / = (/)
>> (~/x/.):parent() >> (~/x/.):parent()
= ~ = (~)
>> (~/x):parent() >> (~/x):parent()
= ~ = (~)
>> (.):parent() >> (.):parent()
= .. = (..)
>> (..):parent() >> (..):parent()
= ../.. = (../..)
>> (../foo):parent() >> (../foo):parent()
= .. = (..)
# Concatenation tests: # Concatenation tests:
!! Basic relative path concatenation: !! Basic relative path concatenation:
>> (/foo) ++ (./baz) >> (/foo) ++ (./baz)
= /foo/baz = (/foo/baz)
!! Concatenation with a current directory (`.`): !! Concatenation with a current directory (`.`):
>> (/foo/bar) ++ (./.) >> (/foo/bar) ++ (./.)
= /foo/bar = (/foo/bar)
!! Trailing slash in the first path: !! Trailing slash in the first path:
>> (/foo/) ++ (./baz) >> (/foo/) ++ (./baz)
= /foo/baz = (/foo/baz)
!! Trailing slash in the second path: !! Trailing slash in the second path:
>> (/foo/bar) ++ (./baz/) >> (/foo/bar) ++ (./baz/)
= /foo/bar/baz = (/foo/bar/baz)
!! Removing redundant current directory (`.`): !! Removing redundant current directory (`.`):
>> (/foo/bar) ++ (./baz/./qux) >> (/foo/bar) ++ (./baz/./qux)
= /foo/bar/baz/qux = (/foo/bar/baz/qux)
!! Removing redundant parent directory (`..`): !! Removing redundant parent directory (`..`):
>> (/foo/bar) ++ (./baz/qux/../quux) >> (/foo/bar) ++ (./baz/qux/../quux)
= /foo/bar/baz/quux = (/foo/bar/baz/quux)
!! Collapsing `..` to navigate up: !! Collapsing `..` to navigate up:
>> (/foo/bar/baz) ++ (../qux) >> (/foo/bar/baz) ++ (../qux)
= /foo/bar/qux = (/foo/bar/qux)
!! Current directory and parent directory mixed: !! Current directory and parent directory mixed:
>> (/foo/bar) ++ (././../baz) >> (/foo/bar) ++ (././../baz)
= /foo/baz = (/foo/baz)
!! Path begins with a `.`: !! Path begins with a `.`:
>> (/foo) ++ (./baz/../qux) >> (/foo) ++ (./baz/../qux)
= /foo/qux = (/foo/qux)
!! Multiple slashes: !! Multiple slashes:
>> (/foo) ++ (./baz//qux) >> (/foo) ++ (./baz//qux)
= /foo/baz/qux = (/foo/baz/qux)
!! Complex path with multiple `.` and `..`: !! Complex path with multiple `.` and `..`:
>> (/foo/bar/baz) ++ (./.././qux/./../quux) >> (/foo/bar/baz) ++ (./.././qux/./../quux)
= /foo/bar/quux = (/foo/bar/quux)
!! Globbing: !! Globbing:
>> (./*.tm):glob() >> (./*.tm):glob()

View File

@ -2,10 +2,10 @@ struct Foo(x,y:Int)
func main(): func main():
>> (+: [10, 20, 30]) >> (+: [10, 20, 30])
= 60 : Int? = 60?
>> (+: [:Int]) >> (+: [:Int])
= none : Int? = none : Int
>> (+: [10, 20, 30]) or 0 >> (+: [10, 20, 30]) or 0
= 60 = 60
@ -14,10 +14,10 @@ func main():
= 0 = 0
>> (_max_: [3, 5, 2, 1, 4]) >> (_max_: [3, 5, 2, 1, 4])
= 5 : Int? = 5?
>> (_max_:abs(): [1, -10, 5]) >> (_max_:abs(): [1, -10, 5])
= -10 : Int? = -10?
>> (_max_: [Foo(0, 0), Foo(1, 0), Foo(0, 10)])! >> (_max_: [Foo(0, 0), Foo(1, 0), Foo(0, 10)])!
= Foo(x=1, y=0) = Foo(x=1, y=0)
@ -37,7 +37,7 @@ func main():
= yes = yes
>> (<=: [:Int]) >> (<=: [:Int])
= none : Bool? = none : Bool
>> (<=: [5, 4, 3, 2, 1])! >> (<=: [5, 4, 3, 2, 1])!
= no = no

View File

@ -12,23 +12,23 @@ func main():
>> rng:int(1, 1000) >> rng:int(1, 1000)
= 921 = 921
>> rng:int64(1, 1000) >> rng:int64(1, 1000)
= 324 : Int64 = Int64(324)
>> rng:int32(1, 1000) >> rng:int32(1, 1000)
= 586 : Int32 = Int32(586)
>> rng:int16(1, 1000) >> rng:int16(1, 1000)
= 453 : Int16 = Int16(453)
>> rng:int8(1, 100) >> rng:int8(1, 100)
= 53 : Int8 = Int8(53)
>> rng:byte() >> rng:byte()
= 0xDC : Byte = Byte(0xDC)
>> rng:bytes(10) >> rng:bytes(10)
= [0xA0, 0x5A, 0x10, 0x3F, 0x6C, 0xD1, 0x35, 0xC2, 0x87, 0x8C] = [:Byte, 0xA0, 0x5A, 0x10, 0x3F, 0x6C, 0xD1, 0x35, 0xC2, 0x87, 0x8C]
>> rng:bool(p=0.8) >> rng:bool(p=0.8)
= yes = yes
>> rng:num() >> rng:num()
= 0.03492503353647658 : Num = 0.03492503353647658
>> rng:num32(1, 1000) >> rng:num32(1, 1000)
= 761.05908 : Num32 = Num32(761.05908)
!! Random array methods: !! Random array methods:
>> nums := [10*i for i in 10] >> nums := [10*i for i in 10]

View File

@ -67,7 +67,7 @@ func main():
>> obj.next = @Foo("abcdef", next=obj) >> obj.next = @Foo("abcdef", next=obj)
>> bytes := obj:serialized() >> bytes := obj:serialized()
>> deserialize(bytes -> @Foo) >> deserialize(bytes -> @Foo)
= @Foo(name="root", next=@Foo(name="abcdef", next=@~1)) # = @Foo(name="root", next=@Foo(name="abcdef", next=@~1))
do: do:
>> obj := MyEnum.Two(123, "OKAY") >> obj := MyEnum.Two(123, "OKAY")

View File

@ -60,7 +60,7 @@ func test_text():
>> a := @CorecursiveA(b) >> a := @CorecursiveA(b)
>> b.other = a >> b.other = a
>> a >> a
= @CorecursiveA(@CorecursiveB(@~1)) # = @CorecursiveA(@CorecursiveB(@~1))
func main(): func main():
test_literals() test_literals()
@ -71,9 +71,11 @@ func main():
>> @LinkedList(10, @LinkedList(20)) >> @LinkedList(10, @LinkedList(20))
>> my_pass := Password("Swordfish") >> my_pass := Password("Swordfish")
= Password(...) >> "$my_pass"
= "Password(...)"
>> users_by_password := {my_pass="User1", Password("xxx")="User2"} >> users_by_password := {my_pass="User1", Password("xxx")="User2"}
= {Password(...)="User1", Password(...)="User2"} >> "$users_by_password"
= '{Password(...)="User1", Password(...)="User2"}'
>> users_by_password[my_pass]! >> users_by_password[my_pass]!
= "User1" = "User1"

View File

@ -3,11 +3,11 @@ func main():
= {"one"=1, "two"=2} = {"one"=1, "two"=2}
>> t["one"] >> t["one"]
= 1 : Int? = 1?
>> t["two"] >> t["two"]
= 2 : Int? = 2?
>> t["???"] >> t["???"]
= none : Int? = none:Int
>> t["one"]! >> t["one"]!
= 1 = 1
>> t["???"] or -1 >> t["???"] or -1
@ -22,7 +22,7 @@ func main():
>> t.length >> t.length
= 2 = 2
>> t.fallback >> t.fallback
= none : {Text,Int}? = none : {Text,Int}
>> t.keys >> t.keys
= ["one", "two"] = ["one", "two"]
@ -33,16 +33,16 @@ func main():
= {"three"=3; fallback={"one"=1, "two"=2}} = {"three"=3; fallback={"one"=1, "two"=2}}
>> t2["one"] >> t2["one"]
= 1 : Int? = 1?
>> t2["three"] >> t2["three"]
= 3 : Int? = 3?
>> t2["???"] >> t2["???"]
= none : Int? = none:Int
>> t2.length >> t2.length
= 1 = 1
>> t2.fallback >> t2.fallback
= {"one"=1, "two"=2} : {Text,Int}? = {"one"=1, "two"=2}?
t2_str := "" t2_str := ""
for k,v in t2: for k,v in t2:
@ -95,11 +95,11 @@ func main():
= no = no
>> {1=1, 2=2} <> {2=2, 1=1} >> {1=1, 2=2} <> {2=2, 1=1}
= 0 = Int32(0)
>> [{:Int,Int}, {0=0}, {99=99}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 2=-99, 3=3}, {1=1, 99=-99, 3=4}]:sorted() >> [{:Int,Int}, {0=0}, {99=99}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 2=-99, 3=3}, {1=1, 99=-99, 3=4}]:sorted()
= [{}, {0=0}, {1=1, 2=-99, 3=3}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 99=-99, 3=4}, {99=99}] = [{:Int,Int}, {0=0}, {1=1, 2=-99, 3=3}, {1=1, 2=2, 3=3}, {1=1, 99=99, 3=3}, {1=1, 99=-99, 3=4}, {99=99}]
>> [{:Int}, {1}, {2}, {99}, {0, 3}, {1, 2}, {99}]:sorted() >> [{:Int}, {1}, {2}, {99}, {0, 3}, {1, 2}, {99}]:sorted()
= [{}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}] = [{:Int}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]

View File

@ -51,23 +51,23 @@ func main():
amelie := "Am$(\UE9)lie" amelie := "Am$(\UE9)lie"
>> amelie:split() >> amelie:split()
= ["A", "m", "é", "l", "i", "e"] : [Text] = ["A", "m", "é", "l", "i", "e"]
>> amelie:utf32_codepoints() >> amelie:utf32_codepoints()
= [65, 109, 233, 108, 105, 101] = [:Int32, 65, 109, 233, 108, 105, 101]
>> amelie:bytes() >> amelie:bytes()
= [0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65] = [:Byte, 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
>> Text.from_bytes([:Byte 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65])! >> Text.from_bytes([:Byte 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65])!
= "Amélie" = "Amélie"
>> Text.from_bytes([Byte(0xFF)]) >> Text.from_bytes([Byte(0xFF)])
= none : Text? = none:Text
amelie2 := "Am$(\U65\U301)lie" amelie2 := "Am$(\U65\U301)lie"
>> amelie2:split() >> amelie2:split()
= ["A", "m", "é", "l", "i", "e"] : [Text] = ["A", "m", "é", "l", "i", "e"]
>> amelie2:utf32_codepoints() >> amelie2:utf32_codepoints()
= [65, 109, 233, 108, 105, 101] = [:Int32, 65, 109, 233, 108, 105, 101]
>> amelie2:bytes() >> amelie2:bytes()
= [0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65] = [:Byte, 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
>> amelie:codepoint_names() >> amelie:codepoint_names()
= ["LATIN CAPITAL LETTER A", "LATIN SMALL LETTER M", "LATIN SMALL LETTER E WITH ACUTE", "LATIN SMALL LETTER L", "LATIN SMALL LETTER I", "LATIN SMALL LETTER E"] = ["LATIN CAPITAL LETTER A", "LATIN SMALL LETTER M", "LATIN SMALL LETTER E WITH ACUTE", "LATIN SMALL LETTER L", "LATIN SMALL LETTER I", "LATIN SMALL LETTER E"]
@ -162,7 +162,7 @@ func main():
>> "one$(\r\n)two$(\r\n)three$(\r\n)":lines() >> "one$(\r\n)two$(\r\n)three$(\r\n)":lines()
= ["one", "two", "three"] = ["one", "two", "three"]
>> "":lines() >> "":lines()
= [] = [:Text]
!! Test splitting and joining text: !! Test splitting and joining text:
>> "one two three":split($/ /) >> "one two three":split($/ /)
@ -190,7 +190,7 @@ func main():
= "" = ""
>> "":split() >> "":split()
= [] = [:Text]
!! Test text:find_all() !! Test text:find_all()
>> " #one #two #three ":find_all($/#{alpha}/) >> " #one #two #three ":find_all($/#{alpha}/)
@ -200,26 +200,26 @@ func main():
= [Match(text="#one", index=2, captures=["one"]), Match(text="#two", index=8, captures=["two"]), Match(text="#three", index=13, captures=["three"])] = [Match(text="#one", index=2, captures=["one"]), Match(text="#two", index=8, captures=["two"]), Match(text="#three", index=13, captures=["three"])]
>> " ":find_all($/{alpha}/) >> " ":find_all($/{alpha}/)
= [] = [:Match]
>> " foo(baz(), 1) doop() ":find_all($/{id}(?)/) >> " foo(baz(), 1) doop() ":find_all($/{id}(?)/)
= [Match(text="foo(baz(), 1)", index=2, captures=["foo", "baz(), 1"]), Match(text="doop()", index=17, captures=["doop", ""])] = [Match(text="foo(baz(), 1)", index=2, captures=["foo", "baz(), 1"]), Match(text="doop()", index=17, captures=["doop", ""])]
>> "":find_all($Pattern'') >> "":find_all($Pattern'')
= [] = [:Match]
>> "Hello":find_all($Pattern'') >> "Hello":find_all($Pattern'')
= [] = [:Match]
!! Test text:find() !! Test text:find()
>> " one two three ":find($/{id}/, start=-999) >> " one two three ":find($/{id}/, start=-999)
= none : Match? = none : Match
>> " one two three ":find($/{id}/, start=999) >> " one two three ":find($/{id}/, start=999)
= none : Match? = none : Match
>> " one two three ":find($/{id}/) >> " one two three ":find($/{id}/)
= Match(text="one", index=2, captures=["one"]) : Match? = Match(text="one", index=2, captures=["one"])?
>> " one two three ":find($/{id}/, start=5) >> " one two three ":find($/{id}/, start=5)
= Match(text="two", index=8, captures=["two"]) : Match? = Match(text="two", index=8, captures=["two"])?
!! Test text slicing: !! Test text slicing:
>> "abcdef":slice() >> "abcdef":slice()
@ -240,13 +240,13 @@ func main():
>> house:codepoint_names() >> house:codepoint_names()
= ["CJK Unified Ideographs-5BB6"] = ["CJK Unified Ideographs-5BB6"]
>> house:utf32_codepoints() >> house:utf32_codepoints()
= [23478] = [:Int32, 23478]
>> "🐧":codepoint_names() >> "🐧":codepoint_names()
= ["PENGUIN"] = ["PENGUIN"]
>> Text.from_codepoint_names(["not a valid name here buddy"]) >> Text.from_codepoint_names(["not a valid name here buddy"])
= none : Text? = none : Text
>> "one two; three four":find_all($/; {..}/) >> "one two; three four":find_all($/; {..}/)
= [Match(text="; three four", index=8, captures=["three four"])] = [Match(text="; three four", index=8, captures=["three four"])]
@ -271,13 +271,13 @@ func main():
= " good(x, fn(y), BAD(z), w) " = " good(x, fn(y), BAD(z), w) "
>> "Hello":matches($/{id}/) >> "Hello":matches($/{id}/)
= ["Hello"] : [Text]? = ["Hello"]?
>> "Hello":matches($/{lower}/) >> "Hello":matches($/{lower}/)
= none : [Text]? = none : [Text]
>> "Hello":matches($/{upper}/) >> "Hello":matches($/{upper}/)
= none : [Text]? = none : [Text]
>> "Hello...":matches($/{id}/) >> "Hello...":matches($/{id}/)
= none : [Text]? = none : [Text]
if matches := "hello world":matches($/{id} {id}/): if matches := "hello world":matches($/{id} {id}/):
>> matches >> matches
@ -317,7 +317,7 @@ func main():
= no = no
>> ("hello" ++ " " ++ "Amélie"):reversed() >> ("hello" ++ " " ++ "Amélie"):reversed()
= "eilémA olleh" : Text = "eilémA olleh"
do: do:
!! Testing concatenation-stability: !! Testing concatenation-stability:
@ -369,11 +369,11 @@ func main():
>> "x":middle_pad(5) >> "x":middle_pad(5)
= " x " = " x "
>> "1234":left_pad(8, "XYZ") >> "1234":left_pad(8, "XYZ")
= "XYZX1234" : Text = "XYZX1234"
>> "1234":right_pad(8, "XYZ") >> "1234":right_pad(8, "XYZ")
= "1234XYZX" : Text = "1234XYZX"
>> "1234":middle_pad(9, "XYZ") >> "1234":middle_pad(9, "XYZ")
= "XY1234XYZ" : Text = "XY1234XYZ"
>> amelie:width() >> amelie:width()
= 6 = 6

View File

@ -13,4 +13,4 @@ func main():
>> when n is 1: Int64(1) >> when n is 1: Int64(1)
is 2: Int64(2) is 2: Int64(2)
is 21 + 2: Int64(23) is 21 + 2: Int64(23)
= 23 : Int64? = Int64(23)?