Clean up and improve patterns
This commit is contained in:
parent
354ed77535
commit
d888bec409
3
Makefile
3
Makefile
@ -106,7 +106,8 @@ clean:
|
||||
|
||||
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/time examples/commands examples/shell examples/base64 examples/log examples/ini examples/vectors examples/game \
|
||||
./build/tomo -qIL examples/patterns examples/time examples/commands examples/shell examples/base64 examples/log \
|
||||
examples/ini examples/vectors examples/game \
|
||||
examples/http examples/tomodeps examples/tomo-install examples/wrap examples/pthreads examples/colorful
|
||||
./build/tomo examples/learnxiny.tm
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Colorful language
|
||||
|
||||
HELP := "
|
||||
colorful: A domain-specific language for writing colored text to the terminal
|
||||
Usage: colorful [args...] [--by-line] [--files files...]
|
||||
@ -6,6 +7,8 @@ HELP := "
|
||||
|
||||
CSI := "$\033["
|
||||
|
||||
use patterns
|
||||
|
||||
lang Colorful:
|
||||
convert(text:Text -> Colorful):
|
||||
text = text:translate({"@"="@(at)", "("="@(lparen)", ")"="@(rparen)"})
|
||||
@ -42,18 +45,18 @@ func main(texts:[Text], files=[:Path], by_line=no):
|
||||
|
||||
|
||||
func _for_terminal(c:Colorful, state:_TermState -> Text):
|
||||
return c.text:map(recursive=no, $/@(?)/, func(m:Match): _add_ansi_sequences(m.captures[1], state))
|
||||
return c.text:map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch): _add_ansi_sequences(m.captures[1], state))
|
||||
|
||||
enum _Color(Default, Bright(color:Int16), Color8Bit(color:Int16), Color24Bit(color:Int32)):
|
||||
func from_text(text:Text -> _Color?):
|
||||
if text:matches($/#{3-6 hex}/):
|
||||
if text:matches_pattern($Pat/#{3-6 hex}/):
|
||||
hex := text:from(2)
|
||||
return none unless hex.length == 3 or hex.length == 6
|
||||
if hex.length == 3:
|
||||
hex = hex[1]++hex[1]++hex[2]++hex[2]++hex[3]++hex[3]
|
||||
n := Int32.parse("0x" ++ hex) or return none
|
||||
return Color24Bit(n)
|
||||
else if text:matches($/{1-3 digit}/):
|
||||
else if text:matches_pattern($Pat/{1-3 digit}/):
|
||||
n := Int16.parse(text) or return none
|
||||
if n >= 0 and n <= 255: return Color8Bit(n)
|
||||
else if text == "black": return _Color.Color8Bit(0)
|
||||
@ -168,10 +171,10 @@ func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text):
|
||||
else if text == "rparen": return ")"
|
||||
else if text == "@" or text == "at": return "@"
|
||||
parts := (
|
||||
text:matches($/{0+..}:{0+..}/) or
|
||||
text:matches_pattern($Pat/{0+..}:{0+..}/) or
|
||||
return "@("++_for_terminal(Colorful.from_text(text), prev_state)++")"
|
||||
)
|
||||
attributes := parts[1]:split($/{0+space},{0+space}/)
|
||||
attributes := parts[1]:split_pattern($Pat/{0+space},{0+space}/)
|
||||
new_state := prev_state
|
||||
for attr in attributes:
|
||||
if attr:starts_with("fg="):
|
||||
@ -210,6 +213,6 @@ func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text):
|
||||
fail("Invalid attribute: '$attr'")
|
||||
|
||||
result := prev_state:apply(new_state)
|
||||
result ++= parts[2]:map(recursive=no, $/@(?)/, func(m:Match): _add_ansi_sequences(m.captures[1], new_state))
|
||||
result ++= parts[2]:map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch): _add_ansi_sequences(m.captures[1], new_state))
|
||||
result ++= new_state:apply(prev_state)
|
||||
return result
|
||||
|
@ -28,7 +28,7 @@ struct ProgramResult(stdout:[Byte], stderr:[Byte], exit_type:ExitType):
|
||||
if status == 0:
|
||||
if text := Text.from_bytes(r.stdout):
|
||||
if trim_newline:
|
||||
text = text:trim($/{1 nl}/, trim_left=no, trim_right=yes)
|
||||
text = text:without_suffix(\n)
|
||||
return text
|
||||
else: return none
|
||||
return none
|
||||
|
@ -1,3 +1,6 @@
|
||||
|
||||
use patterns
|
||||
|
||||
_USAGE := "
|
||||
Usage: ini <filename> "[section[/key]]"
|
||||
"
|
||||
@ -12,18 +15,18 @@ func parse_ini(path:Path -> {Text,{Text,Text}}):
|
||||
current_section := @{:Text,Text}
|
||||
|
||||
# Line wraps:
|
||||
text = text:replace($/\{1 nl}{0+space}/, " ")
|
||||
text = text:replace_pattern($Pat/\{1 nl}{0+space}/, " ")
|
||||
|
||||
for line in text:lines():
|
||||
line = line:trim()
|
||||
skip if line:starts_with(";") or line:starts_with("#")
|
||||
if line:matches($/[?]/):
|
||||
section_name := line:replace($/[?]/, "\1"):trim():lower()
|
||||
if line:matches_pattern($Pat/[?]/):
|
||||
section_name := line:replace($Pat/[?]/, "\1"):trim():lower()
|
||||
current_section = @{:Text,Text}
|
||||
sections[section_name] = current_section
|
||||
else if line:matches($/{..}={..}/):
|
||||
key := line:replace($/{..}={..}/, "\1"):trim():lower()
|
||||
value := line:replace($/{..}={..}/, "\2"):trim()
|
||||
else if line:matches_pattern($Pat/{..}={..}/):
|
||||
key := line:replace_pattern($Pat/{..}={..}/, "\1"):trim():lower()
|
||||
value := line:replace_pattern($Pat/{..}={..}/, "\2"):trim()
|
||||
current_section[key] = value
|
||||
|
||||
return {k=v[] for k,v in sections[]}
|
||||
|
@ -10,49 +10,43 @@ lang Pat:
|
||||
return Pat.from_text("$n")
|
||||
|
||||
extend Text:
|
||||
func matches(text:Text, pattern:Pat -> [Text]?):
|
||||
func matches_pattern(text:Text, pattern:Pat -> [Text]?):
|
||||
return inline C : [Text]? { Pattern$matches(_$text, _$pattern); }
|
||||
|
||||
func pat_replace(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text):
|
||||
func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text):
|
||||
return inline C : Text { Pattern$replace(_$text, _$pattern, _$replacement, _$backref, _$recursive); }
|
||||
|
||||
func pat_replace_all(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text):
|
||||
func translate_patterns(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text):
|
||||
return inline C : Text { Pattern$replace_all(_$text, _$replacements, _$backref, _$recursive); }
|
||||
|
||||
func has(text:Text, pattern:Pat -> Bool):
|
||||
func has_pattern(text:Text, pattern:Pat -> Bool):
|
||||
return inline C : Bool { Pattern$has(_$text, _$pattern); }
|
||||
|
||||
func find_all(text:Text, pattern:Pat -> [PatternMatch]):
|
||||
func find_patterns(text:Text, pattern:Pat -> [PatternMatch]):
|
||||
return inline C : [PatternMatch] { Pattern$find_all(_$text, _$pattern); }
|
||||
|
||||
func by_match(text:Text, pattern:Pat -> func(->PatternMatch?)):
|
||||
func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?)):
|
||||
return inline C : func(->PatternMatch?) { Pattern$by_match(_$text, _$pattern); }
|
||||
|
||||
func each(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes):
|
||||
func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes):
|
||||
inline C { Pattern$each(_$text, _$pattern, _$fn, _$recursive); }
|
||||
|
||||
func map(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text):
|
||||
func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text):
|
||||
return inline C : Text { Pattern$map(_$text, _$pattern, _$fn, _$recursive); }
|
||||
|
||||
func split(text:Text, pattern:Pat -> [Text]):
|
||||
func split_pattern(text:Text, pattern:Pat -> [Text]):
|
||||
return inline C : [Text] { Pattern$split(_$text, _$pattern); }
|
||||
|
||||
func by_split(text:Text, pattern:Pat -> func(->Text?)):
|
||||
func by_pattern_split(text:Text, pattern:Pat -> func(->Text?)):
|
||||
return inline C : func(->Text?) { Pattern$by_split(_$text, _$pattern); }
|
||||
|
||||
func trim(text:Text, pattern:Pat, trim_left=yes, trim_right=yes -> Text):
|
||||
return inline C : Text { Pattern$trim(_$text, _$pattern, _$trim_left, _$trim_right); }
|
||||
|
||||
func trim_left(text:Text, pattern:Pat -> Text):
|
||||
return text:trim(pattern, trim_left=yes, trim_right=no)
|
||||
|
||||
func trim_right(text:Text, pattern:Pat -> Text):
|
||||
return text:trim(pattern, trim_left=no, trim_right=yes)
|
||||
func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text):
|
||||
return inline C : Text { Pattern$trim(_$text, _$pattern, _$left, _$right); }
|
||||
|
||||
func main():
|
||||
>> "hello world":pat_replace($Pat/{id}/, "XXX")
|
||||
>> "hello world":find_all($Pat/l/)
|
||||
>> "hello world":replace_pattern($Pat/{id}/, "XXX")
|
||||
>> "hello world":find_patterns($Pat/l/)
|
||||
|
||||
for m in "hello one two three":by_match($Pat/{id}/):
|
||||
for m in "hello one two three":by_pattern($Pat/{id}/):
|
||||
>> m
|
||||
|
||||
|
@ -123,7 +123,7 @@ struct Time(tv_sec:Int64, tv_usec:Int64; extern):
|
||||
t:format("%l:%M%P")
|
||||
else:
|
||||
t:format("%H:%M")
|
||||
return time:trim($/ /, trim_left=yes, trim_right=yes)
|
||||
return time:trim()
|
||||
|
||||
func date(t:Time, timezone=Time.local_timezone() -> Text):
|
||||
return t:format("%F")
|
||||
|
@ -1,4 +1,5 @@
|
||||
use shell
|
||||
use patterns
|
||||
|
||||
_USAGE := "
|
||||
tomo-install file.tm...
|
||||
@ -16,7 +17,7 @@ func find_urls(path:Path -> [Text]):
|
||||
urls:insert_all(find_urls(f))
|
||||
else if path:is_file() and path:extension() == ".tm":
|
||||
for line in path:by_line()!:
|
||||
if m := line:matches($/use{space}{url}/) or line:matches($/{id}{space}:={space}use{space}{url}/):
|
||||
if m := line:matches_pattern($Pat/use{space}{url}/) or line:matches_pattern($Pat/{id}{space}:={space}use{space}{url}/):
|
||||
urls:insert(m[-1])
|
||||
return urls
|
||||
|
||||
@ -33,7 +34,7 @@ func main(paths:[Path]):
|
||||
|
||||
for url in urls:
|
||||
original_url := url
|
||||
url_without_protocol := url:trim($|http{0-1 s}://|, trim_right=no)
|
||||
url_without_protocol := url:trim_pattern($Pat"http{0-1 s}://", right=no)
|
||||
hash := $Shell@(echo -n @url_without_protocol | sha256sum):get_output()!:slice(to=32)
|
||||
if (~/.local/share/tomo/installed/$hash):is_directory():
|
||||
say("Already installed: $url")
|
||||
@ -41,12 +42,12 @@ func main(paths:[Path]):
|
||||
|
||||
alias := none:Text
|
||||
curl_flags := ["-L"]
|
||||
if github := url_without_protocol:matches($|github.com/{!/}/{!/}#{..}|):
|
||||
if github := url_without_protocol:matches_pattern($Pat"github.com/{!/}/{!/}#{..}"):
|
||||
user := github[1]
|
||||
repo := github[2]
|
||||
tag := github[3]
|
||||
url = "https://api.github.com/repos/$user/$repo/tarball/$tag"
|
||||
alias = "$(repo:trim($/tomo-/, trim_right=no)).$(tag).$(user)"
|
||||
alias = "$(repo:without_prefix("tomo-")).$(tag).$(user)"
|
||||
if github_token:
|
||||
curl_flags ++= ["-H", "Authorization: Bearer $github_token"]
|
||||
curl_flags ++= [
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Show a Tomo dependency graph
|
||||
|
||||
use patterns
|
||||
|
||||
_USAGE := "Usage: tomodeps <files...>"
|
||||
|
||||
_HELP := "
|
||||
@ -17,11 +19,11 @@ func _get_file_dependencies(file:Path -> {Dependency}):
|
||||
deps := @{:Dependency}
|
||||
if lines := file:by_line():
|
||||
for line in lines:
|
||||
if line:matches($/use {..}.tm/):
|
||||
file_import := Path.from_text(line:replace($/use {..}/, "\1")):resolved(relative_to=file)
|
||||
if line:matches_pattern($Pat/use {..}.tm/):
|
||||
file_import := Path.from_text(line:replace_pattern($Pat/use {..}/, "\1")):resolved(relative_to=file)
|
||||
deps:add(Dependency.File(file_import))
|
||||
else if line:matches($/use {id}/):
|
||||
module_name := line:replace($/use {..}/, "\1")
|
||||
else if line:matches_pattern($Pat/use {id}/):
|
||||
module_name := line:replace_pattern($Pat/use {..}/, "\1")
|
||||
deps:add(Dependency.Module(module_name))
|
||||
return deps[]
|
||||
|
||||
@ -102,11 +104,11 @@ func main(files:[Text]):
|
||||
")
|
||||
|
||||
for arg in files:
|
||||
if arg:matches($/{..}.tm/):
|
||||
if arg:matches_pattern($Pat/{..}.tm/):
|
||||
path := Path.from_text(arg):resolved()
|
||||
dependencies := get_dependency_graph(File(path))
|
||||
draw_tree(File(path), dependencies)
|
||||
else if arg:matches($/{id}/):
|
||||
else if arg:matches_pattern($Pat/{id}/):
|
||||
dependencies := get_dependency_graph(Module(arg))
|
||||
draw_tree(Module(arg), dependencies)
|
||||
else:
|
||||
|
@ -4200,8 +4200,12 @@ CORD compile_top_level_code(env_t *env, ast_t *ast)
|
||||
case Extend: {
|
||||
auto extend = Match(ast, Extend);
|
||||
env_t *ns_env = namespace_env(env, extend->name);
|
||||
ns_env->libname = env->libname;
|
||||
return compile_top_level_code(ns_env, extend->body);
|
||||
env_t *extended = new(env_t);
|
||||
*extended = *ns_env;
|
||||
extended->locals = new(Table_t, .fallback=env->locals);
|
||||
extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings);
|
||||
extended->libname = env->libname;
|
||||
return compile_top_level_code(extended, extend->body);
|
||||
}
|
||||
case Extern: return CORD_EMPTY;
|
||||
case Block: {
|
||||
@ -4358,30 +4362,38 @@ CORD compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast)
|
||||
|
||||
CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *ast)
|
||||
{
|
||||
const char *ns_name = NULL;
|
||||
env_t *ns_env = NULL;
|
||||
ast_t *block = NULL;
|
||||
switch (ast->tag) {
|
||||
case LangDef: {
|
||||
auto def = Match(ast, LangDef);
|
||||
ns_name = def->name;
|
||||
ns_env = namespace_env(env, def->name);
|
||||
block = def->namespace;
|
||||
break;
|
||||
}
|
||||
case Extend: {
|
||||
auto extend = Match(ast, Extend);
|
||||
ns_name = extend->name;
|
||||
ns_env = namespace_env(env, extend->name);
|
||||
|
||||
env_t *extended = new(env_t);
|
||||
*extended = *ns_env;
|
||||
extended->locals = new(Table_t, .fallback=env->locals);
|
||||
extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings);
|
||||
extended->libname = env->libname;
|
||||
ns_env = extended;
|
||||
|
||||
block = extend->body;
|
||||
break;
|
||||
}
|
||||
case StructDef: {
|
||||
auto def = Match(ast, StructDef);
|
||||
ns_name = def->name;
|
||||
ns_env = namespace_env(env, def->name);
|
||||
block = def->namespace;
|
||||
break;
|
||||
}
|
||||
case EnumDef: {
|
||||
auto def = Match(ast, EnumDef);
|
||||
ns_name = def->name;
|
||||
ns_env = namespace_env(env, def->name);
|
||||
block = def->namespace;
|
||||
break;
|
||||
}
|
||||
@ -4465,7 +4477,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a
|
||||
}
|
||||
default: return CORD_EMPTY;
|
||||
}
|
||||
env_t *ns_env = namespace_env(env, ns_name);
|
||||
assert(ns_env);
|
||||
CORD header = CORD_EMPTY;
|
||||
for (ast_list_t *stmt = block ? Match(block, Block)->statements : NULL; stmt; stmt = stmt->next) {
|
||||
header = CORD_all(header, compile_statement_namespace_header(ns_env, header_path, stmt->ast));
|
||||
|
@ -274,9 +274,23 @@ void prebind_statement(env_t *env, ast_t *statement)
|
||||
case Extend: {
|
||||
auto extend = Match(statement, Extend);
|
||||
env_t *ns_env = namespace_env(env, extend->name);
|
||||
ns_env->libname = env->libname;
|
||||
env_t *extended = new(env_t);
|
||||
*extended = *ns_env;
|
||||
extended->locals = new(Table_t, .fallback=env->locals);
|
||||
extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings);
|
||||
extended->libname = env->libname;
|
||||
for (ast_list_t *stmt = extend->body ? Match(extend->body, Block)->statements : NULL; stmt; stmt = stmt->next)
|
||||
prebind_statement(ns_env, stmt->ast);
|
||||
prebind_statement(extended, stmt->ast);
|
||||
Array_t new_bindings = extended->locals->entries;
|
||||
for (int64_t i = 0; i < new_bindings.length; i++) {
|
||||
struct { const char *name; binding_t *binding; } *entry = new_bindings.data + i*new_bindings.stride;
|
||||
binding_t *clobbered = Table$str_get(*ns_env->locals, entry->name);
|
||||
if (clobbered && !type_eq(clobbered->type, entry->binding->type))
|
||||
code_err(statement, "This `extend` block overwrites the binding for ", quoted(entry->name),
|
||||
" in the original namespace (with type ", type_to_str(clobbered->type), ") with a new binding with type ",
|
||||
type_to_str(entry->binding->type));
|
||||
Table$str_set(ns_env->locals, entry->name, entry->binding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
@ -448,9 +462,23 @@ void bind_statement(env_t *env, ast_t *statement)
|
||||
case Extend: {
|
||||
auto extend = Match(statement, Extend);
|
||||
env_t *ns_env = namespace_env(env, extend->name);
|
||||
ns_env->libname = env->libname;
|
||||
env_t *extended = new(env_t);
|
||||
*extended = *ns_env;
|
||||
extended->locals = new(Table_t, .fallback=env->locals);
|
||||
extended->namespace_bindings = new(Table_t, .fallback=env->namespace_bindings);
|
||||
extended->libname = env->libname;
|
||||
for (ast_list_t *stmt = extend->body ? Match(extend->body, Block)->statements : NULL; stmt; stmt = stmt->next)
|
||||
bind_statement(ns_env, stmt->ast);
|
||||
bind_statement(extended, stmt->ast);
|
||||
Array_t new_bindings = extended->locals->entries;
|
||||
for (int64_t i = 0; i < new_bindings.length; i++) {
|
||||
struct { const char *name; binding_t *binding; } *entry = new_bindings.data + i*new_bindings.stride;
|
||||
binding_t *clobbered = Table$str_get(*ns_env->locals, entry->name);
|
||||
if (clobbered && !type_eq(clobbered->type, entry->binding->type))
|
||||
code_err(statement, "This `extend` block overwrites the binding for ", quoted(entry->name),
|
||||
" in the original namespace (with type ", type_to_str(clobbered->type), ") with a new binding with type ",
|
||||
type_to_str(entry->binding->type));
|
||||
Table$str_set(ns_env->locals, entry->name, entry->binding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Use: {
|
||||
|
Loading…
Reference in New Issue
Block a user