From 8b512adbbcf62f49b3e92410e643c86f2e70908a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 15 Sep 2024 17:50:43 -0400 Subject: Move each example to its own folder --- examples/README.md | 14 ++--- examples/coroutine.tm | 64 --------------------- examples/coroutine/coroutine.tm | 64 +++++++++++++++++++++ examples/example.ini | 8 --- examples/http.tm | 100 -------------------------------- examples/http/http.tm | 100 ++++++++++++++++++++++++++++++++ examples/ini.tm | 59 ------------------- examples/ini/example.ini | 8 +++ examples/ini/ini.tm | 59 +++++++++++++++++++ examples/log.tm | 50 ---------------- examples/log/log.tm | 50 ++++++++++++++++ examples/tomodeps.tm | 124 ---------------------------------------- examples/tomodeps/tomodeps.tm | 124 ++++++++++++++++++++++++++++++++++++++++ examples/vectors.tm | 119 -------------------------------------- examples/vectors/vectors.tm | 119 ++++++++++++++++++++++++++++++++++++++ examples/wrap.tm | 102 --------------------------------- examples/wrap/wrap.tm | 102 +++++++++++++++++++++++++++++++++ 17 files changed, 633 insertions(+), 633 deletions(-) delete mode 100644 examples/coroutine.tm create mode 100644 examples/coroutine/coroutine.tm delete mode 100644 examples/example.ini delete mode 100644 examples/http.tm create mode 100644 examples/http/http.tm delete mode 100644 examples/ini.tm create mode 100644 examples/ini/example.ini create mode 100644 examples/ini/ini.tm delete mode 100644 examples/log.tm create mode 100644 examples/log/log.tm delete mode 100644 examples/tomodeps.tm create mode 100644 examples/tomodeps/tomodeps.tm delete mode 100644 examples/vectors.tm create mode 100644 examples/vectors/vectors.tm delete mode 100644 examples/wrap.tm create mode 100644 examples/wrap/wrap.tm (limited to 'examples') diff --git a/examples/README.md b/examples/README.md index df5af833..330b0e18 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,12 @@ # Example Programs -- [coroutine.tm](coroutine.tm): A library for coroutines similar to Lua's +- [coroutine](coroutine/coroutine.tm): A library for coroutines similar to Lua's (using [libaco](https://libaco.org)). -- [game/](game/): An example game using raylib. -- [http.tm](http.tm): An HTTP library to make basic synchronous HTTP requests. -- [ini.tm](ini.tm): An INI configuration file reader tool. +- [game](game/): An example game using raylib. +- [http](http/http.tm): An HTTP library to make basic synchronous HTTP requests. +- [ini](ini/ini.tm): An INI configuration file reader tool. - [learnxiny.tm](learnxiny.tm): A quick overview of language features in the style of [learnxinyminutes.com](https://learnxinyminutes.com/). -- [log.tm](log.tm): A logging utility. -- [vectors.tm](vectors.tm): A math vector library. -- [wrap.tm](wrap.tm): A command-line program to wrap text. +- [log](log/log.tm): A logging utility. +- [vectors](vectors/vectors.tm): A math vector library. +- [wrap](wrap/wrap.tm): A command-line program to wrap text. diff --git a/examples/coroutine.tm b/examples/coroutine.tm deleted file mode 100644 index 35eb5fda..00000000 --- a/examples/coroutine.tm +++ /dev/null @@ -1,64 +0,0 @@ -# This is a coroutine library that uses libaco. If you don't have it installed, -# you can get it from the website: https://libaco.org -# -# Lua programmers will recognize this as similar to Lua's stackful coroutines. -# -# Async/Await programmers will weep at its beauty and gnash their teeth and -# rend their garments in despair at what they could have had. - -use libaco.so -use - -func main(): - !! Example usage: - co := new(func(): - say("I'm in the coroutine!") - yield() - say("I'm back in the coroutine!") - ) - >> co - say("I'm in the main func") - >> co:resume() - say("I'm back in the main func") - >> co:resume() - say("I'm back in the main func again") - >> co:resume() - -_main_co := !@Memory -_shared_stack := !@Memory - -struct Coroutine(co:@Memory): - func is_finished(co:Coroutine; inline)->Bool: - return inline C:Bool {((aco_t*)$co.$co)->is_finished} - - func resume(co:Coroutine)->Bool: - if co:is_finished(): - return no - inline C { aco_resume($co.$co); } - return yes - -func _init(): - inline C { - aco_set_allocator(GC_malloc, NULL); - aco_thread_init(aco_exit_fn); - } - _main_co = inline C:@Memory { aco_create(NULL, NULL, 0, NULL, NULL) } - - _shared_stack = inline C:@Memory { aco_shared_stack_new(0) } - -func new(co:func())->Coroutine: - if not _main_co: - _init() - - main_co := _main_co - shared_stack := _shared_stack - aco_ptr := inline C:@Memory { - aco_create($main_co, $shared_stack, 0, (void*)$co.fn, $co.userdata) - } - return Coroutine(aco_ptr) - -func yield(; inline): - inline C { - aco_yield(); - } - diff --git a/examples/coroutine/coroutine.tm b/examples/coroutine/coroutine.tm new file mode 100644 index 00000000..35eb5fda --- /dev/null +++ b/examples/coroutine/coroutine.tm @@ -0,0 +1,64 @@ +# This is a coroutine library that uses libaco. If you don't have it installed, +# you can get it from the website: https://libaco.org +# +# Lua programmers will recognize this as similar to Lua's stackful coroutines. +# +# Async/Await programmers will weep at its beauty and gnash their teeth and +# rend their garments in despair at what they could have had. + +use libaco.so +use + +func main(): + !! Example usage: + co := new(func(): + say("I'm in the coroutine!") + yield() + say("I'm back in the coroutine!") + ) + >> co + say("I'm in the main func") + >> co:resume() + say("I'm back in the main func") + >> co:resume() + say("I'm back in the main func again") + >> co:resume() + +_main_co := !@Memory +_shared_stack := !@Memory + +struct Coroutine(co:@Memory): + func is_finished(co:Coroutine; inline)->Bool: + return inline C:Bool {((aco_t*)$co.$co)->is_finished} + + func resume(co:Coroutine)->Bool: + if co:is_finished(): + return no + inline C { aco_resume($co.$co); } + return yes + +func _init(): + inline C { + aco_set_allocator(GC_malloc, NULL); + aco_thread_init(aco_exit_fn); + } + _main_co = inline C:@Memory { aco_create(NULL, NULL, 0, NULL, NULL) } + + _shared_stack = inline C:@Memory { aco_shared_stack_new(0) } + +func new(co:func())->Coroutine: + if not _main_co: + _init() + + main_co := _main_co + shared_stack := _shared_stack + aco_ptr := inline C:@Memory { + aco_create($main_co, $shared_stack, 0, (void*)$co.fn, $co.userdata) + } + return Coroutine(aco_ptr) + +func yield(; inline): + inline C { + aco_yield(); + } + diff --git a/examples/example.ini b/examples/example.ini deleted file mode 100644 index 782dc76f..00000000 --- a/examples/example.ini +++ /dev/null @@ -1,8 +0,0 @@ -[ Book ] -title = Dirk Gently's Holistic Detective Agency -author = Douglas Adams -published = 1987 - -[ Protagonist ] -name = Dirk Gently -age = 42 diff --git a/examples/http.tm b/examples/http.tm deleted file mode 100644 index bbec3308..00000000 --- a/examples/http.tm +++ /dev/null @@ -1,100 +0,0 @@ -# A simple HTTP library built using CURL - -use libcurl.so -use - -struct HTTPResponse(code:Int, body:Text) - -enum _Method(GET, POST, PUT, PATCH, DELETE) - -_curl := !@Memory - -func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse: - chunks := @[:Text] - save_chunk := func(chunk:CString, size:Int64, n:Int64): - chunks:insert(inline C:Text { - Text$format("%.*s", $size*$n, $chunk) - }) - return n*size - - inline C { - CURL *curl = curl_easy_init(); - struct curl_slist *chunk = NULL; - curl_easy_setopt(curl, CURLOPT_URL, Text$as_c_string($url)); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, $save_chunk.fn); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, $save_chunk.userdata); - } - - when method is POST: - inline C { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - } - if posting := data: - inline C { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Text$as_c_string($posting)); - } - is PUT: - inline C { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - } - if putting := data: - inline C { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Text$as_c_string($putting)); - } - is DELETE: - inline C { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - } - else: - pass - - for header in headers: - inline C { - chunk = curl_slist_append(chunk, Text$as_c_string($header)); - } - - inline C { - if (chunk) - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); - } - - code := 0[64] - inline C { - CURLcode res = curl_easy_perform(curl); - if (res != CURLE_OK) - fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); - - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &$code); - if (chunk) - curl_slist_free_all(chunk); - curl_easy_cleanup(curl); - } - return HTTPResponse(code, "":join(chunks)) - -func get(url:Text, headers=[:Text])->HTTPResponse: - return _send(GET, url, !Text, headers) - -func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: - return _send(POST, url, data, headers) - -func put(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: - return _send(PUT, url, data, headers) - -func delete(url:Text, data=!Text, headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: - return _send(DELETE, url, data, headers) - -func main(): - !! GET: - say(get("https://httpbin.org/get").body) - !! Waiting 1sec... - sleep(1) - !! POST: - say(post("https://httpbin.org/post", `{"key": "value"}`).body) - !! Waiting 1sec... - sleep(1) - !! PUT: - say(put("https://httpbin.org/put", `{"key": "value"}`).body) - !! Waiting 1sec... - sleep(1) - !! DELETE: - say(delete("https://httpbin.org/delete").body) diff --git a/examples/http/http.tm b/examples/http/http.tm new file mode 100644 index 00000000..bbec3308 --- /dev/null +++ b/examples/http/http.tm @@ -0,0 +1,100 @@ +# A simple HTTP library built using CURL + +use libcurl.so +use + +struct HTTPResponse(code:Int, body:Text) + +enum _Method(GET, POST, PUT, PATCH, DELETE) + +_curl := !@Memory + +func _send(method:_Method, url:Text, data:Text?, headers=[:Text])->HTTPResponse: + chunks := @[:Text] + save_chunk := func(chunk:CString, size:Int64, n:Int64): + chunks:insert(inline C:Text { + Text$format("%.*s", $size*$n, $chunk) + }) + return n*size + + inline C { + CURL *curl = curl_easy_init(); + struct curl_slist *chunk = NULL; + curl_easy_setopt(curl, CURLOPT_URL, Text$as_c_string($url)); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, $save_chunk.fn); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, $save_chunk.userdata); + } + + when method is POST: + inline C { + curl_easy_setopt(curl, CURLOPT_POST, 1L); + } + if posting := data: + inline C { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Text$as_c_string($posting)); + } + is PUT: + inline C { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + } + if putting := data: + inline C { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Text$as_c_string($putting)); + } + is DELETE: + inline C { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + else: + pass + + for header in headers: + inline C { + chunk = curl_slist_append(chunk, Text$as_c_string($header)); + } + + inline C { + if (chunk) + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + } + + code := 0[64] + inline C { + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &$code); + if (chunk) + curl_slist_free_all(chunk); + curl_easy_cleanup(curl); + } + return HTTPResponse(code, "":join(chunks)) + +func get(url:Text, headers=[:Text])->HTTPResponse: + return _send(GET, url, !Text, headers) + +func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: + return _send(POST, url, data, headers) + +func put(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: + return _send(PUT, url, data, headers) + +func delete(url:Text, data=!Text, headers=["Content-Type: application/json", "Accept: application/json"])->HTTPResponse: + return _send(DELETE, url, data, headers) + +func main(): + !! GET: + say(get("https://httpbin.org/get").body) + !! Waiting 1sec... + sleep(1) + !! POST: + say(post("https://httpbin.org/post", `{"key": "value"}`).body) + !! Waiting 1sec... + sleep(1) + !! PUT: + say(put("https://httpbin.org/put", `{"key": "value"}`).body) + !! Waiting 1sec... + sleep(1) + !! DELETE: + say(delete("https://httpbin.org/delete").body) diff --git a/examples/ini.tm b/examples/ini.tm deleted file mode 100644 index c0d490cb..00000000 --- a/examples/ini.tm +++ /dev/null @@ -1,59 +0,0 @@ -_USAGE := " - Usage: ini "[section[/key]]" -" -_HELP := " - ini: A .ini config file reader tool. - $_USAGE -" - -func parse_ini(path:Path)->{Text:{Text:Text}}: - text := path:read():or_exit("Could not read INI file: $\[31;1]$(path.text_content)$\[]") - sections := {:Text:@{Text:Text}} - current_section := @{:Text:Text} - sections:set("", current_section) - - # Line wraps: - text = text:replace($/\{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() - current_section = @{:Text:Text} - sections:set(section_name, current_section) - else if line:matches($/{..}={..}/): - key := line:replace($/{..}={..}/, "\1"):trim():lower() - value := line:replace($/{..}={..}/, "\2"):trim() - current_section:set(key, value) - - return {k:v[] for k,v in sections} - -func main(path:Path, key:Text): - keys := key:split($Pattern"/") - if keys.length > 2: - exit(" - Too many arguments! - $_USAGE - ") - - data := parse_ini(path) - if keys.length < 1 or keys[1] == '*': - !! $data - return - - section := keys[1]:lower() - section_data := data:get(section):or_exit(" - Invalid section name: $\[31;1]$section$\[] - Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[] - ") - if keys.length < 2 or keys[2] == '*': - !! $section_data - return - - section_key := keys[2]:lower() - value := section_data:get(section_key):or_exit(" - Invalid key: $\[31;1]$section_key$\[] - Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[] - ") - say(value) diff --git a/examples/ini/example.ini b/examples/ini/example.ini new file mode 100644 index 00000000..782dc76f --- /dev/null +++ b/examples/ini/example.ini @@ -0,0 +1,8 @@ +[ Book ] +title = Dirk Gently's Holistic Detective Agency +author = Douglas Adams +published = 1987 + +[ Protagonist ] +name = Dirk Gently +age = 42 diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm new file mode 100644 index 00000000..c0d490cb --- /dev/null +++ b/examples/ini/ini.tm @@ -0,0 +1,59 @@ +_USAGE := " + Usage: ini "[section[/key]]" +" +_HELP := " + ini: A .ini config file reader tool. + $_USAGE +" + +func parse_ini(path:Path)->{Text:{Text:Text}}: + text := path:read():or_exit("Could not read INI file: $\[31;1]$(path.text_content)$\[]") + sections := {:Text:@{Text:Text}} + current_section := @{:Text:Text} + sections:set("", current_section) + + # Line wraps: + text = text:replace($/\{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() + current_section = @{:Text:Text} + sections:set(section_name, current_section) + else if line:matches($/{..}={..}/): + key := line:replace($/{..}={..}/, "\1"):trim():lower() + value := line:replace($/{..}={..}/, "\2"):trim() + current_section:set(key, value) + + return {k:v[] for k,v in sections} + +func main(path:Path, key:Text): + keys := key:split($Pattern"/") + if keys.length > 2: + exit(" + Too many arguments! + $_USAGE + ") + + data := parse_ini(path) + if keys.length < 1 or keys[1] == '*': + !! $data + return + + section := keys[1]:lower() + section_data := data:get(section):or_exit(" + Invalid section name: $\[31;1]$section$\[] + Valid names: $\[1]$(", ":join([k:quoted() for k in data.keys]))$\[] + ") + if keys.length < 2 or keys[2] == '*': + !! $section_data + return + + section_key := keys[2]:lower() + value := section_data:get(section_key):or_exit(" + Invalid key: $\[31;1]$section_key$\[] + Valid keys: $\[1]$(", ":join([s:quoted() for s in section_data.keys]))$\[] + ") + say(value) diff --git a/examples/log.tm b/examples/log.tm deleted file mode 100644 index 42df072c..00000000 --- a/examples/log.tm +++ /dev/null @@ -1,50 +0,0 @@ -use -use - -timestamp_format := CString("%F %T") - -logfiles := {:Path} - -func _timestamp()->Text: - c_str := inline C:CString { - char *str = GC_MALLOC_ATOMIC(20); - time_t t; time(&t); - struct tm *tm_info = localtime(&t); - strftime(str, 20, "%F %T", tm_info); - str - } - return c_str:as_text() - -func info(text:Text, newline=yes): - say("$\[2]⚫ $text$\[]", newline) - for file in logfiles: - file:append("$(_timestamp()) [info] $text$\n") - -func debug(text:Text, newline=yes): - say("$\[32]🟢 $text$\[]", newline) - for file in logfiles: - file:append("$(_timestamp()) [debug] $text$\n") - -func warn(text:Text, newline=yes): - say("$\[33;1]🟡 $text$\[]", newline) - for file in logfiles: - file:append("$(_timestamp()) [warn] $text$\n") - -func error(text:Text, newline=yes): - say("$\[31;1]🔴 $text$\[]", newline) - for file in logfiles: - file:append("$(_timestamp()) [error] $text$\n") - -func add_logfile(file:Path): - logfiles:add(file) - -func remove_logfile(file:Path): - logfiles:remove(file) - -func main(): - add_logfile((./log.txt)) - >> info("Hello") - >> debug("Hello") - >> warn("Hello") - >> error("Hello") - diff --git a/examples/log/log.tm b/examples/log/log.tm new file mode 100644 index 00000000..42df072c --- /dev/null +++ b/examples/log/log.tm @@ -0,0 +1,50 @@ +use +use + +timestamp_format := CString("%F %T") + +logfiles := {:Path} + +func _timestamp()->Text: + c_str := inline C:CString { + char *str = GC_MALLOC_ATOMIC(20); + time_t t; time(&t); + struct tm *tm_info = localtime(&t); + strftime(str, 20, "%F %T", tm_info); + str + } + return c_str:as_text() + +func info(text:Text, newline=yes): + say("$\[2]⚫ $text$\[]", newline) + for file in logfiles: + file:append("$(_timestamp()) [info] $text$\n") + +func debug(text:Text, newline=yes): + say("$\[32]🟢 $text$\[]", newline) + for file in logfiles: + file:append("$(_timestamp()) [debug] $text$\n") + +func warn(text:Text, newline=yes): + say("$\[33;1]🟡 $text$\[]", newline) + for file in logfiles: + file:append("$(_timestamp()) [warn] $text$\n") + +func error(text:Text, newline=yes): + say("$\[31;1]🔴 $text$\[]", newline) + for file in logfiles: + file:append("$(_timestamp()) [error] $text$\n") + +func add_logfile(file:Path): + logfiles:add(file) + +func remove_logfile(file:Path): + logfiles:remove(file) + +func main(): + add_logfile((./log.txt)) + >> info("Hello") + >> debug("Hello") + >> warn("Hello") + >> error("Hello") + diff --git a/examples/tomodeps.tm b/examples/tomodeps.tm deleted file mode 100644 index e3354136..00000000 --- a/examples/tomodeps.tm +++ /dev/null @@ -1,124 +0,0 @@ -# Show a Tomo dependency graph - -_USAGE := "Usage: dependencies " - -_HELP := " - dependencies: Show a file dependency graph for Tomo source files. - $_USAGE -" - -enum Dependency(File(path:Path), Module(name:Text)) - -func _get_file_dependencies(file:Path)->{Dependency}: - if not file:is_file(): - !! Could not read file: $file - return {:Dependency} - - deps := {:Dependency} - if lines := file:by_line(): - for line in lines: - if line:matches($/use {..}.tm/): - file_import := Path.from_unsafe_text(line:replace($/use {..}/, "\1")):resolved(relative_to=file) - deps:add(Dependency.File(file_import)) - else if line:matches($/use {id}/): - module_name := line:replace($/use {..}/, "\1") - deps:add(Dependency.Module(module_name)) - return deps - -func _build_dependency_graph(dep:Dependency, dependencies:&{Dependency:{Dependency}}): - return if dependencies:has(dep) - - dependencies:set(dep, {:Dependency}) # Placeholder - - dep_deps := when dep is File(path): - _get_file_dependencies(path) - is Module(module): - files_path := (~/.local/src/tomo/$module/lib$module.files):resolved() - if not files_path:is_file(): - !! Could not read file: $files_path - return - - unvisited := {:Path} - if lines := files_path:by_line(): - for line in lines: - tm_path := Path.from_unsafe_text(line):resolved(relative_to=(~/.local/src/tomo/$module/)) - unvisited:add(tm_path) - - module_deps := {:Dependency} - visited := {:Path} - while unvisited.length > 0: - file := unvisited.items[-1] - unvisited:remove(file) - visited:add(file) - - for file_dep in _get_file_dependencies(file): - when file_dep is File(f): - if not visited:has(f): - unvisited:add(f) - is Module(m): - module_deps:add(file_dep) - module_deps - - dependencies:set(dep, dep_deps) - - for dep2 in dep_deps: - _build_dependency_graph(dep2, dependencies) - -func get_dependency_graph(dep:Dependency)->{Dependency:{Dependency}}: - graph := {:Dependency:{Dependency}} - _build_dependency_graph(dep, &graph) - return graph - -func _printable_name(dep:Dependency)->Text: - when dep is Module(module): - return "$(\x1b)[34;1m$module$(\x1b)[m" - is File(f): - f = f:relative() - if f:exists(): - return "$(f.text_content)" - else: - return "$(\x1b)[31;1m$(f.text_content) (not found)$(\x1b)[m" - -func _draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}, already_printed:&{Dependency}, prefix="", is_last=yes): - if already_printed:has(dep): - say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m") - return - - say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep)) - already_printed:add(dep) - - child_prefix := prefix ++ (if is_last: " " else: "│ ") - - children := dependencies:get(dep):or_else({:Dependency}) - for i,child in children.items: - is_child_last := (i == children.length) - _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) - -func draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}): - printed := {:Dependency} - say(_printable_name(dep)) - printed:add(dep) - deps := dependencies:get(dep):or_else({:Dependency}) - for i,child in deps.items: - is_child_last := (i == deps.length) - _draw_tree(child, dependencies, already_printed=&printed, is_last=is_child_last) - -func main(files:[Text]): - if files.length == 0: - exit(" - Please provide at least one file! - $_USAGE - ") - - for arg in files: - if arg:matches($/{..}.tm/): - path := Path.from_unsafe_text(arg):resolved() - dependencies := get_dependency_graph(File(path)) - draw_tree(File(path), dependencies) - else if arg:matches($/{id}/): - dependencies := get_dependency_graph(Module(arg)) - draw_tree(Module(arg), dependencies) - else: - say("$\x1b[2mSkipping $arg$\x1b[m") - skip - diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm new file mode 100644 index 00000000..e3354136 --- /dev/null +++ b/examples/tomodeps/tomodeps.tm @@ -0,0 +1,124 @@ +# Show a Tomo dependency graph + +_USAGE := "Usage: dependencies " + +_HELP := " + dependencies: Show a file dependency graph for Tomo source files. + $_USAGE +" + +enum Dependency(File(path:Path), Module(name:Text)) + +func _get_file_dependencies(file:Path)->{Dependency}: + if not file:is_file(): + !! Could not read file: $file + return {:Dependency} + + deps := {:Dependency} + if lines := file:by_line(): + for line in lines: + if line:matches($/use {..}.tm/): + file_import := Path.from_unsafe_text(line:replace($/use {..}/, "\1")):resolved(relative_to=file) + deps:add(Dependency.File(file_import)) + else if line:matches($/use {id}/): + module_name := line:replace($/use {..}/, "\1") + deps:add(Dependency.Module(module_name)) + return deps + +func _build_dependency_graph(dep:Dependency, dependencies:&{Dependency:{Dependency}}): + return if dependencies:has(dep) + + dependencies:set(dep, {:Dependency}) # Placeholder + + dep_deps := when dep is File(path): + _get_file_dependencies(path) + is Module(module): + files_path := (~/.local/src/tomo/$module/lib$module.files):resolved() + if not files_path:is_file(): + !! Could not read file: $files_path + return + + unvisited := {:Path} + if lines := files_path:by_line(): + for line in lines: + tm_path := Path.from_unsafe_text(line):resolved(relative_to=(~/.local/src/tomo/$module/)) + unvisited:add(tm_path) + + module_deps := {:Dependency} + visited := {:Path} + while unvisited.length > 0: + file := unvisited.items[-1] + unvisited:remove(file) + visited:add(file) + + for file_dep in _get_file_dependencies(file): + when file_dep is File(f): + if not visited:has(f): + unvisited:add(f) + is Module(m): + module_deps:add(file_dep) + module_deps + + dependencies:set(dep, dep_deps) + + for dep2 in dep_deps: + _build_dependency_graph(dep2, dependencies) + +func get_dependency_graph(dep:Dependency)->{Dependency:{Dependency}}: + graph := {:Dependency:{Dependency}} + _build_dependency_graph(dep, &graph) + return graph + +func _printable_name(dep:Dependency)->Text: + when dep is Module(module): + return "$(\x1b)[34;1m$module$(\x1b)[m" + is File(f): + f = f:relative() + if f:exists(): + return "$(f.text_content)" + else: + return "$(\x1b)[31;1m$(f.text_content) (not found)$(\x1b)[m" + +func _draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}, already_printed:&{Dependency}, prefix="", is_last=yes): + if already_printed:has(dep): + say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep) ++ " $\x1b[2m(recursive)$\x1b[m") + return + + say(prefix ++ (if is_last: "└── " else: "├── ") ++ _printable_name(dep)) + already_printed:add(dep) + + child_prefix := prefix ++ (if is_last: " " else: "│ ") + + children := dependencies:get(dep):or_else({:Dependency}) + for i,child in children.items: + is_child_last := (i == children.length) + _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) + +func draw_tree(dep:Dependency, dependencies:{Dependency:{Dependency}}): + printed := {:Dependency} + say(_printable_name(dep)) + printed:add(dep) + deps := dependencies:get(dep):or_else({:Dependency}) + for i,child in deps.items: + is_child_last := (i == deps.length) + _draw_tree(child, dependencies, already_printed=&printed, is_last=is_child_last) + +func main(files:[Text]): + if files.length == 0: + exit(" + Please provide at least one file! + $_USAGE + ") + + for arg in files: + if arg:matches($/{..}.tm/): + path := Path.from_unsafe_text(arg):resolved() + dependencies := get_dependency_graph(File(path)) + draw_tree(File(path), dependencies) + else if arg:matches($/{id}/): + dependencies := get_dependency_graph(Module(arg)) + draw_tree(Module(arg), dependencies) + else: + say("$\x1b[2mSkipping $arg$\x1b[m") + skip + diff --git a/examples/vectors.tm b/examples/vectors.tm deleted file mode 100644 index 8560f9df..00000000 --- a/examples/vectors.tm +++ /dev/null @@ -1,119 +0,0 @@ -# A math vector library for 2D and 3D vectors of Nums or Ints - -struct Vec2(x,y:Num): - ZERO := Vec2(0, 0) - func plus(a,b:Vec2; inline)->Vec2: - return Vec2(a.x+b.x, a.y+b.y) - func minus(a,b:Vec2; inline)->Vec2: - return Vec2(a.x-b.x, a.y-b.y) - func times(a,b:Vec2; inline)->Vec2: - return Vec2(a.x*b.x, a.y*b.y) - func dot(a,b:Vec2; inline)->Num: - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:Vec2; inline)->Num: - return a.x*b.y - a.y*b.x - func scaled_by(v:Vec2, k:Num; inline)->Vec2: - return Vec2(v.x*k, v.y*k) - func divided_by(v:Vec2, divisor:Num; inline)->Vec2: - return Vec2(v.x/divisor, v.y/divisor) - func length(v:Vec2; inline)->Num: - return (v.x*v.x + v.y*v.y):sqrt() - func dist(a,b:Vec2; inline)->Num: - return a:minus(b):length() - func angle(v:Vec2; inline)->Num: - return Num.atan2(v.y, v.x) - func norm(v:Vec2; inline)->Vec2: - if v.x == 0 and v.y == 0: - return v - len := v:length() - return Vec2(v.x/len, v.y/len) - func mix(a,b:Vec2, amount:Num)->Vec2: - return Vec2( - amount:mix(a.x, b.x), - amount:mix(a.y, b.y), - ) - -struct Vec3(x,y,z:Num): - ZERO := Vec3(0, 0, 0) - func plus(a,b:Vec3; inline)->Vec3: - return Vec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:Vec3; inline)->Vec3: - return Vec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:Vec3; inline)->Vec3: - return Vec3(a.x*b.x, a.y*b.y, a.z*b.z) - func dot(a,b:Vec3; inline)->Num: - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) - func cross(a,b:Vec3; inline)->Vec3: - return Vec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) - func scaled_by(v:Vec3, k:Num; inline)->Vec3: - return Vec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:Vec3, divisor:Num; inline)->Vec3: - return Vec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:Vec3; inline)->Num: - return (v.x*v.x + v.y*v.y + v.z*v.z):sqrt() - func dist(a,b:Vec3; inline)->Num: - return a:minus(b):length() - func norm(v:Vec3; inline)->Vec3: - if v.x == 0 and v.y == 0 and v.z == 0: - return v - len := v:length() - return Vec3(v.x/len, v.y/len, v.z/len) - func mix(a,b:Vec3, amount:Num)->Vec3: - return Vec3( - amount:mix(a.x, b.x), - amount:mix(a.y, b.y), - amount:mix(a.z, b.z), - ) - - -struct IVec2(x,y:Int): - ZERO := IVec2(0, 0) - func plus(a,b:IVec2; inline)->IVec2: - return IVec2(a.x+b.x, a.y+b.y) - func minus(a,b:IVec2; inline)->IVec2: - return IVec2(a.x-b.x, a.y-b.y) - func times(a,b:IVec2; inline)->IVec2: - return IVec2(a.x*b.x, a.y*b.y) - func dot(a,b:IVec2; inline)->Int: - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:IVec2; inline)->Int: - return a.x*b.y - a.y*b.x - func scaled_by(v:IVec2, k:Int; inline)->IVec2: - return IVec2(v.x*k, v.y*k) - func divided_by(v:IVec2, divisor:Int; inline)->IVec2: - return IVec2(v.x/divisor, v.y/divisor) - func length(v:IVec2; inline)->Num: - return Num.sqrt(v.x*v.x + v.y*v.y) - func dist(a,b:IVec2; inline)->Num: - return a:minus(b):length() - func angle(v:IVec2; inline)->Num: - return Num.atan2(v.y, v.x) - -struct IVec3(x,y,z:Int): - ZERO := IVec3(0, 0, 0) - func plus(a,b:IVec3; inline)->IVec3: - return IVec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:IVec3; inline)->IVec3: - return IVec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:IVec3; inline)->IVec3: - return IVec3(a.x*b.x, a.y*b.y, a.z*b.z) - func dot(a,b:IVec3; inline)->Int: - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) - func cross(a,b:IVec3; inline)->IVec3: - return IVec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) - func scaled_by(v:IVec3, k:Int; inline)->IVec3: - return IVec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:IVec3, divisor:Int; inline)->IVec3: - return IVec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:IVec3; inline)->Num: - return Num.sqrt(v.x*v.x + v.y*v.y + v.z*v.z) - func dist(a,b:IVec3; inline)->Num: - return a:minus(b):length() - -func main(): - >> Vec2(10, 20) - >> Vec2(10, 20) + Vec2(100, 100) - = Vec2(x=110, y=120) - >> Vec3(10, 20, 30) - >> Vec2.ZERO - diff --git a/examples/vectors/vectors.tm b/examples/vectors/vectors.tm new file mode 100644 index 00000000..8560f9df --- /dev/null +++ b/examples/vectors/vectors.tm @@ -0,0 +1,119 @@ +# A math vector library for 2D and 3D vectors of Nums or Ints + +struct Vec2(x,y:Num): + ZERO := Vec2(0, 0) + func plus(a,b:Vec2; inline)->Vec2: + return Vec2(a.x+b.x, a.y+b.y) + func minus(a,b:Vec2; inline)->Vec2: + return Vec2(a.x-b.x, a.y-b.y) + func times(a,b:Vec2; inline)->Vec2: + return Vec2(a.x*b.x, a.y*b.y) + func dot(a,b:Vec2; inline)->Num: + return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + func cross(a,b:Vec2; inline)->Num: + return a.x*b.y - a.y*b.x + func scaled_by(v:Vec2, k:Num; inline)->Vec2: + return Vec2(v.x*k, v.y*k) + func divided_by(v:Vec2, divisor:Num; inline)->Vec2: + return Vec2(v.x/divisor, v.y/divisor) + func length(v:Vec2; inline)->Num: + return (v.x*v.x + v.y*v.y):sqrt() + func dist(a,b:Vec2; inline)->Num: + return a:minus(b):length() + func angle(v:Vec2; inline)->Num: + return Num.atan2(v.y, v.x) + func norm(v:Vec2; inline)->Vec2: + if v.x == 0 and v.y == 0: + return v + len := v:length() + return Vec2(v.x/len, v.y/len) + func mix(a,b:Vec2, amount:Num)->Vec2: + return Vec2( + amount:mix(a.x, b.x), + amount:mix(a.y, b.y), + ) + +struct Vec3(x,y,z:Num): + ZERO := Vec3(0, 0, 0) + func plus(a,b:Vec3; inline)->Vec3: + return Vec3(a.x+b.x, a.y+b.y, a.z+b.z) + func minus(a,b:Vec3; inline)->Vec3: + return Vec3(a.x-b.x, a.y-b.y, a.z-b.z) + func times(a,b:Vec3; inline)->Vec3: + return Vec3(a.x*b.x, a.y*b.y, a.z*b.z) + func dot(a,b:Vec3; inline)->Num: + return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) + func cross(a,b:Vec3; inline)->Vec3: + return Vec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) + func scaled_by(v:Vec3, k:Num; inline)->Vec3: + return Vec3(v.x*k, v.y*k, v.z*k) + func divided_by(v:Vec3, divisor:Num; inline)->Vec3: + return Vec3(v.x/divisor, v.y/divisor, v.z/divisor) + func length(v:Vec3; inline)->Num: + return (v.x*v.x + v.y*v.y + v.z*v.z):sqrt() + func dist(a,b:Vec3; inline)->Num: + return a:minus(b):length() + func norm(v:Vec3; inline)->Vec3: + if v.x == 0 and v.y == 0 and v.z == 0: + return v + len := v:length() + return Vec3(v.x/len, v.y/len, v.z/len) + func mix(a,b:Vec3, amount:Num)->Vec3: + return Vec3( + amount:mix(a.x, b.x), + amount:mix(a.y, b.y), + amount:mix(a.z, b.z), + ) + + +struct IVec2(x,y:Int): + ZERO := IVec2(0, 0) + func plus(a,b:IVec2; inline)->IVec2: + return IVec2(a.x+b.x, a.y+b.y) + func minus(a,b:IVec2; inline)->IVec2: + return IVec2(a.x-b.x, a.y-b.y) + func times(a,b:IVec2; inline)->IVec2: + return IVec2(a.x*b.x, a.y*b.y) + func dot(a,b:IVec2; inline)->Int: + return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + func cross(a,b:IVec2; inline)->Int: + return a.x*b.y - a.y*b.x + func scaled_by(v:IVec2, k:Int; inline)->IVec2: + return IVec2(v.x*k, v.y*k) + func divided_by(v:IVec2, divisor:Int; inline)->IVec2: + return IVec2(v.x/divisor, v.y/divisor) + func length(v:IVec2; inline)->Num: + return Num.sqrt(v.x*v.x + v.y*v.y) + func dist(a,b:IVec2; inline)->Num: + return a:minus(b):length() + func angle(v:IVec2; inline)->Num: + return Num.atan2(v.y, v.x) + +struct IVec3(x,y,z:Int): + ZERO := IVec3(0, 0, 0) + func plus(a,b:IVec3; inline)->IVec3: + return IVec3(a.x+b.x, a.y+b.y, a.z+b.z) + func minus(a,b:IVec3; inline)->IVec3: + return IVec3(a.x-b.x, a.y-b.y, a.z-b.z) + func times(a,b:IVec3; inline)->IVec3: + return IVec3(a.x*b.x, a.y*b.y, a.z*b.z) + func dot(a,b:IVec3; inline)->Int: + return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) + func cross(a,b:IVec3; inline)->IVec3: + return IVec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) + func scaled_by(v:IVec3, k:Int; inline)->IVec3: + return IVec3(v.x*k, v.y*k, v.z*k) + func divided_by(v:IVec3, divisor:Int; inline)->IVec3: + return IVec3(v.x/divisor, v.y/divisor, v.z/divisor) + func length(v:IVec3; inline)->Num: + return Num.sqrt(v.x*v.x + v.y*v.y + v.z*v.z) + func dist(a,b:IVec3; inline)->Num: + return a:minus(b):length() + +func main(): + >> Vec2(10, 20) + >> Vec2(10, 20) + Vec2(100, 100) + = Vec2(x=110, y=120) + >> Vec3(10, 20, 30) + >> Vec2.ZERO + diff --git a/examples/wrap.tm b/examples/wrap.tm deleted file mode 100644 index 54198193..00000000 --- a/examples/wrap.tm +++ /dev/null @@ -1,102 +0,0 @@ -HELP := " - wrap: A tool for wrapping lines of text - - usage: wrap [--help] [files...] [--width=80] [--inplace=no] [--min_split=3] [--no-rewrap] [--hyphen='-'] - --help: Print this message and exit - [files...]: The files to wrap (stdin is used if no files are provided) - --width=N: The width to wrap the text - --inplace: Whether or not to perform the modification in-place or print the output - --min-split=N: The minimum amount of text on either end of a hyphenation split - --rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines - --hyphen='-': The text to use for hyphenation -" - -UNICODE_HYPHEN := \{hyphen} - -func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN)->Text: - if preserve_paragraphs: - paragraphs := text:split($/{2+ nl}/) - if paragraphs.length > 1: - return \n\n:join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs]) - - return text:replace($/$(hyphen)$(\n)/, "") - -func wrap(text:Text, width:Int, min_split=3, hyphen="-")->Text: - if width <= 0: - fail("Width must be a positive integer, not $width") - - if 2*min_split - hyphen.length > width: - fail(" - Minimum word split length ($min_split) is too small for the given wrap width ($width)! - - I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it, - ... and I can't split it without splitting into chunks smaller than $min_split. - ") - - lines := [:Text] - line := "" - for word in text:split($/{whitespace}/): - letters := word:split() - skip if letters.length == 0 - - while not _can_fit_word(line, letters, width): - line_space := width - line.length - if line != "": line_space -= 1 - - if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split: - # Split word with a hyphen: - split := line_space - hyphen.length - split = split _max_ min_split - split = split _min_ (letters.length - min_split) - if line != "": line ++= " " - line ++= ((++) letters:to(split)) ++ hyphen - letters = letters:from(split + 1) - else if line == "": - # Force split word without hyphenation: - if line != "": line ++= " " - line ++= ((++) letters:to(line_space)) - letters = letters:from(line_space + 1) - else: - pass # Move to next line - - lines:insert(line) - line = "" - - if letters.length > 0: - if line != "": line ++= " " - line ++= (++) letters - - if line != "": - lines:insert(line) - - return \n:join(lines) - -func _can_fit_word(line:Text, letters:[Text], width:Int; inline)->Bool: - if line == "": - return letters.length <= width - else: - return line.length + 1 + letters.length <= width - -func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UNICODE_HYPHEN): - if files.length == 0: - files = [(/dev/stdin)] - - for file in files: - text := file:read():or_exit("Could not read file: $(file.text_content)") - - if rewrap: - text = unwrap(text) - - out := if file:is_file() and inplace: - file - else: - (/dev/stdout) - - first := yes - wrapped_paragraphs := [:Text] - for paragraph in text:split($/{2+ nl}/): - wrapped_paragraphs:insert( - wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen) - ) - - out:write(\n\n:join(wrapped_paragraphs) ++ \n) diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm new file mode 100644 index 00000000..54198193 --- /dev/null +++ b/examples/wrap/wrap.tm @@ -0,0 +1,102 @@ +HELP := " + wrap: A tool for wrapping lines of text + + usage: wrap [--help] [files...] [--width=80] [--inplace=no] [--min_split=3] [--no-rewrap] [--hyphen='-'] + --help: Print this message and exit + [files...]: The files to wrap (stdin is used if no files are provided) + --width=N: The width to wrap the text + --inplace: Whether or not to perform the modification in-place or print the output + --min-split=N: The minimum amount of text on either end of a hyphenation split + --rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines + --hyphen='-': The text to use for hyphenation +" + +UNICODE_HYPHEN := \{hyphen} + +func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN)->Text: + if preserve_paragraphs: + paragraphs := text:split($/{2+ nl}/) + if paragraphs.length > 1: + return \n\n:join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs]) + + return text:replace($/$(hyphen)$(\n)/, "") + +func wrap(text:Text, width:Int, min_split=3, hyphen="-")->Text: + if width <= 0: + fail("Width must be a positive integer, not $width") + + if 2*min_split - hyphen.length > width: + fail(" + Minimum word split length ($min_split) is too small for the given wrap width ($width)! + + I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it, + ... and I can't split it without splitting into chunks smaller than $min_split. + ") + + lines := [:Text] + line := "" + for word in text:split($/{whitespace}/): + letters := word:split() + skip if letters.length == 0 + + while not _can_fit_word(line, letters, width): + line_space := width - line.length + if line != "": line_space -= 1 + + if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split: + # Split word with a hyphen: + split := line_space - hyphen.length + split = split _max_ min_split + split = split _min_ (letters.length - min_split) + if line != "": line ++= " " + line ++= ((++) letters:to(split)) ++ hyphen + letters = letters:from(split + 1) + else if line == "": + # Force split word without hyphenation: + if line != "": line ++= " " + line ++= ((++) letters:to(line_space)) + letters = letters:from(line_space + 1) + else: + pass # Move to next line + + lines:insert(line) + line = "" + + if letters.length > 0: + if line != "": line ++= " " + line ++= (++) letters + + if line != "": + lines:insert(line) + + return \n:join(lines) + +func _can_fit_word(line:Text, letters:[Text], width:Int; inline)->Bool: + if line == "": + return letters.length <= width + else: + return line.length + 1 + letters.length <= width + +func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UNICODE_HYPHEN): + if files.length == 0: + files = [(/dev/stdin)] + + for file in files: + text := file:read():or_exit("Could not read file: $(file.text_content)") + + if rewrap: + text = unwrap(text) + + out := if file:is_file() and inplace: + file + else: + (/dev/stdout) + + first := yes + wrapped_paragraphs := [:Text] + for paragraph in text:split($/{2+ nl}/): + wrapped_paragraphs:insert( + wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen) + ) + + out:write(\n\n:join(wrapped_paragraphs) ++ \n) -- cgit v1.2.3