NODE(sl_node_var_t, var) { size_t frame; sl_compile_state_t* xcs = cs; size_t index = 0xCAFE; SLVAL err; frame = 0; while(xcs) { if(sl_st_lookup(xcs->vars, (sl_st_data_t)node->name, (sl_st_data_t*)&index)) { if(frame == 0) { op_mov(cs, index, dest); } else { op_get_outer(cs, frame, index, dest); mark_upper_scopes_as_closure_unsafe(cs, frame); } return; } xcs = xcs->parent; frame++; } err = sl_make_formatted_string(cs->vm, "Undefined variable %QV", node->name); err = sl_make_error2(cs->vm, cs->vm->lib.NameError, err); sl_error_add_frame(cs->vm, err, sl_make_cstring(cs->vm, "<compiler>"), sl_make_cstring(cs->vm, (char*)cs->section->filename), sl_make_int(cs->vm, node->base.line)); sl_throw(cs->vm, err); }
static void error(sl_parse_state_t* ps, SLVAL err, sl_token_t* tok) { err = sl_make_error2(ps->vm, ps->vm->lib.SyntaxError, err); sl_error_add_frame(ps->vm, err, sl_make_cstring(ps->vm, "<parser>"), sl_make_cstring(ps->vm, (char*)ps->filename), sl_make_int(ps->vm, tok->line)); sl_throw(ps->vm, err); }
SLVAL sl_float_to_s(sl_vm_t* vm, SLVAL self) { double d = sl_get_float(vm, self); if(isnan(d)) { return sl_make_cstring(vm, "NaN"); } else if(!isfinite(d)) { if(d > 0) { return sl_make_cstring(vm, "Infinity"); } else { return sl_make_cstring(vm, "-Infinity"); } } char buff[1024]; sprintf(buff, "%f", d); for(size_t i = strlen(buff) - 1; i > 0; i--) { if(buff[i] != '0' || (i > 0 && buff[i - 1] == '.')) { break; } buff[i] = 0; } return sl_make_cstring(vm, buff); }
void sl_parse_error(sl_parse_state_t* ps, char* message) { SLVAL err = sl_make_error2(ps->vm, ps->vm->lib.SyntaxError, sl_make_cstring(ps->vm, message)); sl_error_add_frame(ps->vm, err, sl_make_cstring(ps->vm, "<parser>"), sl_make_cstring(ps->vm, (char*)ps->filename), sl_make_int(ps->vm, ps->line)); sl_throw(ps->vm, err); }
void sl_lex_error(sl_lex_state_t* st, char* text, int lineno) { SLVAL msg = sl_make_formatted_string(st->vm, "Unexpected character '%s'", text); SLVAL err = sl_make_error2(st->vm, st->vm->lib.SyntaxError, msg); sl_error_add_frame(st->vm, err, sl_make_cstring(st->vm, "<lexer>"), sl_make_cstring(st->vm, (char*)st->filename), sl_make_int(st->vm, lineno)); sl_throw(st->vm, err); }
static SLVAL sl_gcrypt_algorithm_inspect(sl_vm_t* vm, SLVAL self) { gcrypt_algorithm_t* algo = get_algo(vm, self); SLVAL str = sl_make_cstring(vm, "#<GCrypt::Algorithm: "); str = sl_string_concat(vm, str, algo->name); str = sl_string_concat(vm, str, sl_make_cstring(vm, ">")); return str; }
static SLVAL request_safe_method(sl_vm_t* vm) { if(sl_eq(vm, request(vm)->method, sl_make_cstring(vm, "GET"))) { return vm->lib._true; } if(sl_eq(vm, request(vm)->method, sl_make_cstring(vm, "HEAD"))) { return vm->lib._true; } return vm->lib._false; }
static void unexpected(sl_parse_state_t* ps, sl_token_t* tok) { SLVAL err; if(tok->type != SL_TOK_END) { err = sl_make_cstring(ps->vm, "Unexpected '"); err = sl_string_concat(ps->vm, err, tok->str); err = sl_string_concat(ps->vm, err, sl_make_cstring(ps->vm, "'")); } else { err = sl_make_cstring(ps->vm, "Unexpected end of file"); } error(ps, err, tok); }
SLVAL sl_do_file(sl_vm_t* vm, char* filename) { filename = sl_realpath(vm, filename); FILE* f = fopen(filename, "rb"); uint8_t* src; size_t file_size; SLVAL err; if(!f) { err = sl_make_cstring(vm, "Could not load file: "); err = sl_string_concat(vm, err, sl_make_cstring(vm, filename)); err = sl_string_concat(vm, err, sl_make_cstring(vm, " - ")); err = sl_string_concat(vm, err, sl_make_cstring(vm, strerror(errno))); sl_throw(vm, sl_make_error2(vm, vm->lib.Error, err)); } fseek(f, 0, SEEK_END); file_size = ftell(f); fseek(f, 0, SEEK_SET); src = sl_alloc(vm->arena, file_size); if(file_size && !fread(src, file_size, 1, f)) { fclose(f); err = sl_make_cstring(vm, "Could not load file: "); err = sl_string_concat(vm, err, sl_make_cstring(vm, filename)); err = sl_string_concat(vm, err, sl_make_cstring(vm, " - ")); err = sl_string_concat(vm, err, sl_make_cstring(vm, strerror(errno))); sl_throw(vm, sl_make_error2(vm, vm->lib.Error, err)); } fclose(f); return sl_do_string(vm, src, file_size, filename, 0); }
static SLVAL response_set_cookie(sl_vm_t* vm, SLVAL self, SLVAL name, SLVAL value) { SLVAL header; sl_expect(vm, name, vm->lib.String); sl_expect(vm, value, vm->lib.String); name = sl_string_url_encode(vm, name); value = sl_string_url_encode(vm, value); header = name; header = sl_string_concat(vm, header, sl_make_cstring(vm, "=")); header = sl_string_concat(vm, header, value); return response_set_header(vm, self, sl_make_cstring(vm, "Set-Cookie"), header); }
void sl_init_regexp(sl_vm_t* vm) { vm->lib.Regexp = sl_define_class(vm, "Regexp", vm->lib.Object); sl_class_set_allocator(vm, vm->lib.Regexp, allocate_regexp); vm->lib.Regexp_Match = sl_define_class3(vm, sl_intern(vm, "Match"), vm->lib.Object, vm->lib.Regexp); sl_class_set_allocator(vm, vm->lib.Regexp_Match, allocate_regexp_match); sl_define_method(vm, vm->lib.Regexp, "init", -2, sl_regexp_init); /* sl_define_method(vm, vm->lib.Regexp, "compile", 0, sl_regexp_compile); */ sl_define_method(vm, vm->lib.Regexp, "match", -2, sl_regexp_match); sl_define_method(vm, vm->lib.Regexp, "source", 0, sl_regexp_source); sl_define_method(vm, vm->lib.Regexp, "options", 0, sl_regexp_options); sl_define_method(vm, vm->lib.Regexp, "==", 1, sl_regexp_eq); sl_class_set_const(vm, vm->lib.Regexp, "CASELESS", sl_make_int(vm, PCRE_CASELESS)); sl_class_set_const(vm, vm->lib.Regexp, "EXTENDED", sl_make_int(vm, PCRE_EXTENDED)); sl_class_set_const(vm, vm->lib.Regexp, "PCRE_VERSION", sl_make_cstring(vm, pcre_version())); sl_define_method(vm, vm->lib.Regexp_Match, "regexp", 0, sl_regexp_match_regexp); sl_define_method(vm, vm->lib.Regexp_Match, "[]", 1, sl_regexp_match_index); sl_define_method(vm, vm->lib.Regexp_Match, "byte_offset", 1, sl_regexp_match_byte_offset); sl_define_method(vm, vm->lib.Regexp_Match, "offset", 1, sl_regexp_match_offset); sl_define_method(vm, vm->lib.Regexp_Match, "capture", 1, sl_regexp_match_capture); sl_define_method(vm, vm->lib.Regexp_Match, "length", 0, sl_regexp_match_length); sl_define_method(vm, vm->lib.Regexp_Match, "before", 0, sl_regexp_match_before); sl_define_method(vm, vm->lib.Regexp_Match, "after", 0, sl_regexp_match_after); }
NODE(sl_node_lambda_t, lambda) { sl_vm_insn_t insn; sl_compile_state_t sub_cs; size_t i; init_compile_state(&sub_cs, cs->vm, cs, node->arg_count + 1); for(i = 0; i < node->arg_count; i++) { st_insert(sub_cs.vars, (st_data_t)node->args[i], (st_data_t)(i + 1)); } sub_cs.section->req_registers = node->arg_count; sub_cs.section->arg_registers = node->arg_count; sub_cs.section->name = sl_make_cstring(cs->vm, "<lambda>"); compile_node(&sub_cs, node->body, 0); insn.opcode = SL_OP_RETURN; emit(&sub_cs, insn); insn.uint = 0; emit(&sub_cs, insn); insn.opcode = SL_OP_LAMBDA; emit(cs, insn); insn.section = sub_cs.section; emit(cs, insn); insn.uint = dest; emit(cs, insn); }
static SLVAL sl_string_replace(sl_vm_t* vm, SLVAL self, SLVAL search, SLVAL replace) { if(sl_is_a(vm, search, vm->lib.String)) { return sl_enumerable_join(vm, sl_string_split(vm, self, 1, &search), 1, &replace); } sl_expect(vm, search, vm->lib.Regexp); SLVAL retn = sl_make_cstring(vm, ""); while(1) { SLVAL match = sl_regexp_match(vm, search, 1, &self); if(!sl_is_truthy(match)) { return sl_string_concat(vm, retn, self); } else { SLVAL part = sl_regexp_match_before(vm, match); if(sl_is_a(vm, replace, vm->lib.String)) { part = sl_string_concat(vm, part, replace); } else { part = sl_string_concat(vm, part, sl_send_id(vm, replace, vm->id.call, 1, match)); } retn = sl_string_concat(vm, retn, part); } self = sl_regexp_match_after(vm, match); } }
static void emit_assignment(sl_compile_state_t* cs, sl_node_base_t* lval, size_t reg) { sl_node_assign_var_t a_var; sl_node_send_t send; size_t dest_reg = reg_alloc(cs); sl_node__register_t node; node.base.type = SL_NODE__REGISTER; node.base.line = 0; node.reg = reg; switch(lval->type) { case SL_NODE_VAR: a_var.base.type = SL_NODE_ASSIGN_VAR; break; case SL_NODE_IVAR: a_var.base.type = SL_NODE_ASSIGN_IVAR; break; case SL_NODE_CVAR: a_var.base.type = SL_NODE_ASSIGN_CVAR; break; case SL_NODE_CONST: a_var.base.type = SL_NODE_ASSIGN_CONST; break; case SL_NODE_ARRAY: a_var.base.type = SL_NODE_ASSIGN_ARRAY; break; case SL_NODE_SEND: /* special case that turns a.b = 1 into a.send("b=", 1) */ /* this is separate to the other method of handling send assignments which also handles compound assignments. */ memcpy(&send, lval, sizeof(sl_node_send_t)); send.id = sl_id_make_setter(cs->vm, send.id); sl_node_base_t** args = sl_alloc(cs->vm->arena, sizeof(sl_node_base_t*) * (send.arg_count + 1)); memcpy(args, send.args, sizeof(sl_node_base_t*) * send.arg_count); args[send.arg_count++] = (sl_node_base_t*)&node; send.args = args; compile_node(cs, (sl_node_base_t*)&send, dest_reg); reg_free(cs, dest_reg); return; default: { SLVAL err = sl_make_cstring(cs->vm, "Invalid lval in assignment"); err = sl_make_error2(cs->vm, cs->vm->lib.SyntaxError, err); sl_error_add_frame(cs->vm, err, sl_make_cstring(cs->vm, "<compiler>"), sl_make_cstring(cs->vm, (char*)cs->section->filename), sl_make_int(cs->vm, lval->line)); sl_throw(cs->vm, err); } } a_var.base.line = 0; a_var.lval = (void*)lval; a_var.rval = (sl_node_base_t*)&node; compile_node(cs, (sl_node_base_t*)&a_var, dest_reg); reg_free(cs, dest_reg); }
static sl_object_t* allocate_gcrypt_algorithm(sl_vm_t* vm) { gcrypt_algorithm_t* algo = sl_alloc(vm->arena, sizeof(gcrypt_algorithm_t)); algo->algo = 0; algo->name = sl_make_cstring(vm, "(Invalid)"); return (sl_object_t*)algo; }
static SLVAL response_redirect(sl_vm_t* vm, SLVAL self, SLVAL url) { response(vm)->status = 302; response_set_header(vm, self, sl_make_cstring(vm, "Location"), url); sl_exit(vm, sl_make_int(vm, 0)); return self; /* never reached */ }
static void make_algorithm_object(sl_vm_t* vm, SLVAL GCrypt, SLVAL Algorithm, char* name, int algo) { SLVAL obj = sl_allocate(vm, Algorithm); gcrypt_algorithm_t* ptr = (gcrypt_algorithm_t*)sl_get_ptr(obj); ptr->name = sl_make_cstring(vm, name); ptr->algo = algo; sl_class_set_const2(vm, GCrypt, ptr->name, obj); }
static SLVAL sl_new_oserror(sl_vm_t* vm, int errno) { char* description = strerror(errno); SLVAL Posix_OSError = vm->store[cPosix_OSError]; SLVAL error = sl_make_error2(vm, Posix_OSError, sl_make_cstring(vm, description)); sl_set_ivar(vm, error, sl_intern(vm, "errno"), sl_make_int(vm, errno)); return error; }
static SLVAL string_range_index(sl_vm_t* vm, SLVAL self, SLVAL range) { sl_string_t* str = sl_get_string(vm, self); SLVAL lowerv = sl_range_lower(vm, range); SLVAL upperv = sl_range_upper(vm, range); if(!sl_is_a(vm, lowerv, vm->lib.Int) || !sl_is_a(vm, upperv, vm->lib.Int)) { sl_throw_message2(vm, vm->lib.TypeError, "Expected range of integers"); } long lower = sl_get_int(lowerv), upper = sl_get_int(upperv); if(lower < 0) { lower += str->char_len; } if(lower < 0 || (size_t)lower >= str->char_len) { return sl_make_cstring(vm, ""); } if(upper < 0) { upper += str->char_len; } if(upper < 0) { return sl_make_cstring(vm, ""); } if(sl_range_is_exclusive(vm, range)) { upper--; } if(upper < lower) { return sl_make_cstring(vm, ""); } uint8_t* begin_ptr = str->buff; uint8_t* end_ptr; size_t len = str->buff_len; long idx = 0; while(idx < lower && len) { idx++; sl_utf8_each_char(vm, &begin_ptr, &len); } end_ptr = begin_ptr; while(lower <= upper) { lower++; sl_utf8_each_char(vm, &end_ptr, &len); } return sl_make_string(vm, begin_ptr, (size_t)end_ptr - (size_t)begin_ptr); }
void sl_request_set_opts(sl_vm_t* vm, sl_request_opts_t* opts) { size_t i; SLVAL n, v, cookies; sl_string_t* str; sl_request_internal_opts_t* req = sl_alloc(vm->arena, sizeof(sl_request_internal_opts_t)); req->method = sl_make_cstring(vm, opts->method); req->uri = sl_make_cstring(vm, opts->uri); req->path_info = sl_make_cstring(vm, opts->path_info ? opts->path_info : ""); req->query_string = sl_make_cstring(vm, opts->query_string ? opts->query_string : ""); req->remote_addr = sl_make_cstring(vm, opts->remote_addr); req->headers = sl_make_dict(vm, 0, NULL); req->env = sl_make_dict(vm, 0, NULL); req->get = sl_make_dict(vm, 0, NULL); req->post = sl_make_dict(vm, 0, NULL); req->post_data = sl_make_string(vm, (uint8_t*)opts->post_data, opts->post_length); req->cookies = sl_make_dict(vm, 0, NULL); for(i = 0; i < opts->header_count; i++) { n = sl_make_cstring(vm, opts->headers[i].name); v = sl_make_cstring(vm, opts->headers[i].value); sl_dict_set(vm, req->headers, n, v); } for(i = 0; i < opts->env_count; i++) { n = sl_make_cstring(vm, opts->env[i].name); v = sl_make_cstring(vm, opts->env[i].value); sl_dict_set(vm, req->env, n, v); } if(opts->query_string) { parse_query_string(vm, req->get, strlen(opts->query_string), (uint8_t*)opts->query_string); } if(opts->content_type && strcmp(opts->content_type, "application/x-www-form-urlencoded") == 0) { parse_query_string(vm, req->post, opts->post_length, (uint8_t*)opts->post_data); } cookies = sl_dict_get(vm, req->headers, sl_make_cstring(vm, "Cookie")); if(sl_is_a(vm, cookies, vm->lib.String)) { str = (sl_string_t*)sl_get_ptr(cookies); parse_cookie_string(vm, req->cookies, str->buff_len, str->buff); } req->params = sl_dict_merge(vm, req->get, req->post); sl_vm_store_put(vm, &Request_opts, sl_make_ptr((sl_object_t*)req)); }
static void unexpected(sl_parse_state_t* ps, sl_token_t* tok) { SLVAL err; if(tok->type != SL_TOK_END) { err = sl_make_formatted_string(ps->vm, "Unexpected %QV", tok->str); } else { err = sl_make_cstring(ps->vm, "Unexpected end of file"); } error(ps, err, tok); }
static void process_arguments(sl_vm_t* vm, int argc, char** argv) { int i = 1; for(; i < argc; i++) { if(strcmp(argv[i], "-I") == 0) { if(i + 1 == argc) { fprintf(stderr, "Expected <path> after -I\n"); shutdown_vm(vm, 1); } sl_require_path_prepend(vm, argv[++i]); continue; } if(strcmp(argv[i], "-i") == 0) { opt_interactive = true; continue; } if(strcmp(argv[i], "-h") == 0) { print_help(argv[0]); shutdown_vm(vm, 0); } if(strcmp(argv[i], "--") == 0) { i++; break; } if(argv[i][0] == '-') { fprintf(stderr, "Unknown option '%s'\n", argv[i]); shutdown_vm(vm, 1); } break; } if(!opt_interactive) { if(i == argc || strcmp(argv[i], "-") == 0) { opt_source_file = stdin; opt_source_file_name = "(stdin)"; } else { opt_source_file = fopen(argv[i], "rb"); opt_source_file_name = argv[i]; if(!opt_source_file) { perror("Could not open source file"); shutdown_vm(vm, 1); } } i++; } SLVAL vargv = sl_make_array(vm, 0, NULL); for(; i < argc; i++) { SLVAL varg = sl_make_cstring(vm, argv[i]); sl_array_push(vm, vargv, 1, &varg); } sl_class_set_const(vm, vm->lib.Object, "ARGV", vargv); }
SLVAL sl_enumerable_join(sl_vm_t* vm, SLVAL self, size_t argc, SLVAL* argv) { SLVAL enumerator = sl_send(vm, self, "enumerate", 0); SLVAL joiner, val, str; if(argc) { joiner = sl_to_s(vm, argv[0]); } else { joiner = sl_make_cstring(vm, ""); } if(!sl_is_truthy(sl_send(vm, enumerator, "next", 0))) { return sl_make_cstring(vm, ""); } str = sl_to_s(vm, sl_send(vm, enumerator, "current", 0)); while(sl_is_truthy(sl_send(vm, enumerator, "next", 0))) { val = sl_send(vm, enumerator, "current", 0); val = sl_to_s(vm, val); str = sl_string_concat(vm, str, joiner); str = sl_string_concat(vm, str, val); } return str; }
static sl_node_base_t* use_expression(sl_parse_state_t* ps) { expect_token(ps, SL_TOK_USE); sl_node_base_t* node = call_expression(ps); if(node->type != SL_NODE_CONST) { error(ps, sl_make_cstring(ps->vm, "Expected constant reference in use expression"), token(ps)); } for(sl_node_base_t* i = node; i && i->type == SL_NODE_CONST; i = ((sl_node_const_t*)i)->obj) { i->type = SL_NODE_USE; } return node; }
NODE(sl_node_var_t, var) { sl_vm_insn_t insn; size_t frame; sl_compile_state_t* xcs = cs; size_t index = 0xCAFE; SLVAL err; frame = 0; while(xcs) { if(st_lookup(xcs->vars, (st_data_t)node->name, (st_data_t*)&index)) { if(frame == 0) { insn.opcode = SL_OP_MOV; emit(cs, insn); } else { insn.opcode = SL_OP_GET_OUTER; emit(cs, insn); insn.uint = frame; emit(cs, insn); mark_upper_scopes_as_closure_unsafe(cs, frame); } insn.uint = index; emit(cs, insn); insn.uint = dest; emit(cs, insn); return; } xcs = xcs->parent; frame++; } err = sl_make_cstring(cs->vm, "Undefined variable '"); err = sl_string_concat(cs->vm, err, sl_make_ptr((sl_object_t*)node->name)); err = sl_string_concat(cs->vm, err, sl_make_cstring(cs->vm, "' ")); err = sl_make_error2(cs->vm, cs->vm->lib.NameError, err); sl_error_add_frame(cs->vm, err, sl_make_cstring(cs->vm, "<compiler>"), sl_make_cstring(cs->vm, (char*)cs->section->filename), sl_make_int(cs->vm, node->base.line)); sl_throw(cs->vm, err); }
static void load_cgi_options(sl_vm_t* vm, cgi_options* options) { int i = 0; for(; options->incpaths && i < options->incpaths_count; i++) { sl_require_path_prepend(vm, options->incpaths[i]); } SLVAL vargv = sl_make_array(vm, 0, NULL); for(i = 0; i < options->arg_script_options_count; i++) { SLVAL varg = sl_make_cstring(vm, options->arg_script_options[i]); sl_array_push(vm, vargv, 1, &varg); } sl_class_set_const(vm, vm->lib.Object, "ARGV", vargv); }
static SLVAL sl_class_to_s(sl_vm_t* vm, SLVAL self) { sl_class_t* klass = get_class(vm, self); sl_class_t* object = (sl_class_t*)sl_get_ptr(vm->lib.Object); if(klass == object || sl_get_ptr(klass->in) == (sl_object_t*)object) { return get_class(vm, self)->name; } else { return sl_string_concat(vm, sl_class_to_s(vm, klass->in), sl_string_concat(vm, sl_make_cstring(vm, "::"), klass->name)); } }
static sl_node_base_t* unary_expression(sl_parse_state_t* ps) { sl_node_base_t* expr; sl_token_t* tok; switch(peek_token(ps)->type) { case SL_TOK_MINUS: tok = next_token(ps); expr = unary_expression(ps); return sl_make_send_node(ps, expr, sl_intern(ps->vm, "negate"), 0, NULL); case SL_TOK_TILDE: tok = next_token(ps); expr = unary_expression(ps); return sl_make_send_node(ps, expr, sl_intern2(ps->vm, tok->str), 0, NULL); case SL_TOK_NOT: next_token(ps); expr = unary_expression(ps); return sl_make_unary_node(ps, expr, SL_NODE_NOT); case SL_TOK_RETURN: tok = next_token(ps); if(!(ps->scope->flags & SL_PF_CAN_RETURN)) { error(ps, sl_make_cstring(ps->vm, "Can't return outside of a method or lambda"), tok); } switch(peek_token(ps)->type) { case SL_TOK_SEMICOLON: case SL_TOK_CLOSE_BRACE: case SL_TOK_CLOSE_TAG: /* in these case we want to allow for postfix control structures: */ case SL_TOK_IF: case SL_TOK_UNLESS: return sl_make_unary_node(ps, sl_make_immediate_node(ps, ps->vm->lib.nil), SL_NODE_RETURN); default: return sl_make_unary_node(ps, low_precedence_logical_expression(ps), SL_NODE_RETURN); } break; case SL_TOK_THROW: next_token(ps); return sl_make_unary_node(ps, low_precedence_logical_expression(ps), SL_NODE_THROW); case SL_TOK_USE: return use_expression(ps); default: return power_expression(ps); } }
static void sl_json_parse_check_error(sl_vm_t* vm, sl_string_t* str, json_parse_t* json, yajl_status status) { uint8_t* err_str; SLVAL err; if(status == yajl_status_client_canceled) { /* the only reason we'd cancel the parse is if the data structre is too deep */ yajl_free(json->yajl); sl_throw_message2(vm, vm->store[cJSON_ParseError], "JSON structure recurses too deep"); } if(status == yajl_status_error) { err_str = yajl_get_error(json->yajl, 0, (const uint8_t*)str->buff, (size_t)str->buff_len); err = sl_make_cstring(vm, (char*)err_str); yajl_free_error(json->yajl, err_str); yajl_free(json->yajl); sl_throw(vm, sl_make_error2(vm, vm->store[cJSON_ParseError], err)); } }
static SLVAL ordinalize(sl_vm_t* vm, int rem) { char* suffix; switch(rem) { case 1: suffix = "st"; break; case 2: suffix = "nd"; break; case 3: suffix = "rd"; break; default: suffix = "th"; break; } return sl_make_cstring(vm, suffix); }