aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compile.c7
-rw-r--r--docs/langs.md8
-rw-r--r--environment.c33
-rw-r--r--environment.h1
-rw-r--r--test/lang.tm4
-rw-r--r--typecheck.c9
6 files changed, 47 insertions, 15 deletions
diff --git a/compile.c b/compile.c
index 3da83977..a2c638e7 100644
--- a/compile.c
+++ b/compile.c
@@ -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;
}
diff --git a/docs/langs.md b/docs/langs.md
index 37141bdc..96c026f1 100644
--- a/docs/langs.md
+++ b/docs/langs.md
@@ -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({
$/&/ = "&",
$/</ = "&lt;",
@@ -57,8 +57,8 @@ Hello &lt;script&gt;alert(&#39;pwned&#39;)&lt;/script&gt;! 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):
diff --git a/environment.c b/environment.c
index 1354999d..3cc5392a 100644
--- a/environment.c
+++ b/environment.c
@@ -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;
}
diff --git a/environment.h b/environment.h
index e32e7bd6..97a6647d 100644
--- a/environment.h
+++ b/environment.h
@@ -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;
diff --git a/test/lang.tm b/test/lang.tm
index c0add87b..77fd7958 100644
--- a/test/lang.tm
+++ b/test/lang.tm
@@ -1,6 +1,6 @@
lang HTML:
HEADER := $HTML"<!DOCTYPE HTML>"
- func escape(t:Text->HTML):
+ func HTML(t:Text->HTML):
t = t:replace_all({
$/&/="&amp;",
$/</="&lt;",
@@ -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):
diff --git a/typecheck.c b/typecheck.c
index 7e63396f..cbdbc8cd 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -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;