Switch to langs using constructors
This commit is contained in:
parent
29849d1457
commit
058a028aef
@ -4082,6 +4082,8 @@ CORD compile_function(env_t *env, ast_t *ast, CORD *staticdefs)
|
||||
auto fndef = Match(ast, FunctionDef);
|
||||
bool is_private = Match(fndef->name, Var)->name[0] == '_';
|
||||
CORD name = compile(env, fndef->name);
|
||||
if (env->namespace && env->namespace->parent && env->namespace->name && streq(Match(fndef->name, Var)->name, env->namespace->name))
|
||||
name = CORD_asprintf("%r$%ld", name, get_line_number(ast->file, ast->start));
|
||||
type_t *ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType);
|
||||
|
||||
CORD arg_signature = "(";
|
||||
@ -4485,7 +4487,10 @@ CORD compile_statement_namespace_header(env_t *env, ast_t *ast)
|
||||
|
||||
type_t *ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType);
|
||||
CORD ret_type_code = compile_type(ret_t);
|
||||
return CORD_all(ret_type_code, " ", namespace_prefix(env, env->namespace), decl_name, arg_signature, ";\n");
|
||||
CORD name = CORD_all(namespace_prefix(env, env->namespace), decl_name);
|
||||
if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name))
|
||||
name = CORD_asprintf("%r$%ld", name, get_line_number(ast->file, ast->start));
|
||||
return CORD_all(ret_type_code, " ", name, arg_signature, ";\n");
|
||||
}
|
||||
default: return CORD_EMPTY;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ where a different type of string is needed.
|
||||
|
||||
```tomo
|
||||
lang HTML:
|
||||
func escape(t:Text -> HTML):
|
||||
func HTML(t:Text -> HTML):
|
||||
t = t:replace_all({
|
||||
$/&/ = "&",
|
||||
$/</ = "<",
|
||||
@ -57,8 +57,8 @@ Hello <script>alert('pwned')</script>! How are you?
|
||||
```
|
||||
|
||||
This works because the compiler checks for a function in the HTML namespace
|
||||
called `HTML.escape` or any method that starts with `HTML.escape_` that takes a
|
||||
`Text` argument and returns an `HTML` value. When performing interpolation, the
|
||||
that was defined with the name `HTML` that takes a `Text` argument and returns
|
||||
an `HTML` value (a constructor). When performing interpolation, the
|
||||
interpolation will only succeed if such a function exists and it will apply
|
||||
that function to the value before concatenating it.
|
||||
|
||||
@ -74,7 +74,7 @@ instead of building a global function called `execute()` that takes a
|
||||
|
||||
```tomo
|
||||
lang Sh:
|
||||
func escape(text:Text -> Sh):
|
||||
func Sh(text:Text -> Sh):
|
||||
return Sh.without_escaping("'" ++ text:replace($/'/, "''") ++ "'")
|
||||
|
||||
func execute(sh:Sh -> Text):
|
||||
|
@ -463,6 +463,27 @@ env_t *new_compilation_unit(CORD libname)
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion constructors:
|
||||
#define ADD_CONSTRUCTOR(ns_env, identifier, typestr) Array$insert(&ns_env->namespace->constructors, ((binding_t[1]){{.code=identifier, .type=Match(parse_type_string(ns_env, typestr), ClosureType)->fn}}), I(0), sizeof(binding_t))
|
||||
{
|
||||
env_t *ns_env = namespace_env(env, "Pattern");
|
||||
ADD_CONSTRUCTOR(ns_env, "Pattern$escape_text", "func(text:Text -> Pattern)");
|
||||
ADD_CONSTRUCTOR(ns_env, "Int$value_as_text", "func(i:Int -> Pattern)");
|
||||
}
|
||||
{
|
||||
env_t *ns_env = namespace_env(env, "Path");
|
||||
ADD_CONSTRUCTOR(ns_env, "Path$escape_text", "func(text:Text -> Path)");
|
||||
ADD_CONSTRUCTOR(ns_env, "Path$escape_path", "func(path:Path -> Path)");
|
||||
ADD_CONSTRUCTOR(ns_env, "Int$value_as_text", "func(i:Int -> Path)");
|
||||
}
|
||||
{
|
||||
env_t *ns_env = namespace_env(env, "Shell");
|
||||
ADD_CONSTRUCTOR(ns_env, "Shell$escape_text", "func(text:Text -> Shell)");
|
||||
ADD_CONSTRUCTOR(ns_env, "Shell$escape_text_array", "func(texts:[Text] -> Shell)");
|
||||
ADD_CONSTRUCTOR(ns_env, "Int$value_as_text", "func(i:Int -> Shell)");
|
||||
}
|
||||
|
||||
|
||||
set_binding(namespace_env(env, "Shell"), "without_escaping",
|
||||
Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE),
|
||||
.ret=Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell"))),
|
||||
@ -675,18 +696,16 @@ PUREFUNC binding_t *get_lang_escape_function(env_t *env, const char *lang_name,
|
||||
binding_t *typeinfo = get_binding(env, lang_name);
|
||||
assert(typeinfo && typeinfo->type->tag == TypeInfoType);
|
||||
env_t *lang_env = Match(typeinfo->type, TypeInfoType)->env;
|
||||
for (int64_t i = 1; i <= Table$length(*lang_env->locals); i++) {
|
||||
struct {const char *name; binding_t *b; } *entry = Table$entry(*lang_env->locals, i);
|
||||
if (entry->b->type->tag != FunctionType) continue;
|
||||
if (!(streq(entry->name, "escape") || strncmp(entry->name, "escape_", strlen("escape_")) == 0))
|
||||
continue;
|
||||
auto fn = Match(entry->b->type, FunctionType);
|
||||
Array_t constructors = lang_env->namespace->constructors;
|
||||
for (int64_t i = 0; i < constructors.length; i++) {
|
||||
binding_t *b = constructors.data + i*constructors.stride;
|
||||
auto fn = Match(b->type, FunctionType);
|
||||
if (!fn->args || fn->args->next) continue;
|
||||
if (fn->ret->tag != TextType || !streq(Match(fn->ret, TextType)->lang, lang_name))
|
||||
continue;
|
||||
if (!can_promote(type_to_escape, fn->args->type))
|
||||
continue;
|
||||
return entry->b;
|
||||
return b;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ typedef struct loop_ctx_s {
|
||||
|
||||
typedef struct namespace_s {
|
||||
const char *name;
|
||||
Array_t constructors;
|
||||
struct namespace_s *parent;
|
||||
} namespace_t;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
lang HTML:
|
||||
HEADER := $HTML"<!DOCTYPE HTML>"
|
||||
func escape(t:Text->HTML):
|
||||
func HTML(t:Text->HTML):
|
||||
t = t:replace_all({
|
||||
$/&/="&",
|
||||
$/</="<",
|
||||
@ -11,7 +11,7 @@ lang HTML:
|
||||
|
||||
return HTML.without_escaping(t)
|
||||
|
||||
func escape_int(i:Int->HTML):
|
||||
func HTML(i:Int->HTML):
|
||||
return HTML.without_escaping("$i")
|
||||
|
||||
func paragraph(content:HTML->HTML):
|
||||
|
@ -298,9 +298,16 @@ void bind_statement(env_t *env, ast_t *statement)
|
||||
case FunctionDef: {
|
||||
auto def = Match(statement, FunctionDef);
|
||||
const char *name = Match(def->name, Var)->name;
|
||||
type_t *type = get_function_def_type(env, statement);
|
||||
if (env->namespace && env->namespace->parent && env->namespace->name && streq(name, env->namespace->name)) {
|
||||
CORD code = CORD_asprintf("%r%ld", namespace_prefix(env, env->namespace), get_line_number(statement->file, statement->start));
|
||||
binding_t binding = {.type=type, .code=code};
|
||||
Array$insert(&env->namespace->constructors, &binding, I(0), sizeof(binding));
|
||||
break;
|
||||
}
|
||||
|
||||
if (get_binding(env, name))
|
||||
code_err(def->name, "A %T called '%s' has already been defined", get_binding(env, name)->type, name);
|
||||
type_t *type = get_function_def_type(env, statement);
|
||||
CORD code = CORD_all(namespace_prefix(env, env->namespace), name);
|
||||
set_binding(env, name, type, code);
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user