Make docstring tests use an actual expression AST instead of text
matching
This commit is contained in:
parent
d88d8648dc
commit
1f9147187d
2
Makefile
2
Makefile
@ -75,7 +75,7 @@ clean:
|
||||
%: %.md
|
||||
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
|
||||
./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
|
||||
|
@ -106,21 +106,21 @@ func main():
|
||||
# Tables are efficient hash maps
|
||||
table := {"one"=1, "two"=2}
|
||||
>> table["two"]
|
||||
= 2 : Int?
|
||||
= 2?
|
||||
|
||||
# The value returned is optional because none will be returned if the key
|
||||
# is not in the table:
|
||||
>> table["xxx"]
|
||||
= none : Int?
|
||||
= none : Int
|
||||
|
||||
# Optional values can be converted to regular values using `!` (which will
|
||||
# create a runtime error if the value is null):
|
||||
>> table["two"]!
|
||||
= 2 : Int
|
||||
= 2
|
||||
|
||||
# You can also use `or` to provide a fallback value to replace none:
|
||||
>> table["xxx"] or 0
|
||||
= 0 : Int
|
||||
= 0
|
||||
|
||||
# Empty tables require specifying the key and value types:
|
||||
empty_table := {:Text,Int}
|
||||
@ -339,7 +339,7 @@ func demo_enums():
|
||||
= yes
|
||||
|
||||
>> {my_shape="nice"}
|
||||
= {Circle(1)="nice"}
|
||||
= {Shape.Circle(1)="nice"}
|
||||
|
||||
func demo_lambdas():
|
||||
# Lambdas, or anonymous functions, can be used like this:
|
||||
|
@ -165,7 +165,7 @@ CORD ast_to_xml(ast_t *ast)
|
||||
T(Optional, "<Optional>%r</Optional>", ast_to_xml(data.value))
|
||||
T(NonOptional, "<NonOptional>%r</NonOptional>", ast_to_xml(data.value))
|
||||
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(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))
|
||||
|
@ -323,8 +323,7 @@ struct ast_s {
|
||||
Moment_t moment;
|
||||
} Moment;
|
||||
struct {
|
||||
ast_t *expr;
|
||||
const char *output;
|
||||
ast_t *expr, *expected;
|
||||
bool skip_source:1;
|
||||
} DocTest;
|
||||
struct {
|
||||
|
@ -39,7 +39,7 @@ static CORD compile_string_literal(CORD literal);
|
||||
|
||||
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;
|
||||
} else if (t->tag == IntType) {
|
||||
switch (Match(t, IntType)->bits) {
|
||||
@ -878,24 +878,13 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
|
||||
if (!expr_t)
|
||||
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 test_code;
|
||||
if (test->expr->tag == Declare) {
|
||||
auto decl = Match(test->expr, Declare);
|
||||
const char *varname = Match(decl->var, Var)->name;
|
||||
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);
|
||||
type_t *t = get_type(env, decl->value);
|
||||
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;
|
||||
} else {
|
||||
// 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");
|
||||
|
||||
test_code = "({ // Assignment\n";
|
||||
@ -969,12 +958,16 @@ static CORD _compile_statement(env_t *env, ast_t *ast)
|
||||
} else {
|
||||
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(
|
||||
"%rtest(%r, %r, %r, %ld, %ld);",
|
||||
setup, test_code,
|
||||
compile(env, test->expected),
|
||||
compile_type_info(expr_t),
|
||||
compile_string_literal(output),
|
||||
(int64_t)(test->expr->start - test->expr->file->text),
|
||||
(int64_t)(test->expr->end - test->expr->file->text));
|
||||
} 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;
|
||||
switch (target_bits) {
|
||||
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)
|
||||
return CORD_asprintf("I64(%s)", c_literal);
|
||||
return CORD_asprintf("I64(%sL)", c_literal);
|
||||
break;
|
||||
case TYPE_IBITS32:
|
||||
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";
|
||||
else if (t == PATH_TYPE) return "NONE_PATH";
|
||||
else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})";
|
||||
else if (t == MATCH_TYPE) return "NONE_MATCH";
|
||||
|
||||
switch (t->tag) {
|
||||
case BigIntType: return "NONE_INT";
|
||||
|
14
src/parse.c
14
src/parse.c
@ -2418,22 +2418,14 @@ PARSER(parse_doctest) {
|
||||
spaces(&pos);
|
||||
ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest");
|
||||
whitespace(&pos);
|
||||
const char* output = NULL;
|
||||
ast_t *expected = NULL;
|
||||
if (match(&pos, "=")) {
|
||||
spaces(&pos);
|
||||
const char *output_start = pos,
|
||||
*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;
|
||||
expected = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the expected expression here");
|
||||
} else {
|
||||
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) {
|
||||
|
@ -602,29 +602,19 @@ public void end_inspect(const void *expr, const TypeInfo_t *type)
|
||||
}
|
||||
|
||||
__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 type_name = generic_as_text(NULL, false, 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);
|
||||
}
|
||||
}
|
||||
Text_t expected_text = generic_as_text(expected, USE_COLOR, type);
|
||||
|
||||
bool success = Text$equal_values(expr_text, expected_text);
|
||||
if (!success) {
|
||||
print_stack_trace(stderr, 2, 4);
|
||||
fprintf(stderr,
|
||||
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==================== TEST FAILED ====================\n\nYou expected: %s\n But I got: %k\n\n",
|
||||
expected, &expr_text);
|
||||
? "\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: %k\n But I got: %k\n\n",
|
||||
&expected_text, &expr_text);
|
||||
|
||||
fflush(stderr);
|
||||
raise(SIGABRT);
|
||||
|
@ -39,10 +39,11 @@ void end_inspect(const void *expr, const TypeInfo_t *type);
|
||||
end_inspect(&_expr, typeinfo); \
|
||||
}
|
||||
__attribute__((nonnull))
|
||||
void test_value(const void *expr, const TypeInfo_t *type, const char *expected);
|
||||
#define test(expr, typeinfo, expected, start, end) {\
|
||||
void test_value(const void *expr, const void *expected, const TypeInfo_t *type);
|
||||
#define test(expr, expected, typeinfo, start, end) {\
|
||||
auto _expr = expr; \
|
||||
test_value(&_expr, typeinfo, expected); \
|
||||
auto _expected = expected; \
|
||||
test_value(&_expr, &_expected, typeinfo); \
|
||||
}
|
||||
|
||||
void say(Text_t text, bool newline);
|
||||
|
@ -285,6 +285,7 @@ PUREFUNC bool has_heap_memory(type_t *t)
|
||||
|
||||
PUREFUNC bool has_stack_memory(type_t *t)
|
||||
{
|
||||
if (!t) return false;
|
||||
switch (t->tag) {
|
||||
case PointerType: return Match(t, PointerType)->is_stack;
|
||||
case OptionalType: return has_stack_memory(Match(t, OptionalType)->type);
|
||||
|
@ -1,7 +1,7 @@
|
||||
func main():
|
||||
do:
|
||||
>> [:Num32]
|
||||
= [] : [Num32]
|
||||
= [:Num32]
|
||||
|
||||
do:
|
||||
>> arr := [10, 20, 30]
|
||||
@ -160,27 +160,27 @@ func main():
|
||||
= [1, 2, 3, 4, 5]
|
||||
|
||||
>> ["a", "b", "c"]:find("b")
|
||||
= 2 : Int?
|
||||
= 2?
|
||||
>> ["a", "b", "c"]:find("XXX")
|
||||
= none : Int?
|
||||
= none:Int
|
||||
|
||||
>> [10, 20]:first(func(i:&Int): i:is_prime())
|
||||
= none : Int?
|
||||
= none:Int
|
||||
>> [4, 5, 6]:first(func(i:&Int): i:is_prime())
|
||||
= 2 : Int?
|
||||
= 2?
|
||||
|
||||
do:
|
||||
>> nums := &[10, 20, 30, 40, 50]
|
||||
>> nums:pop()
|
||||
= 50
|
||||
= 50?
|
||||
>> nums
|
||||
= &[10, 20, 30, 40]
|
||||
>> nums:pop(2)
|
||||
= 20
|
||||
= 20?
|
||||
>> nums
|
||||
= &[10, 30, 40]
|
||||
>> nums:clear()
|
||||
>> nums
|
||||
= &[]
|
||||
= &[:Int]
|
||||
>> nums:pop()
|
||||
= none
|
||||
= none:Int
|
||||
|
@ -2,10 +2,10 @@
|
||||
func main():
|
||||
!! Test bytes:
|
||||
>> Byte(100)
|
||||
= 0x64
|
||||
= Byte(0x64)
|
||||
|
||||
>> Byte(0xFF)
|
||||
= 0xFF
|
||||
= Byte(0xFF)
|
||||
|
||||
>> b := Byte(0x0F)
|
||||
>> b:hex()
|
||||
|
@ -17,11 +17,11 @@ func choose_text(f:Foo->Text):
|
||||
|
||||
func main():
|
||||
>> Foo.Zero
|
||||
= Zero
|
||||
= Foo.Zero
|
||||
>> Foo.One(123)
|
||||
= One(123)
|
||||
= Foo.One(123)
|
||||
>> Foo.Two(123, 456)
|
||||
= Two(x=123, y=456)
|
||||
= Foo.Two(x=123, y=456)
|
||||
|
||||
>> one := Foo.One(123)
|
||||
>> one.One
|
||||
|
@ -2,4 +2,4 @@ extern sqrt:func(n:Num->Num)
|
||||
|
||||
func main():
|
||||
>> sqrt(4)
|
||||
= 2
|
||||
= 2.
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
func main():
|
||||
>> inline C:Int32 { int x = 1 + 2; x }
|
||||
= 3
|
||||
= Int32(3)
|
||||
|
||||
>> inline C {
|
||||
say(Text("Inline C code works!"), true);
|
||||
|
@ -12,20 +12,20 @@ func main():
|
||||
= 10
|
||||
|
||||
>> Int8(1) + Int16(2)
|
||||
= 3 : Int16
|
||||
= Int16(3)
|
||||
|
||||
>> 1 << 10
|
||||
= 1024
|
||||
|
||||
!! Signed and unsigned bit shifting:
|
||||
>> Int64(-2) << 1
|
||||
= -4 : Int64
|
||||
= Int64(-4)
|
||||
>> Int64(-2) <<< 1
|
||||
= -4 : Int64
|
||||
= Int64(-4)
|
||||
>> Int64(-2) >> 1
|
||||
= -1 : Int64
|
||||
= Int64(-1)
|
||||
>> Int64(-2) >>> 1
|
||||
= 9223372036854775807 : Int64
|
||||
= Int64(9223372036854775807)
|
||||
|
||||
>> 3 and 2
|
||||
= 2
|
||||
@ -51,9 +51,9 @@ func main():
|
||||
= "0o173"
|
||||
|
||||
>> Int64.min
|
||||
= -9223372036854775808
|
||||
= Int64(-9223372036854775808)
|
||||
>> Int64.max
|
||||
= 9223372036854775807
|
||||
= Int64(9223372036854775807)
|
||||
|
||||
|
||||
>> Int32(123):hex()
|
||||
@ -64,7 +64,7 @@ func main():
|
||||
= "0x7B"
|
||||
|
||||
>> Int(2.1, truncate=yes)
|
||||
= 2 : Int
|
||||
= 2
|
||||
|
||||
do:
|
||||
>> small_int := 1
|
||||
@ -127,9 +127,9 @@ func main():
|
||||
= 0
|
||||
|
||||
>> Int64(yes)
|
||||
= 1 : Int64
|
||||
= Int64(1)
|
||||
>> Int64(no)
|
||||
= 0 : Int64
|
||||
= Int64(0)
|
||||
|
||||
>> 4:choose(2)
|
||||
= 6
|
||||
|
@ -21,13 +21,13 @@ func main():
|
||||
= "1 hour ago"
|
||||
|
||||
>> t:seconds_till(t:after(minutes=2))
|
||||
= 120
|
||||
= 120.
|
||||
|
||||
>> t:minutes_till(t:after(minutes=2))
|
||||
= 2
|
||||
= 2.
|
||||
|
||||
>> t:hours_till(t:after(minutes=60))
|
||||
= 1
|
||||
= 1.
|
||||
|
||||
>> t:day_of_week() # 1 = Sun, 2 = Mon, 3 = Tue
|
||||
= 3
|
||||
@ -36,7 +36,7 @@ func main():
|
||||
= "Tuesday"
|
||||
|
||||
>> t:unix_timestamp()
|
||||
= 1704221100 : Int64
|
||||
= Int64(1704221100)
|
||||
>> t == Moment.from_unix_timestamp(1704221100)
|
||||
= yes
|
||||
|
||||
|
24
test/nums.tm
24
test/nums.tm
@ -3,13 +3,13 @@ func main():
|
||||
= 1.5
|
||||
|
||||
>> n + n
|
||||
= 3
|
||||
= 3.
|
||||
|
||||
>> n * 2
|
||||
= 3
|
||||
= 3.
|
||||
|
||||
>> n - n
|
||||
= 0
|
||||
= 0.
|
||||
|
||||
>> Num.PI
|
||||
= 3.141592653589793
|
||||
@ -18,12 +18,12 @@ func main():
|
||||
= "3.1415926536"
|
||||
|
||||
>> Num.INF
|
||||
= inf
|
||||
= Num.INF
|
||||
>> Num.INF:isinf()
|
||||
= yes
|
||||
|
||||
>> nan := none : Num
|
||||
= none : Num?
|
||||
= none:Num
|
||||
>> nan == nan
|
||||
= yes
|
||||
>> nan < nan
|
||||
@ -33,7 +33,7 @@ func main():
|
||||
>> nan != nan
|
||||
= no
|
||||
>> nan <> nan
|
||||
= 0
|
||||
= Int32(0)
|
||||
>> nan == 0.0
|
||||
= no
|
||||
>> nan < 0.0
|
||||
@ -43,13 +43,13 @@ func main():
|
||||
>> nan != 0.0
|
||||
= yes
|
||||
>> nan <> 0.0
|
||||
= -1
|
||||
= Int32(-1)
|
||||
|
||||
>> nan + 1
|
||||
= none : Num?
|
||||
= none:Num
|
||||
|
||||
>> 0./0.
|
||||
= none : Num?
|
||||
= none:Num
|
||||
|
||||
>> Num.PI:cos()!:near(-1)
|
||||
= yes
|
||||
@ -60,15 +60,15 @@ func main():
|
||||
= no
|
||||
|
||||
>> Num32.sqrt(16)
|
||||
= 4 : Num32?
|
||||
= Num32(4)?
|
||||
|
||||
>> 0.25:mix(10, 20)
|
||||
= 12.5
|
||||
>> 2.0:mix(10, 20)
|
||||
= 30
|
||||
= 30.
|
||||
|
||||
>> Num(5)
|
||||
= 5 : Num
|
||||
= 5.
|
||||
|
||||
>> 0.5:percent()
|
||||
= "50%"
|
||||
|
@ -75,32 +75,32 @@ func maybe_mutexed(should_i:Bool->mutexed(Bool)?):
|
||||
|
||||
func main():
|
||||
>> 5?
|
||||
= 5 : Int?
|
||||
= 5?
|
||||
|
||||
>> if no:
|
||||
none:Int
|
||||
else:
|
||||
5
|
||||
= 5 : Int?
|
||||
= 5?
|
||||
|
||||
>> 5? or -1
|
||||
= 5 : Int
|
||||
= 5
|
||||
|
||||
>> 5? or fail("Non-null is falsey")
|
||||
= 5 : Int
|
||||
= 5
|
||||
|
||||
>> 5? or exit("Non-null is falsey")
|
||||
= 5 : Int
|
||||
= 5
|
||||
|
||||
>> (none:Int) or -1
|
||||
= -1 : Int
|
||||
= -1
|
||||
|
||||
do:
|
||||
!! Ints:
|
||||
>> yep := maybe_int(yes)
|
||||
= 123 : Int?
|
||||
= 123?
|
||||
>> nope := maybe_int(no)
|
||||
= none : Int?
|
||||
= none:Int
|
||||
>> if yep:
|
||||
>> yep
|
||||
= 123
|
||||
@ -113,12 +113,12 @@ func main():
|
||||
!! ...
|
||||
!! Int64s:
|
||||
>> yep := maybe_int64(yes)
|
||||
= 123 : Int64?
|
||||
= Int64(123)?
|
||||
>> nope := maybe_int64(no)
|
||||
= none : Int64?
|
||||
= none:Int64
|
||||
>> if yep:
|
||||
>> yep
|
||||
= 123
|
||||
= Int64(123)
|
||||
else: fail("Falsey: $yep")
|
||||
>> if nope:
|
||||
fail("Truthy: $nope")
|
||||
@ -128,9 +128,9 @@ func main():
|
||||
!! ...
|
||||
!! Arrays:
|
||||
>> yep := maybe_array(yes)
|
||||
= [10, 20, 30] : [Int]?
|
||||
= [10, 20, 30]?
|
||||
>> nope := maybe_array(no)
|
||||
= none : [Int]?
|
||||
= none:[Int]
|
||||
>> if yep:
|
||||
>> yep
|
||||
= [10, 20, 30]
|
||||
@ -143,9 +143,9 @@ func main():
|
||||
!! ...
|
||||
!! Bools:
|
||||
>> yep := maybe_bool(yes)
|
||||
= no : Bool?
|
||||
= no?
|
||||
>> nope := maybe_bool(no)
|
||||
= none : Bool?
|
||||
= none:Bool
|
||||
>> if yep:
|
||||
>> yep
|
||||
= no
|
||||
@ -158,9 +158,9 @@ func main():
|
||||
!! ...
|
||||
!! Text:
|
||||
>> yep := maybe_text(yes)
|
||||
= "Hello" : Text?
|
||||
= "Hello"?
|
||||
>> nope := maybe_text(no)
|
||||
= none : Text?
|
||||
= none:Text
|
||||
>> if yep:
|
||||
>> yep
|
||||
= "Hello"
|
||||
@ -173,9 +173,9 @@ func main():
|
||||
!! ...
|
||||
!! Nums:
|
||||
>> yep := maybe_num(yes)
|
||||
= 12.3 : Num?
|
||||
= 12.3?
|
||||
>> nope := maybe_num(no)
|
||||
= none : Num?
|
||||
= none:Num
|
||||
>> if yep:
|
||||
>> yep
|
||||
= 12.3
|
||||
@ -187,14 +187,14 @@ func main():
|
||||
do:
|
||||
!! ...
|
||||
!! Lambdas:
|
||||
>> yep := maybe_lambda(yes)
|
||||
= func() [optionals.tm:54] : func()?
|
||||
# >> yep := maybe_lambda(yes)
|
||||
# = func() [optionals.tm:54] : func()?
|
||||
>> nope := maybe_lambda(no)
|
||||
= none : func()?
|
||||
>> if yep:
|
||||
>> yep
|
||||
= func() [optionals.tm:54]
|
||||
else: fail("Falsey: $yep")
|
||||
= none : func()
|
||||
# >> if yep:
|
||||
# >> yep
|
||||
# = func() [optionals.tm:54]
|
||||
# else: fail("Falsey: $yep")
|
||||
>> if nope:
|
||||
fail("Truthy: $nope")
|
||||
else: !! Falsey: $nope
|
||||
@ -203,9 +203,9 @@ func main():
|
||||
!! ...
|
||||
!! Structs:
|
||||
>> yep := Struct.maybe(yes)
|
||||
= Struct(x=123, y="hello") : Struct?
|
||||
= Struct(x=123, y="hello")?
|
||||
>> nope := Struct.maybe(no)
|
||||
= none : Struct?
|
||||
= none:Struct
|
||||
>> if yep:
|
||||
>> yep
|
||||
= Struct(x=123, y="hello")
|
||||
@ -218,12 +218,12 @@ func main():
|
||||
!! ...
|
||||
!! Enums:
|
||||
>> yep := Enum.maybe(yes)
|
||||
= Y(123) : Enum?
|
||||
= Enum.Y(123)?
|
||||
>> nope := Enum.maybe(no)
|
||||
= none : Enum?
|
||||
= none : Enum
|
||||
>> if yep:
|
||||
>> yep
|
||||
= Y(123)
|
||||
= Enum.Y(123)
|
||||
else: fail("Falsey: $yep")
|
||||
>> if nope:
|
||||
fail("Truthy: $nope")
|
||||
@ -233,9 +233,9 @@ func main():
|
||||
!! ...
|
||||
!! C Strings:
|
||||
>> yep := maybe_c_string(yes)
|
||||
= CString("hi") : CString?
|
||||
= CString("hi")?
|
||||
>> nope := maybe_c_string(no)
|
||||
= none : CString?
|
||||
= none : CString
|
||||
>> if yep:
|
||||
>> yep
|
||||
= CString("hi")
|
||||
@ -250,7 +250,7 @@ func main():
|
||||
>> yep := maybe_thread(yes)
|
||||
# No "=" test here because threads use addresses in the text version
|
||||
>> nope := maybe_thread(no)
|
||||
= none : Thread?
|
||||
= none : Thread
|
||||
>> if yep: >> yep
|
||||
else: fail("Falsey: $yep")
|
||||
>> if nope:
|
||||
@ -263,7 +263,7 @@ func main():
|
||||
>> yep := maybe_mutexed(yes)
|
||||
# No "=" test here because threads use addresses in the text version
|
||||
>> nope := maybe_mutexed(no)
|
||||
= none : mutexed(Bool)?
|
||||
= none : mutexed(Bool)
|
||||
>> if yep: >> yep
|
||||
else: fail("Falsey: $yep")
|
||||
>> if nope:
|
||||
@ -273,11 +273,11 @@ func main():
|
||||
|
||||
if yep := maybe_int(yes):
|
||||
>> yep
|
||||
= 123 : Int
|
||||
= 123
|
||||
else: fail("Unreachable")
|
||||
|
||||
>> maybe_int(yes)!
|
||||
= 123 : Int
|
||||
= 123
|
||||
|
||||
# Test comparisons, hashing, equality:
|
||||
>> (none:Int == 5?)
|
||||
@ -285,11 +285,11 @@ func main():
|
||||
>> (5? == 5?)
|
||||
= yes
|
||||
>> {none:Int, none:Int}
|
||||
= {none}
|
||||
= {none:Int}
|
||||
>> {:Int? none, none}
|
||||
= {none}
|
||||
= {none:Int}
|
||||
>> [5?, none:Int, none:Int, 6?]:sorted()
|
||||
= [none, none, 5, 6]
|
||||
= [none:Int, none:Int, 5, 6]
|
||||
|
||||
do:
|
||||
>> value := if var := 5?:
|
||||
@ -326,7 +326,7 @@ func main():
|
||||
= yes
|
||||
|
||||
>> [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:
|
||||
say("Binary op 'or' works with optionals")
|
||||
|
@ -6,14 +6,14 @@ func main():
|
||||
= yes
|
||||
|
||||
>> (~/Downloads/file(1).txt)
|
||||
= ~/Downloads/file(1).txt
|
||||
= (~/Downloads/file(1).txt)
|
||||
|
||||
>> (/half\)paren)
|
||||
= /half)paren
|
||||
= (/half\)paren)
|
||||
|
||||
>> filename := "example.txt"
|
||||
>> (~):child(filename)
|
||||
= ~/example.txt
|
||||
= (~/example.txt)
|
||||
|
||||
>> tmpdir := (/tmp/tomo-test-path-XXXXXX):unique_directory()
|
||||
>> (/tmp):subdirectories():has(tmpdir)
|
||||
@ -23,9 +23,9 @@ func main():
|
||||
>> tmpfile:write("Hello world")
|
||||
>> tmpfile:append("!")
|
||||
>> tmpfile:read()
|
||||
= "Hello world!" : Text?
|
||||
= "Hello world!"?
|
||||
>> 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)
|
||||
= yes
|
||||
|
||||
@ -36,9 +36,9 @@ func main():
|
||||
fail("Couldn't read lines in $tmpfile")
|
||||
|
||||
>> (./does-not-exist.xxx):read()
|
||||
= none : Text?
|
||||
= none : Text
|
||||
>> (./does-not-exist.xxx):read_bytes()
|
||||
= none : [Byte]?
|
||||
= none : [Byte]
|
||||
if lines := (./does-not-exist.xxx):by_line():
|
||||
fail("I could read lines in a nonexistent file")
|
||||
else:
|
||||
@ -55,7 +55,7 @@ func main():
|
||||
>> p:base_name()
|
||||
= "qux.tar.gz"
|
||||
>> p:parent()
|
||||
= /foo/baz.x
|
||||
= (/foo/baz.x)
|
||||
>> p:extension()
|
||||
= "tar.gz"
|
||||
>> p:extension(full=no)
|
||||
@ -69,62 +69,62 @@ func main():
|
||||
= "baz.qux"
|
||||
|
||||
>> (/):parent()
|
||||
= /
|
||||
= (/)
|
||||
>> (~/x/.):parent()
|
||||
= ~
|
||||
= (~)
|
||||
>> (~/x):parent()
|
||||
= ~
|
||||
= (~)
|
||||
>> (.):parent()
|
||||
= ..
|
||||
= (..)
|
||||
>> (..):parent()
|
||||
= ../..
|
||||
= (../..)
|
||||
>> (../foo):parent()
|
||||
= ..
|
||||
= (..)
|
||||
|
||||
# Concatenation tests:
|
||||
!! Basic relative path concatenation:
|
||||
>> (/foo) ++ (./baz)
|
||||
= /foo/baz
|
||||
= (/foo/baz)
|
||||
|
||||
!! Concatenation with a current directory (`.`):
|
||||
>> (/foo/bar) ++ (./.)
|
||||
= /foo/bar
|
||||
= (/foo/bar)
|
||||
|
||||
!! Trailing slash in the first path:
|
||||
>> (/foo/) ++ (./baz)
|
||||
= /foo/baz
|
||||
= (/foo/baz)
|
||||
|
||||
!! Trailing slash in the second path:
|
||||
>> (/foo/bar) ++ (./baz/)
|
||||
= /foo/bar/baz
|
||||
= (/foo/bar/baz)
|
||||
|
||||
!! Removing redundant current directory (`.`):
|
||||
>> (/foo/bar) ++ (./baz/./qux)
|
||||
= /foo/bar/baz/qux
|
||||
= (/foo/bar/baz/qux)
|
||||
|
||||
!! Removing redundant parent directory (`..`):
|
||||
>> (/foo/bar) ++ (./baz/qux/../quux)
|
||||
= /foo/bar/baz/quux
|
||||
= (/foo/bar/baz/quux)
|
||||
|
||||
!! Collapsing `..` to navigate up:
|
||||
>> (/foo/bar/baz) ++ (../qux)
|
||||
= /foo/bar/qux
|
||||
= (/foo/bar/qux)
|
||||
|
||||
!! Current directory and parent directory mixed:
|
||||
>> (/foo/bar) ++ (././../baz)
|
||||
= /foo/baz
|
||||
= (/foo/baz)
|
||||
|
||||
!! Path begins with a `.`:
|
||||
>> (/foo) ++ (./baz/../qux)
|
||||
= /foo/qux
|
||||
= (/foo/qux)
|
||||
|
||||
!! Multiple slashes:
|
||||
>> (/foo) ++ (./baz//qux)
|
||||
= /foo/baz/qux
|
||||
= (/foo/baz/qux)
|
||||
|
||||
!! Complex path with multiple `.` and `..`:
|
||||
>> (/foo/bar/baz) ++ (./.././qux/./../quux)
|
||||
= /foo/bar/quux
|
||||
= (/foo/bar/quux)
|
||||
|
||||
!! Globbing:
|
||||
>> (./*.tm):glob()
|
||||
|
@ -2,10 +2,10 @@ struct Foo(x,y:Int)
|
||||
|
||||
func main():
|
||||
>> (+: [10, 20, 30])
|
||||
= 60 : Int?
|
||||
= 60?
|
||||
|
||||
>> (+: [:Int])
|
||||
= none : Int?
|
||||
= none : Int
|
||||
|
||||
>> (+: [10, 20, 30]) or 0
|
||||
= 60
|
||||
@ -14,10 +14,10 @@ func main():
|
||||
= 0
|
||||
|
||||
>> (_max_: [3, 5, 2, 1, 4])
|
||||
= 5 : Int?
|
||||
= 5?
|
||||
|
||||
>> (_max_:abs(): [1, -10, 5])
|
||||
= -10 : Int?
|
||||
= -10?
|
||||
|
||||
>> (_max_: [Foo(0, 0), Foo(1, 0), Foo(0, 10)])!
|
||||
= Foo(x=1, y=0)
|
||||
@ -37,7 +37,7 @@ func main():
|
||||
= yes
|
||||
|
||||
>> (<=: [:Int])
|
||||
= none : Bool?
|
||||
= none : Bool
|
||||
|
||||
>> (<=: [5, 4, 3, 2, 1])!
|
||||
= no
|
||||
|
16
test/rng.tm
16
test/rng.tm
@ -12,23 +12,23 @@ func main():
|
||||
>> rng:int(1, 1000)
|
||||
= 921
|
||||
>> rng:int64(1, 1000)
|
||||
= 324 : Int64
|
||||
= Int64(324)
|
||||
>> rng:int32(1, 1000)
|
||||
= 586 : Int32
|
||||
= Int32(586)
|
||||
>> rng:int16(1, 1000)
|
||||
= 453 : Int16
|
||||
= Int16(453)
|
||||
>> rng:int8(1, 100)
|
||||
= 53 : Int8
|
||||
= Int8(53)
|
||||
>> rng:byte()
|
||||
= 0xDC : Byte
|
||||
= Byte(0xDC)
|
||||
>> 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)
|
||||
= yes
|
||||
>> rng:num()
|
||||
= 0.03492503353647658 : Num
|
||||
= 0.03492503353647658
|
||||
>> rng:num32(1, 1000)
|
||||
= 761.05908 : Num32
|
||||
= Num32(761.05908)
|
||||
|
||||
!! Random array methods:
|
||||
>> nums := [10*i for i in 10]
|
||||
|
@ -67,7 +67,7 @@ func main():
|
||||
>> obj.next = @Foo("abcdef", next=obj)
|
||||
>> bytes := obj:serialized()
|
||||
>> deserialize(bytes -> @Foo)
|
||||
= @Foo(name="root", next=@Foo(name="abcdef", next=@~1))
|
||||
# = @Foo(name="root", next=@Foo(name="abcdef", next=@~1))
|
||||
|
||||
do:
|
||||
>> obj := MyEnum.Two(123, "OKAY")
|
||||
|
@ -60,7 +60,7 @@ func test_text():
|
||||
>> a := @CorecursiveA(b)
|
||||
>> b.other = a
|
||||
>> a
|
||||
= @CorecursiveA(@CorecursiveB(@~1))
|
||||
# = @CorecursiveA(@CorecursiveB(@~1))
|
||||
|
||||
func main():
|
||||
test_literals()
|
||||
@ -71,9 +71,11 @@ func main():
|
||||
>> @LinkedList(10, @LinkedList(20))
|
||||
|
||||
>> my_pass := Password("Swordfish")
|
||||
= Password(...)
|
||||
>> "$my_pass"
|
||||
= "Password(...)"
|
||||
>> 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]!
|
||||
= "User1"
|
||||
|
||||
|
@ -3,11 +3,11 @@ func main():
|
||||
= {"one"=1, "two"=2}
|
||||
|
||||
>> t["one"]
|
||||
= 1 : Int?
|
||||
= 1?
|
||||
>> t["two"]
|
||||
= 2 : Int?
|
||||
= 2?
|
||||
>> t["???"]
|
||||
= none : Int?
|
||||
= none:Int
|
||||
>> t["one"]!
|
||||
= 1
|
||||
>> t["???"] or -1
|
||||
@ -22,7 +22,7 @@ func main():
|
||||
>> t.length
|
||||
= 2
|
||||
>> t.fallback
|
||||
= none : {Text,Int}?
|
||||
= none : {Text,Int}
|
||||
|
||||
>> t.keys
|
||||
= ["one", "two"]
|
||||
@ -33,16 +33,16 @@ func main():
|
||||
= {"three"=3; fallback={"one"=1, "two"=2}}
|
||||
|
||||
>> t2["one"]
|
||||
= 1 : Int?
|
||||
= 1?
|
||||
>> t2["three"]
|
||||
= 3 : Int?
|
||||
= 3?
|
||||
>> t2["???"]
|
||||
= none : Int?
|
||||
= none:Int
|
||||
|
||||
>> t2.length
|
||||
= 1
|
||||
>> t2.fallback
|
||||
= {"one"=1, "two"=2} : {Text,Int}?
|
||||
= {"one"=1, "two"=2}?
|
||||
|
||||
t2_str := ""
|
||||
for k,v in t2:
|
||||
@ -95,11 +95,11 @@ func main():
|
||||
= no
|
||||
|
||||
>> {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()
|
||||
= [{}, {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()
|
||||
= [{}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]
|
||||
= [{:Int}, {0, 3}, {1}, {1, 2}, {2}, {99}, {99}]
|
||||
|
||||
|
||||
|
52
test/text.tm
52
test/text.tm
@ -51,23 +51,23 @@ func main():
|
||||
|
||||
amelie := "Am$(\UE9)lie"
|
||||
>> amelie:split()
|
||||
= ["A", "m", "é", "l", "i", "e"] : [Text]
|
||||
= ["A", "m", "é", "l", "i", "e"]
|
||||
>> amelie:utf32_codepoints()
|
||||
= [65, 109, 233, 108, 105, 101]
|
||||
= [:Int32, 65, 109, 233, 108, 105, 101]
|
||||
>> 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])!
|
||||
= "Amélie"
|
||||
>> Text.from_bytes([Byte(0xFF)])
|
||||
= none : Text?
|
||||
= none:Text
|
||||
|
||||
amelie2 := "Am$(\U65\U301)lie"
|
||||
>> amelie2:split()
|
||||
= ["A", "m", "é", "l", "i", "e"] : [Text]
|
||||
= ["A", "m", "é", "l", "i", "e"]
|
||||
>> amelie2:utf32_codepoints()
|
||||
= [65, 109, 233, 108, 105, 101]
|
||||
= [:Int32, 65, 109, 233, 108, 105, 101]
|
||||
>> amelie2:bytes()
|
||||
= [0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
= [:Byte, 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65]
|
||||
|
||||
>> 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"]
|
||||
@ -162,7 +162,7 @@ func main():
|
||||
>> "one$(\r\n)two$(\r\n)three$(\r\n)":lines()
|
||||
= ["one", "two", "three"]
|
||||
>> "":lines()
|
||||
= []
|
||||
= [:Text]
|
||||
|
||||
!! Test splitting and joining text:
|
||||
>> "one two three":split($/ /)
|
||||
@ -190,7 +190,7 @@ func main():
|
||||
= ""
|
||||
|
||||
>> "":split()
|
||||
= []
|
||||
= [:Text]
|
||||
|
||||
!! Test text:find_all()
|
||||
>> " #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"])]
|
||||
|
||||
>> " ":find_all($/{alpha}/)
|
||||
= []
|
||||
= [:Match]
|
||||
|
||||
>> " 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", ""])]
|
||||
|
||||
>> "":find_all($Pattern'')
|
||||
= []
|
||||
= [:Match]
|
||||
|
||||
>> "Hello":find_all($Pattern'')
|
||||
= []
|
||||
= [:Match]
|
||||
|
||||
!! Test text:find()
|
||||
>> " one two three ":find($/{id}/, start=-999)
|
||||
= none : Match?
|
||||
= none : Match
|
||||
>> " one two three ":find($/{id}/, start=999)
|
||||
= none : Match?
|
||||
= none : Match
|
||||
>> " 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)
|
||||
= Match(text="two", index=8, captures=["two"]) : Match?
|
||||
= Match(text="two", index=8, captures=["two"])?
|
||||
|
||||
!! Test text slicing:
|
||||
>> "abcdef":slice()
|
||||
@ -240,13 +240,13 @@ func main():
|
||||
>> house:codepoint_names()
|
||||
= ["CJK Unified Ideographs-5BB6"]
|
||||
>> house:utf32_codepoints()
|
||||
= [23478]
|
||||
= [:Int32, 23478]
|
||||
|
||||
>> "🐧":codepoint_names()
|
||||
= ["PENGUIN"]
|
||||
|
||||
>> Text.from_codepoint_names(["not a valid name here buddy"])
|
||||
= none : Text?
|
||||
= none : Text
|
||||
|
||||
>> "one two; three four":find_all($/; {..}/)
|
||||
= [Match(text="; three four", index=8, captures=["three four"])]
|
||||
@ -271,13 +271,13 @@ func main():
|
||||
= " good(x, fn(y), BAD(z), w) "
|
||||
|
||||
>> "Hello":matches($/{id}/)
|
||||
= ["Hello"] : [Text]?
|
||||
= ["Hello"]?
|
||||
>> "Hello":matches($/{lower}/)
|
||||
= none : [Text]?
|
||||
= none : [Text]
|
||||
>> "Hello":matches($/{upper}/)
|
||||
= none : [Text]?
|
||||
= none : [Text]
|
||||
>> "Hello...":matches($/{id}/)
|
||||
= none : [Text]?
|
||||
= none : [Text]
|
||||
|
||||
if matches := "hello world":matches($/{id} {id}/):
|
||||
>> matches
|
||||
@ -317,7 +317,7 @@ func main():
|
||||
= no
|
||||
|
||||
>> ("hello" ++ " " ++ "Amélie"):reversed()
|
||||
= "eilémA olleh" : Text
|
||||
= "eilémA olleh"
|
||||
|
||||
do:
|
||||
!! Testing concatenation-stability:
|
||||
@ -369,11 +369,11 @@ func main():
|
||||
>> "x":middle_pad(5)
|
||||
= " x "
|
||||
>> "1234":left_pad(8, "XYZ")
|
||||
= "XYZX1234" : Text
|
||||
= "XYZX1234"
|
||||
>> "1234":right_pad(8, "XYZ")
|
||||
= "1234XYZX" : Text
|
||||
= "1234XYZX"
|
||||
>> "1234":middle_pad(9, "XYZ")
|
||||
= "XY1234XYZ" : Text
|
||||
= "XY1234XYZ"
|
||||
|
||||
>> amelie:width()
|
||||
= 6
|
||||
|
@ -13,4 +13,4 @@ func main():
|
||||
>> when n is 1: Int64(1)
|
||||
is 2: Int64(2)
|
||||
is 21 + 2: Int64(23)
|
||||
= 23 : Int64?
|
||||
= Int64(23)?
|
||||
|
Loading…
Reference in New Issue
Block a user