diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-09-01 16:44:58 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-09-01 16:44:58 -0400 |
| commit | ed50c5fefb8892ad2ba5262491669f268ddbd436 (patch) | |
| tree | e93a6a8b7e963c37a001691751d6845d10e0cbf8 /examples | |
| parent | 02a99d24a310c04622a875dcf4b0c6fd2de71332 (diff) | |
Overhaul code to stop keeping examples and libraries in the same repo,
but instead spin each out into its own repo.
Diffstat (limited to 'examples')
45 files changed, 13 insertions, 2515 deletions
diff --git a/examples/README.md b/examples/README.md index 9e9291e5..d527d692 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,19 +4,20 @@ This folder contains some example programs and libraries. ## Example Programs +- [hello.tm](hello.tm): A Hello World program. - [learnxiny.tm](learnxiny.tm): A quick overview of language features in the style of [learnxinyminutes.com](https://learnxinyminutes.com/). -- [game](game/): An example game using raylib. -- [http-server](http-server/): A multithreaded HTTP server. -- [wrap](wrap/): A command-line program to wrap text. +- [game](https://github.com/bruce-hill/tomo-game): An example game using raylib. +- [http-server](https://github.com/bruce-hill/http-server): A multithreaded HTTP server. +- [wrap](https://github.com/bruce-hill/wrap): A command-line program to wrap text. ## Example Libraries Libraries can be installed with `tomo -IL ./library-folder` -- [colorful](colorful/): A DSL useful for rendering terminal colors. -- [coroutines](coroutines/): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64) -- [http](http/): An HTTP library to make basic synchronous HTTP requests. -- [ini](ini/): An INI configuration file reader tool. -- [log](log/): A logging utility. -- [vectors](vectors/): A math vector library. +- [colorful](https://github.com/bruce-hill/tomo-colorful): A DSL useful for rendering terminal colors. +- [coroutines](https://github.com/bruce-hill/tomo-coroutines): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64) +- [http](https://github.com/bruce-hill/tomo-http): An HTTP library to make basic synchronous HTTP requests. +- [ini](https://github.com/bruce-hill/tomo-ini): An INI configuration file reader tool. +- [log](https://github.com/bruce-hill/tomo-log): A logging utility. +- [vectors](https://github.com/bruce-hill/tomo-vectors): A math vector library. diff --git a/examples/colorful/CHANGES.md b/examples/colorful/CHANGES.md deleted file mode 100644 index 093cc077..00000000 --- a/examples/colorful/CHANGES.md +++ /dev/null @@ -1,13 +0,0 @@ -# Version History - -## v1.2 - -- Version bump for patterns. - -## v1.1 - -- Added syntax for `@(strikethrough:...)` or `@(s:...)` - -## v1.0 - -Initial version diff --git a/examples/colorful/README.md b/examples/colorful/README.md deleted file mode 100644 index faded9b1..00000000 --- a/examples/colorful/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Colorful Lang - -Colorful is a `lang` that lets you write colorful text for the terminal without -having to stress about managing state for color highlighting. - -## Grammar - -The grammar looks like this: - -``` -colorful <- ("@(at)" / "@(lparen)" / "@(rparen)" # Escapes - / "@(" attributes ":" colorful ")" # Colorful text - / .)* # Plain text - -attributes <- (attribute ("," attribute)*)? - -attribute <- color # Color defaults to foreground - / "fg=" color # Foreground color - / "bg=" color # Background color - / "ul=" color # Underline color - / "b" / "bold" - / "d" / "dim" - / "u" / "underline" - / "i" / "italic" - / "B" / "blink" - / "r" / "reverse" - # These are rarely supported by terminals: - / "fraktur" - / "frame" - / "encircle" - / "overline" - / "super" / "superscript" - / "sub" / "subscript" - -color <- "black" / "red" / "green" / "yellow" / "blue" / "magenta" / "cyan" / "white" - # All caps colors are "bright" colors (not always supported): - / "BLACK" / "RED" / "GREEN" / "YELLOW" / "BLUE" / "MAGENTA" / "CYAN" / "WHITE" - / "default" - / "#" 6 hex # Values 0x000000-0xFFFFFF - / "#" 3 hex # Values 0x000-0xFFF - / 1-3 digit # Values 0-255 -``` - -## Command Line Usage - -You can run `colorful` as a standalone executable to render colorful text with -ANSI escape sequences so it looks nice on a terminal. - -``` -colorful [--help] [texts...] [--by-line] [--files ...] -``` - -## Library Usage - -`colorful` can also be used as a Tomo library: - -```ini -# modules.ini -[colorful] -version=v1.0 -``` - -```tomo -use colorful - -$Colorful" - @(blue:Welcome to the @(bold:party)!) - We have @(green,bold:colors)! -".print() -``` diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm deleted file mode 100644 index 5b01cfd5..00000000 --- a/examples/colorful/colorful.tm +++ /dev/null @@ -1,220 +0,0 @@ -# Colorful language - -HELP := " - colorful: A domain-specific language for writing colored text to the terminal - Usage: colorful [args...] [--by-line] [--files files...] -" - -CSI := "\033[" - -use patterns - -lang Colorful - convert(text:Text -> Colorful) - text = text.translate({"@"="@(at)", "("="@(lparen)", ")"="@(rparen)"}) - return Colorful.from_text(text) - - convert(i:Int -> Colorful) return Colorful.from_text("$i") - convert(n:Num -> Colorful) return Colorful.from_text("$n") - - func for_terminal(c:Colorful -> Text) - return CSI ++ "m" ++ _for_terminal(c, _TermState()) - - func print(c:Colorful, newline=yes) - say(c.for_terminal(), newline=newline) - - -func main(texts:[Text], files:[Path]=[], by_line=no) - for i,text in texts - colorful := Colorful.from_text(text) - colorful.print(newline=no) - if i == texts.length then say("") - else say(" ", newline=no) - - if texts.length == 0 and files.length == 0 - files = [(/dev/stdin)] - - for file in files - if by_line - for line in file.by_line() or exit("Could not read file: $file") - colorful := Colorful.from_text(line) - colorful.print() - else - colorful := Colorful.from_text(file.read() or exit("Could not read file: $file")) - colorful.print(newline=no) - - -func _for_terminal(c:Colorful, state:_TermState -> Text) - 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_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_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) - else if text == "red" return _Color.Color8Bit(1) - else if text == "green" return _Color.Color8Bit(2) - else if text == "yellow" return _Color.Color8Bit(3) - else if text == "blue" return _Color.Color8Bit(4) - else if text == "magenta" return _Color.Color8Bit(5) - else if text == "cyan" return _Color.Color8Bit(6) - else if text == "white" return _Color.Color8Bit(7) - else if text == "default" return _Color.Default - else if text == "BLACK" return _Color.Bright(0) - else if text == "RED" return _Color.Bright(1) - else if text == "GREEN" return _Color.Bright(2) - else if text == "YELLOW" return _Color.Bright(3) - else if text == "BLUE" return _Color.Bright(4) - else if text == "MAGENTA" return _Color.Bright(5) - else if text == "CYAN" return _Color.Bright(6) - else if text == "WHITE" return _Color.Bright(7) - return none - - func fg(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 7 return "$(30+color)" - else if color >= 0 and color <= 255 return "38;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "38;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Bright(color) - if color <= 7 return "$(90+color)" - is Default - return "39" - fail("Invalid foreground color: '$c'") - - func bg(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 7 return "$(40+color)" - else if color >= 0 and color <= 255 return "48;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "48;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Bright(color) - if color <= 7 return "$(90+color)" - is Default - return "49" - fail("Invalid background color: '$c'") - - func underline(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 255 return "58;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "58;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Default - return "59" - is Bright(color) - pass - fail("Invalid underline color: '$c'") - -func _toggle(sequences:&[Text], cur,new:Bool, apply,unapply:Text; inline) - if new and not cur - sequences.insert(apply) - else if cur and not new - sequences.insert(unapply) - -func _toggle2(sequences:&[Text], cur1,cur2,new1,new2:Bool, apply1,apply2,unapply:Text; inline) - return if new1 == cur1 and new2 == cur2 - if (cur1 and not new1) or (cur2 and not new2) # Gotta wipe at least one - sequences.insert(unapply) - cur1, cur2 = no, no # Wiped out - - if new1 and not cur1 - sequences.insert(apply1) - if new2 and not cur2 - sequences.insert(apply2) - -struct _TermState( - bold=no, dim=no, italic=no, underline=no, blink=no, - reverse=no, conceal=no, strikethrough=no, fraktur=no, frame=no, - encircle=no, overline=no, superscript=no, subscript=no, - bg=_Color.Default, fg=_Color.Default, underline_color=_Color.Default, -) - - func apply(old,new:_TermState -> Text) - sequences : &[Text] - _toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22") - _toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23") - _toggle(sequences, old.underline, new.underline, "4", "24") - _toggle(sequences, old.blink, new.blink, "5", "25") - _toggle(sequences, old.reverse, new.reverse, "7", "27") - _toggle(sequences, old.conceal, new.conceal, "8", "28") - _toggle(sequences, old.strikethrough, new.strikethrough, "9", "29") - _toggle2(sequences, old.frame, old.encircle, new.frame, new.frame, "51", "52", "54") - _toggle(sequences, old.overline, new.overline, "53", "55") - _toggle2(sequences, old.subscript, old.subscript, new.superscript, new.superscript, "73", "74", "75") - - if new.bg != old.bg - sequences.insert(new.bg.bg()) - - if new.fg != old.fg - sequences.insert(new.fg.fg()) - - if new.underline_color != old.underline_color - sequences.insert(new.underline_color.underline()) - - if sequences.length == 0 - return "" - return CSI ++ ";".join(sequences) ++ "m" - -func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text) - if text == "lparen" return "(" - else if text == "rparen" return ")" - else if text == "@" or text == "at" return "@" - parts := ( - text.pattern_captures($Pat/{0+..}:{0+..}/) or - return "@("++_for_terminal(Colorful.from_text(text), prev_state)++")" - ) - attributes := parts[1].split_pattern($Pat/{0+space},{0+space}/) - new_state := prev_state - for attr in attributes - if attr.starts_with("fg=") - new_state.fg = _Color.from_text(attr.from(4))! - else if attr.starts_with("bg=") - new_state.bg = _Color.from_text(attr.from(4))! - else if attr.starts_with("ul=") - new_state.underline_color = _Color.from_text(attr.from(4))! - else if color := _Color.from_text(attr) - new_state.fg = color - else if attr == "b" or attr == "bold" - new_state.bold = yes - else if attr == "d" or attr == "dim" - new_state.dim = yes - else if attr == "i" or attr == "italic" - new_state.italic = yes - else if attr == "u" or attr == "underline" - new_state.underline = yes - else if attr == "s" or attr == "strikethrough" - new_state.strikethrough = yes - else if attr == "B" or attr == "blink" - new_state.blink = yes - else if attr == "r" or attr == "reverse" - new_state.reverse = yes - else if attr == "fraktur" - new_state.fraktur = yes - else if attr == "frame" - new_state.frame = yes - else if attr == "encircle" - new_state.encircle = yes - else if attr == "overline" - new_state.overline = yes - else if attr == "super" or attr == "superscript" - new_state.superscript = yes - else if attr == "sub" or attr == "subscript" - new_state.subscript = yes - else - fail("Invalid attribute: '$attr'") - - result := prev_state.apply(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 diff --git a/examples/colorful/modules.ini b/examples/colorful/modules.ini deleted file mode 100644 index 5e4b5b0a..00000000 --- a/examples/colorful/modules.ini +++ /dev/null @@ -1,2 +0,0 @@ -[patterns] -version=v1.1 diff --git a/examples/colorful/test.colors b/examples/colorful/test.colors deleted file mode 100644 index 314b8b05..00000000 --- a/examples/colorful/test.colors +++ /dev/null @@ -1,9 +0,0 @@ -This is some text that has @(bold:@(red:c)@(yellow:o)@(green:l)@(cyan:o)@(blue:r)@(magenta:s))! - -@(fg=#aaf,b:You can have @(red:nested) color directives and stuff (even in -parens) will be handled @(i:right)) - -@(dim:The output @(bold:ANSI) sequences will be optimal, even if you have -nested stuff like bold and dim) - -(which is distinct from @(bold:BOLD) by itself) diff --git a/examples/coroutines/ACO_LICENSE b/examples/coroutines/ACO_LICENSE deleted file mode 100644 index ef4f82f0..00000000 --- a/examples/coroutines/ACO_LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [2018] [Sen Han <00hnes@gmail.com>] - Copyright [2024] [Bruce Hill <bruce@bruce-hill.com>] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/examples/coroutines/CHANGES.md b/examples/coroutines/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/coroutines/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/coroutines/README.md b/examples/coroutines/README.md deleted file mode 100644 index 644c0e07..00000000 --- a/examples/coroutines/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Tomo Coroutine Library - -This is a coroutine library built on top of a modified version of -[libaco](https://libaco.org). - -## Example Usage - -```ini -# modules.ini -[coroutines] -version=v1.0 -``` - -```tomo -use coroutines - -func main() - co := Coroutine(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() -``` diff --git a/examples/coroutines/aco.c b/examples/coroutines/aco.c deleted file mode 100644 index 258efe28..00000000 --- a/examples/coroutines/aco.c +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2018 Sen Han <00hnes@gmail.com> -// Modifications copyright 2025 Bruce Hill <bruce@bruce-hill.com> -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#define _GNU_SOURCE - -#include "aco.h" -#include <stdint.h> -#include <stdio.h> - -#ifndef public -#define public __attribute__((visibility("default"))) -#endif - -#define aco_size_t_safe_add_assert(a, b) aco_assert((a) + (b) >= (a)) - -static void aco_default_protector_last_word(void *); - -void *(*aco_alloc_fn)(size_t) = malloc; -void (*aco_dealloc_fn)(void *) = free; - -#define aco_alloc(size) \ - ({ \ - void *_ptr = aco_alloc_fn(size); \ - if (aco_unlikely((_ptr) == NULL)) { \ - fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, \ - __PRETTY_FUNCTION__); \ - abort(); \ - } \ - _ptr; \ - }) - -// aco's Global Thread Local Storage variable `co` -public -__thread aco_t *aco_gtls_co; -static __thread aco_cofuncp_t aco_gtls_last_word_fp = aco_default_protector_last_word; - -#ifdef __i386__ -static __thread void *aco_gtls_fpucw_mxcsr[2]; -#elif __x86_64__ -static __thread void *aco_gtls_fpucw_mxcsr[1]; -#else -#error "platform not supporteded yet" -#endif - -public -void aco_runtime_test(void) { -#ifdef __i386__ - _Static_assert(sizeof(void *) == 4, "require 'sizeof(void*) == 4'"); -#elif __x86_64__ - _Static_assert(sizeof(void *) == 8, "require 'sizeof(void*) == 8'"); - _Static_assert(sizeof(__uint128_t) == 16, "require 'sizeof(__uint128_t) == 16'"); -#else -#error "platform not supporteded yet" -#endif - _Static_assert(sizeof(int) >= 4, "require 'sizeof(int) >= 4'"); - aco_assert(sizeof(int) >= 4); - _Static_assert(sizeof(int) <= sizeof(size_t), "require 'sizeof(int) <= sizeof(size_t)'"); - aco_assert(sizeof(int) <= sizeof(size_t)); -} - -#ifdef __x86_64__ -static inline void aco_fast_memcpy(void *dst, const void *src, size_t sz) { - if (((uintptr_t)src & 0x0f) != 0 || ((uintptr_t)dst & 0x0f) != 0 || (sz & 0x0f) != 0x08 || (sz >> 4) > 8) { - memcpy(dst, src, sz); - return; - } - - __uint128_t xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7; - switch (sz >> 4) { - case 0: break; - case 1: - xmm0 = *((__uint128_t *)src + 0); - *((__uint128_t *)dst + 0) = xmm0; - break; - case 2: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - break; - case 3: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - break; - case 4: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - break; - case 5: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - break; - case 6: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - break; - case 7: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - xmm6 = *((__uint128_t *)src + 6); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - *((__uint128_t *)dst + 6) = xmm6; - break; - case 8: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - xmm6 = *((__uint128_t *)src + 6); - xmm7 = *((__uint128_t *)src + 7); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - *((__uint128_t *)dst + 6) = xmm6; - *((__uint128_t *)dst + 7) = xmm7; - break; - } - *((uint64_t *)((uintptr_t)dst + sz - 8)) = *((uint64_t *)((uintptr_t)src + sz - 8)); -} -#endif - -void aco_default_protector_last_word(void *_) { - aco_t *co = aco_get_co(); - // do some log about the offending `co` - fprintf(stderr, "error: aco_default_protector_last_word triggered\n"); - fprintf(stderr, - "error: co:%p should call `aco_exit()` instead of direct " - "`return` in co_fp:%p to finish its execution\n", - co, (void *)co->fp); - aco_assert(0); -} - -public -void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *)) { - aco_alloc_fn = alloc; - aco_dealloc_fn = dealloc; -} - -public -void aco_thread_init(aco_cofuncp_t last_word_co_fp) { - aco_save_fpucw_mxcsr(aco_gtls_fpucw_mxcsr); - - if ((void *)last_word_co_fp != NULL) aco_gtls_last_word_fp = last_word_co_fp; -} - -// This function `aco_funcp_protector` should never be -// called. If it's been called, that means the offending -// `co` didn't call aco_exit(co) instead of `return` to -// finish its execution. -public -void aco_funcp_protector(void) { - if ((void *)(aco_gtls_last_word_fp) != NULL) { - aco_gtls_last_word_fp(NULL); - } else { - aco_default_protector_last_word(NULL); - } - aco_assert(0); -} - -public -aco_shared_stack_t *aco_shared_stack_new(size_t sz) { return aco_shared_stack_new2(sz, 1); } - -public -aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled) { - if (sz == 0) { - sz = 1024 * 1024 * 2; - } - if (sz < 4096) { - sz = 4096; - } - aco_assert(sz > 0); - - size_t u_pgsz = 0; - if (guard_page_enabled) { - // although gcc's Built-in Functions to Perform Arithmetic with - // Overflow Checking is better, but it would require gcc >= 5.0 - long pgsz = sysconf(_SC_PAGESIZE); - // pgsz must be > 0 && a power of two - aco_assert(pgsz > 0 && (((pgsz - 1) & pgsz) == 0)); - u_pgsz = (size_t)((unsigned long)pgsz); - // it should be always true in real life - aco_assert(u_pgsz == (unsigned long)pgsz && ((u_pgsz << 1) >> 1) == u_pgsz); - if (sz <= u_pgsz) { - sz = u_pgsz << 1; - } else { - size_t new_sz; - if ((sz & (u_pgsz - 1)) != 0) { - new_sz = (sz & (~(u_pgsz - 1))); - aco_assert(new_sz >= u_pgsz); - aco_size_t_safe_add_assert(new_sz, (u_pgsz << 1)); - new_sz = new_sz + (u_pgsz << 1); - aco_assert(sz / u_pgsz + 2 == new_sz / u_pgsz); - } else { - aco_size_t_safe_add_assert(sz, u_pgsz); - new_sz = sz + u_pgsz; - aco_assert(sz / u_pgsz + 1 == new_sz / u_pgsz); - } - sz = new_sz; - aco_assert((sz / u_pgsz > 1) && ((sz & (u_pgsz - 1)) == 0)); - } - } - - aco_shared_stack_t *p = aco_alloc(sizeof(aco_shared_stack_t)); - memset(p, 0, sizeof(aco_shared_stack_t)); - - if (guard_page_enabled) { - p->real_ptr = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (aco_unlikely(p->real_ptr == MAP_FAILED)) { - fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); - abort(); - } - p->guard_page_enabled = true; - aco_assert(0 == mprotect(p->real_ptr, u_pgsz, PROT_READ)); - - p->ptr = (void *)(((uintptr_t)p->real_ptr) + u_pgsz); - p->real_sz = sz; - aco_assert(sz >= (u_pgsz << 1)); - p->sz = sz - u_pgsz; - } else { - // p->guard_page_enabled = 0; - p->sz = sz; - p->ptr = aco_alloc(sz); - } - - p->owner = NULL; -#ifdef ACO_USE_VALGRIND - p->valgrind_stk_id = VALGRIND_STACK_REGISTER(p->ptr, (void *)((uintptr_t)p->ptr + p->sz)); -#endif -#if defined(__i386__) || defined(__x86_64__) - uintptr_t u_p = (uintptr_t)(p->sz - (sizeof(void *) << 1) + (uintptr_t)p->ptr); - u_p = (u_p >> 4) << 4; - p->align_highptr = (void *)u_p; - p->align_retptr = (void *)(u_p - sizeof(void *)); - *((void **)(p->align_retptr)) = (void *)(aco_funcp_protector_asm); - aco_assert(p->sz > (16 + (sizeof(void *) << 1) + sizeof(void *))); - p->align_limit = p->sz - 16 - (sizeof(void *) << 1); -#else -#error "platform not supporteded yet" -#endif - return p; -} - -public -void aco_shared_stack_destroy(aco_shared_stack_t *sstk) { - aco_assert(sstk != NULL && sstk->ptr != NULL); -#ifdef ACO_USE_VALGRIND - VALGRIND_STACK_DEREGISTER(sstk->valgrind_stk_id); -#endif - if (sstk->guard_page_enabled) { - aco_assert(0 == munmap(sstk->real_ptr, sstk->real_sz)); - sstk->real_ptr = NULL; - sstk->ptr = NULL; - } else { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk->ptr); - sstk->ptr = NULL; - } - if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk); -} - -public -aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, - void *arg) { - aco_t *p = aco_alloc(sizeof(aco_t)); - memset(p, 0, sizeof(aco_t)); - - if (main_co != NULL) { // non-main co - aco_assertptr(shared_stack); - p->shared_stack = shared_stack; -#ifdef __i386__ - // POSIX.1-2008 (IEEE Std 1003.1-2008) - General Information - Data Types - Pointer Types - // http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/functions/V2_chap02.html#tag_15_12_03 - p->reg[ACO_REG_IDX_RETADDR] = (void *)fp; - // push retaddr - p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr; -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0]; - p->reg[ACO_REG_IDX_FPU + 1] = aco_gtls_fpucw_mxcsr[1]; -#endif -#elif __x86_64__ - p->reg[ACO_REG_IDX_RETADDR] = (void *)fp; - p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr; -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0]; -#endif -#else -#error "platform not supporteded yet" -#endif - p->main_co = main_co; - p->arg = arg; - p->fp = fp; - if (saved_stack_sz == 0) { - saved_stack_sz = 64; - } - p->saved_stack.ptr = aco_alloc(saved_stack_sz); - p->saved_stack.sz = saved_stack_sz; -#if defined(__i386__) || defined(__x86_64__) - p->saved_stack.valid_sz = 0; -#else -#error "platform not supporteded yet" -#endif - return p; - } else { // main co - p->main_co = NULL; - p->arg = arg; - p->fp = fp; - p->shared_stack = NULL; - p->saved_stack.ptr = NULL; - return p; - } - aco_assert(0); -} - -public -aco_attr_no_asan void aco_resume(aco_t *resume_co) { - aco_assert(resume_co != NULL && resume_co->main_co != NULL && !resume_co->is_finished); - if (resume_co->shared_stack->owner != resume_co) { - if (resume_co->shared_stack->owner != NULL) { - aco_t *owner_co = resume_co->shared_stack->owner; - aco_assert(owner_co->shared_stack == resume_co->shared_stack); -#if defined(__i386__) || defined(__x86_64__) - aco_assert(((uintptr_t)(owner_co->shared_stack->align_retptr) >= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])) - && ((uintptr_t)(owner_co->shared_stack->align_highptr) - - (uintptr_t)(owner_co->shared_stack->align_limit) - <= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]))); - owner_co->saved_stack.valid_sz = - (uintptr_t)(owner_co->shared_stack->align_retptr) - (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]); - if (owner_co->saved_stack.sz < owner_co->saved_stack.valid_sz) { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(owner_co->saved_stack.ptr); - owner_co->saved_stack.ptr = NULL; - while (1) { - owner_co->saved_stack.sz = owner_co->saved_stack.sz << 1; - aco_assert(owner_co->saved_stack.sz > 0); - if (owner_co->saved_stack.sz >= owner_co->saved_stack.valid_sz) { - break; - } - } - owner_co->saved_stack.ptr = aco_alloc(owner_co->saved_stack.sz); - } - // TODO: optimize the performance penalty of memcpy function call - // for very short memory span - if (owner_co->saved_stack.valid_sz > 0) { -#ifdef __x86_64__ - aco_fast_memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP], - owner_co->saved_stack.valid_sz); -#else - memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP], owner_co->saved_stack.valid_sz); -#endif - owner_co->saved_stack.ct_save++; - } - if (owner_co->saved_stack.valid_sz > owner_co->saved_stack.max_cpsz) { - owner_co->saved_stack.max_cpsz = owner_co->saved_stack.valid_sz; - } - owner_co->shared_stack->owner = NULL; - owner_co->shared_stack->align_validsz = 0; -#else -#error "platform not supporteded yet" -#endif - } - aco_assert(resume_co->shared_stack->owner == NULL); -#if defined(__i386__) || defined(__x86_64__) - aco_assert(resume_co->saved_stack.valid_sz <= resume_co->shared_stack->align_limit - sizeof(void *)); - // TODO: optimize the performance penalty of memcpy function call - // for very short memory span - if (resume_co->saved_stack.valid_sz > 0) { - void *dst = (void *)((uintptr_t)(resume_co->shared_stack->align_retptr) - resume_co->saved_stack.valid_sz); -#ifdef __x86_64__ - aco_fast_memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz); -#else - memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz); -#endif - resume_co->saved_stack.ct_restore++; - } - if (resume_co->saved_stack.valid_sz > resume_co->saved_stack.max_cpsz) { - resume_co->saved_stack.max_cpsz = resume_co->saved_stack.valid_sz; - } - resume_co->shared_stack->align_validsz = resume_co->saved_stack.valid_sz + sizeof(void *); - resume_co->shared_stack->owner = resume_co; -#else -#error "platform not supporteded yet" -#endif - } - aco_gtls_co = resume_co; - aco_yield_asm(resume_co->main_co, resume_co); - aco_gtls_co = resume_co->main_co; -} - -public -void aco_destroy(aco_t *co) { - aco_assertptr(co); - if (aco_is_main_co(co)) { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co); - } else { - if (co->shared_stack->owner == co) { - co->shared_stack->owner = NULL; - co->shared_stack->align_validsz = 0; - } - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co->saved_stack.ptr); - co->saved_stack.ptr = NULL; - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co); - } -} - -public -void aco_exit_fn(void *_) { aco_exit(); } diff --git a/examples/coroutines/aco.h b/examples/coroutines/aco.h deleted file mode 100644 index 05ba0bdb..00000000 --- a/examples/coroutines/aco.h +++ /dev/null @@ -1,213 +0,0 @@ -// A coroutine library -// Copyright 2018 Sen Han <00hnes@gmail.com> -// Modifications copyright 2025 Bruce Hill <bruce@bruce-hill.com> -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include <limits.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <time.h> -#include <unistd.h> - -#ifdef ACO_USE_VALGRIND -#include <valgrind/valgrind.h> -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define ACO_VERSION_MAJOR 2 -#define ACO_VERSION_MINOR 0 -#define ACO_VERSION_PATCH 0 - -#ifdef __i386__ -#define ACO_REG_IDX_RETADDR 0 -#define ACO_REG_IDX_SP 1 -#define ACO_REG_IDX_BP 2 -#define ACO_REG_IDX_ARG1 0 -#define ACO_REG_IDX_FPU 6 -#elif __x86_64__ -#define ACO_REG_IDX_RETADDR 4 -#define ACO_REG_IDX_SP 5 -#define ACO_REG_IDX_BP 7 -#define ACO_REG_IDX_EDI 8 -#define ACO_REG_IDX_FPU 8 -#else -#error "platform not supported yet" -#endif - -typedef struct { - void *ptr; - size_t sz; - size_t valid_sz; - // max copy size in bytes - size_t max_cpsz; - // copy from shared stack to this saved stack - size_t ct_save; - // copy from this saved stack to shared stack - size_t ct_restore; -} aco_saved_stack_t; - -struct aco_s; -typedef struct aco_s aco_t; - -typedef struct { - void *ptr; - size_t sz; - void *align_highptr; - void *align_retptr; - size_t align_validsz; - size_t align_limit; - aco_t *owner; - - bool guard_page_enabled; - void *real_ptr; - size_t real_sz; - -#ifdef ACO_USE_VALGRIND - unsigned long valgrind_stk_id; -#endif -} aco_shared_stack_t; - -typedef void (*aco_cofuncp_t)(void *); - -struct aco_s { - // cpu registers' state -#ifdef __i386__ -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - void *reg[6]; -#else - void *reg[8]; -#endif -#elif __x86_64__ -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - void *reg[8]; -#else - void *reg[9]; -#endif -#else -#error "platform not supported yet" -#endif - aco_t *main_co; - void *arg; - bool is_finished; - - aco_cofuncp_t fp; - - aco_saved_stack_t saved_stack; - aco_shared_stack_t *shared_stack; -}; - -#define aco_likely(x) (__builtin_expect(!!(x), 1)) - -#define aco_unlikely(x) (__builtin_expect(!!(x), 0)) - -#define aco_assert(EX) ((aco_likely(EX)) ? ((void)0) : (abort())) - -#define aco_assertptr(ptr) ((aco_likely((ptr) != NULL)) ? ((void)0) : (abort())) - -#if defined(aco_attr_no_asan) -#error "aco_attr_no_asan already defined" -#endif -#if defined(ACO_USE_ASAN) -#if defined(__has_feature) -#if __has_feature(__address_sanitizer__) -#define aco_attr_no_asan __attribute__((__no_sanitize_address__)) -#endif -#endif -#if defined(__SANITIZE_ADDRESS__) && !defined(aco_attr_no_asan) -#define aco_attr_no_asan __attribute__((__no_sanitize_address__)) -#endif -#endif -#ifndef aco_attr_no_asan -#define aco_attr_no_asan -#endif - -void aco_runtime_test(void); - -void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *)); - -void aco_thread_init(aco_cofuncp_t last_word_co_fp); - -void aco_yield_asm(aco_t *from_co, aco_t *to_co) __asm__("aco_yield_asm"); // asm - -void aco_save_fpucw_mxcsr(void *p) __asm__("aco_save_fpucw_mxcsr"); // asm - -void aco_funcp_protector_asm(void) __asm__("aco_funcp_protector_asm"); // asm - -void aco_funcp_protector(void); - -aco_shared_stack_t *aco_shared_stack_new(size_t sz); - -aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled); - -void aco_shared_stack_destroy(aco_shared_stack_t *sstk); - -aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, void *arg); - -// aco's Global Thread Local Storage variable `co` -#ifdef __TINYC__ -#error "TinyCC doesn't support thread-local storage!" -#else -extern __thread aco_t *aco_gtls_co; -#endif - -aco_attr_no_asan void aco_resume(aco_t *resume_co); - -// void aco_yield1(aco_t* yield_co); -#define aco_yield1(yield_co) \ - do { \ - aco_assertptr((yield_co)); \ - aco_assertptr((yield_co)->main_co); \ - aco_yield_asm((yield_co), (yield_co)->main_co); \ - } while (0) - -#define aco_yield() aco_yield1(aco_gtls_co) - -#define aco_get_arg() (aco_gtls_co->arg) - -#define aco_get_co() \ - ({ \ - (void)0; \ - aco_gtls_co; \ - }) - -void aco_destroy(aco_t *co); - -#define aco_is_main_co(co) ({ ((co)->main_co) == NULL; }) - -#define aco_exit1(co) \ - do { \ - (co)->is_finished = true; \ - aco_assert((co)->shared_stack->owner == (co)); \ - (co)->shared_stack->owner = NULL; \ - (co)->shared_stack->align_validsz = 0; \ - aco_yield1((co)); \ - aco_assert(0); \ - } while (0) - -#define aco_exit() aco_exit1(aco_gtls_co) - -void aco_exit_fn(void *); - -#ifdef __cplusplus -} -#endif diff --git a/examples/coroutines/acoyield.S b/examples/coroutines/acoyield.S deleted file mode 100644 index 7bc87ff1..00000000 --- a/examples/coroutines/acoyield.S +++ /dev/null @@ -1,208 +0,0 @@ -.text -.globl aco_yield_asm -#if defined(__APPLE__) -#else -.type aco_yield_asm, @function -#endif -.intel_syntax noprefix -aco_yield_asm: -/* - extern void aco_yield_asm(aco_t* from_co, aco_t* to_co); - - struct aco_t { - void* reg[X]; - // ... - } - - reference: - https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI - - pitfall: - http://man7.org/linux/man-pages/man7/signal.7.html - http://man7.org/linux/man-pages/man2/sigaltstack.2.html - - > $ man 7 signal - > ... - > By default, the signal handler is invoked on the normal process - > stack. It is possible to arrange that the signal handler - > uses an alternate stack; see sigaltstack(2) for a discussion of - > how to do this and when it might be useful. - > ... - - This is a BUG example: - https://github.com/Tencent/libco/blob/v1.0/coctx_swap.S#L27 - - proof of correctness: - https://github.com/hnes/libaco - - mxcsr & fpu: - fnstcw * m2byte - Store FPU control word to m2byte without checking for - pending unmasked floating-point exceptions. - - fldcw m2byte - Load FPU control word from m2byte. - - stmxcsr m32 - Store contents of MXCSR register to m32 - - ldmxcsr m32 - Load MXCSR register from m32. -*/ -/* - 0x00 --> 0xff - eip esp ebp edi esi ebx fpucw16 mxcsr32 - 0 4 8 c 10 14 18 1c -*/ -#ifdef __i386__ - mov eax,DWORD PTR [esp+0x4] // from_co - mov edx,DWORD PTR [esp] // retaddr - lea ecx,[esp+0x4] // esp - mov DWORD PTR [eax+0x8],ebp //<ebp - mov DWORD PTR [eax+0x4],ecx //<esp - mov DWORD PTR [eax+0x0],edx //<retaddr - mov DWORD PTR [eax+0xc],edi //<edi - mov ecx,DWORD PTR [esp+0x8] // to_co - mov DWORD PTR [eax+0x10],esi //<esi - mov DWORD PTR [eax+0x14],ebx //<ebx -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fnstcw WORD PTR [eax+0x18] //<fpucw - stmxcsr DWORD PTR [eax+0x1c] //<mxcsr -#endif - mov edx,DWORD PTR [ecx+0x4] //>esp - mov ebp,DWORD PTR [ecx+0x8] //>ebp - mov eax,DWORD PTR [ecx+0x0] //>retaddr - mov edi,DWORD PTR [ecx+0xc] //>edi - mov esi,DWORD PTR [ecx+0x10] //>esi - mov ebx,DWORD PTR [ecx+0x14] //>ebx -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fldcw WORD PTR [ecx+0x18] //>fpucw - ldmxcsr DWORD PTR [ecx+0x1c] //>mxcsr -#endif - xor ecx,ecx - mov esp,edx - mov edx,eax - - // Pass the user-provided argument as first argument: -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - mov eax,DWORD PTR [ecx+0x24] -#else - mov eax,DWORD PTR [ecx+0x28] -#endif - - jmp edx -#elif __x86_64__ -/* - 0x00 --> 0xff - r12 r13 r14 r15 rip rsp rbx rbp fpucw16 mxcsr32 - 0 8 10 18 20 28 30 38 40 44 -*/ - // rdi - from_co | rsi - to_co - mov rdx,QWORD PTR [rsp] // retaddr - lea rcx,[rsp+0x8] // rsp - mov QWORD PTR [rdi+0x0], r12 - mov QWORD PTR [rdi+0x8], r13 - mov QWORD PTR [rdi+0x10],r14 - mov QWORD PTR [rdi+0x18],r15 - mov QWORD PTR [rdi+0x20],rdx // retaddr - mov QWORD PTR [rdi+0x28],rcx // rsp - mov QWORD PTR [rdi+0x30],rbx - mov QWORD PTR [rdi+0x38],rbp -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fnstcw WORD PTR [rdi+0x40] - stmxcsr DWORD PTR [rdi+0x44] -#endif - mov r12,QWORD PTR [rsi+0x0] - mov r13,QWORD PTR [rsi+0x8] - mov r14,QWORD PTR [rsi+0x10] - mov r15,QWORD PTR [rsi+0x18] - mov rax,QWORD PTR [rsi+0x20] // retaddr - mov rcx,QWORD PTR [rsi+0x28] // rsp - mov rbx,QWORD PTR [rsi+0x30] - mov rbp,QWORD PTR [rsi+0x38] -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fldcw WORD PTR [rsi+0x40] - ldmxcsr DWORD PTR [rsi+0x44] -#endif - - // Pass the user-provided argument as first argument: -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - mov rdi,QWORD PTR [rsi+0x48] -#else - mov rdi,QWORD PTR [rsi+0x50] -#endif - - mov rsp,rcx - jmp rax -#else - #error "platform not supported" -#endif - -.globl aco_save_fpucw_mxcsr -#if defined(__APPLE__) -#else -.type aco_save_fpucw_mxcsr, @function -#endif -.intel_syntax noprefix -aco_save_fpucw_mxcsr: -#ifdef __i386__ - mov eax,DWORD PTR [esp+0x4] // ptr - fnstcw WORD PTR [eax] - stmxcsr DWORD PTR [eax+0x4] - ret -#elif __x86_64__ - fnstcw WORD PTR [rdi] - stmxcsr DWORD PTR [rdi+0x4] - ret -#else - #error "platform not supported" -#endif - -#if defined(__APPLE__) -.globl _abort -.globl _aco_funcp_protector -#else -.globl abort -.globl aco_funcp_protector -#endif - -.globl aco_funcp_protector_asm -#if defined(__APPLE__) -#else -.type aco_funcp_protector_asm, @function -#endif -.intel_syntax noprefix -aco_funcp_protector_asm: -#ifdef __i386__ - and esp,0xfffffff0 - #if defined(__APPLE__) - call _aco_funcp_protector - call _abort - #else - #if defined(__pic__) || defined(__PIC__) - call aco_funcp_protector@PLT - call abort@PLT - #else - call aco_funcp_protector - call abort - #endif - #endif - ret -#elif __x86_64__ - and rsp,0xfffffffffffffff0 - #if defined(__APPLE__) - call _aco_funcp_protector - call _abort - #else - #if defined(__pic__) || defined(__PIC__) - call aco_funcp_protector@PLT - call abort@PLT - #else - call aco_funcp_protector - call abort - #endif - #endif - ret -#else - #error "platform not supported" -#endif diff --git a/examples/coroutines/coroutines.tm b/examples/coroutines/coroutines.tm deleted file mode 100644 index b530a685..00000000 --- a/examples/coroutines/coroutines.tm +++ /dev/null @@ -1,67 +0,0 @@ -# This is a coroutine library that uses libaco (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 ./aco.h -use ./aco.c -use ./acoyield.S - -func main() - say("Example usage") - co := Coroutine(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() - -struct aco_t(; extern, opaque) -struct aco_shared_stack_t(; extern, opaque) - -_main_co : @aco_t? = none -_shared_stack : @aco_shared_stack_t? = none - -struct Coroutine(co:@aco_t) - convert(fn:func() -> Coroutine) - if not _main_co - _init() - - main_co := _main_co - shared_stack := _shared_stack - aco_ptr := C_code:@aco_t( - aco_create(@main_co, @shared_stack, 0, (void*)@fn.fn, @fn.userdata) - ) - return Coroutine(aco_ptr) - - func is_finished(co:Coroutine->Bool; inline) - return C_code:Bool(((aco_t*)@co.co)->is_finished) - - func resume(co:Coroutine->Bool) - if co.is_finished() - return no - C_code { aco_resume(@co.co); } - return yes - -func _init() - C_code { - aco_set_allocator(GC_malloc, NULL); - aco_thread_init(aco_exit_fn); - } - _main_co = C_code:@aco_t(aco_create(NULL, NULL, 0, NULL, NULL)) - - _shared_stack = C_code:@aco_shared_stack_t(aco_shared_stack_new(0)) - -func yield(; inline) - C_code { - aco_yield(); - } - diff --git a/examples/game/CHANGES.md b/examples/game/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/game/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/game/Makefile b/examples/game/Makefile deleted file mode 100644 index 7cf46ce6..00000000 --- a/examples/game/Makefile +++ /dev/null @@ -1,17 +0,0 @@ - - -game: game.tm box.tm color.tm player.tm world.tm - tomo -e game.tm - -# Disable built-in makefile rules: -%: %.c -%.o: %.c -%: %.o - -clean: - rm -vf game *.tm.* - -play: game - ./game - -.PHONY: play, clean diff --git a/examples/game/README.md b/examples/game/README.md deleted file mode 100644 index 475a8299..00000000 --- a/examples/game/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Example Game - -This is a simple example game that uses [raylib](https://www.raylib.com/) to -demonstrate a project that spans multiple files and showcases some game -programming concepts used in idiomatic Tomo code. It also showcases how to -interact with an external C library. To run the game: - -```bash -tomo game.tm -``` - -An example [Makefile](Makefile) is also provided if you want to use `make` to -build the game and `make clean` to clean up the built files. diff --git a/examples/game/box.tm b/examples/game/box.tm deleted file mode 100644 index 41ae10e5..00000000 --- a/examples/game/box.tm +++ /dev/null @@ -1,7 +0,0 @@ -# Defines a struct representing boxes on the terrain -use ./world.tm -use ./raylib.tm - -struct Box(pos:Vector2, size=Vector2(50, 50), color=Color(0x80,0x80,0x80)) - func draw(b:Box) - DrawRectangleV(b.pos, b.size, b.color) diff --git a/examples/game/game.tm b/examples/game/game.tm deleted file mode 100644 index f82e4f40..00000000 --- a/examples/game/game.tm +++ /dev/null @@ -1,29 +0,0 @@ -# This game demo uses Raylib to present a simple maze-type game -use ./raylib.tm -use ./world.tm - -func main(map=(./map.txt)) - InitWindow(1600, 900, CString("raylib [core] example - 2d camera")) - - map_contents := map.read() or exit("Could not find the game map: $map") - - world := @World( - player=@Player(Vector2(0,0), Vector2(0,0)), - goal=@Box(Vector2(0,0), Vector2(50,50), color=Color(0x10,0xa0,0x10)), - boxes=@[], - ) - world.load_map(map_contents) - - SetTargetFPS(60) - - while not WindowShouldClose() - dt := GetFrameTime() - world.update(dt) - - BeginDrawing() - ClearBackground(Color(0xCC, 0xCC, 0xCC, 0xFF)) - world.draw() - EndDrawing() - - CloseWindow() - diff --git a/examples/game/map.txt b/examples/game/map.txt deleted file mode 100644 index 46fccd09..00000000 --- a/examples/game/map.txt +++ /dev/null @@ -1,18 +0,0 @@ -################################ -#@ # # # -# #### #### # # #### ##### -# # # # # # -# ####### # ####### # # ### -# # # # # # # -# #### ########## # #### # -# # # # # # # -#### # # # # # # # # ## -# # # # # # # # -####### ########## # ######## -# # # # # -# ########## #### # # # # -# # # # # # # # # -# ####### # # ########## # -# # # # # # # ? # -# # # # # # # -################################ diff --git a/examples/game/player.tm b/examples/game/player.tm deleted file mode 100644 index 2e5e54f6..00000000 --- a/examples/game/player.tm +++ /dev/null @@ -1,28 +0,0 @@ -# Defines a struct representing the player, which is controlled by WASD keys -use ./world.tm -use ./raylib.tm - -struct Player(pos,prev_pos:Vector2) - WALK_SPEED := Num32(500.) - ACCEL := Num32(0.3) - FRICTION := Num32(0.99) - SIZE := Vector2(30, 30) - COLOR := Color(0x60, 0x60, 0xbF) - - func update(p:@Player) - target_x := C_code:Num32( - (Num32_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0)) - ) - target_y := C_code:Num32( - (Num32_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0)) - ) - target_vel := Vector2(target_x, target_y).norm() * Player.WALK_SPEED - - vel := (p.pos - p.prev_pos)/World.DT - vel *= Player.FRICTION - vel = vel.mix(target_vel, Player.ACCEL) - - p.prev_pos, p.pos = p.pos, p.pos + World.DT*vel - - func draw(p:Player) - DrawRectangleV(p.pos, Player.SIZE, Player.COLOR) diff --git a/examples/game/raylib.tm b/examples/game/raylib.tm deleted file mode 100644 index b2ba53d7..00000000 --- a/examples/game/raylib.tm +++ /dev/null @@ -1,63 +0,0 @@ -# Raylib wrapper for some functions and structs -use -lraylib -use <raylib.h> -use <raymath.h> - -struct Color(r,g,b:Byte,a=Byte(255); extern) -struct Rectangle(x,y,width,height:Num32; extern) - func draw(r:Rectangle, color:Color) - DrawRectangleRec(r, color) - -struct Vector2(x,y:Num32; extern) - ZERO := Vector2(0, 0) - func plus(a,b:Vector2->Vector2; inline) - return Vector2(a.x+b.x, a.y+b.y) - func minus(a,b:Vector2->Vector2; inline) - return Vector2(a.x-b.x, a.y-b.y) - func times(a,b:Vector2->Vector2; inline) - return Vector2(a.x*b.x, a.y*b.y) - func negative(v:Vector2->Vector2; inline) - return Vector2(-v.x, -v.y) - func dot(a,b:Vector2->Num32; inline) - return ((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)) - func cross(a,b:Vector2->Num32; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:Vector2, k:Num32->Vector2; inline) - return Vector2(v.x*k, v.y*k) - func divided_by(v:Vector2, divisor:Num32->Vector2; inline) - return Vector2(v.x/divisor, v.y/divisor) - func length(v:Vector2->Num32; inline) - return (v.x*v.x + v.y*v.y).sqrt() - func dist(a,b:Vector2->Num32; inline) - return a.minus(b).length() - func angle(v:Vector2->Num32; inline) - return Num32.atan2(v.y, v.x) - func norm(v:Vector2->Vector2; inline) - if v.x == 0 and v.y == 0 - return v - len := v.length() - return Vector2(v.x/len, v.y/len) - func rotated(v:Vector2, radians:Num32 -> Vector2) - cos := radians.cos() or return v - sin := radians.sin() or return v - return Vector2(cos*v.x - sin*v.y, sin*v.x + cos*v.y) - func mix(a,b:Vector2, amount:Num32 -> Vector2) - return Vector2( - amount.mix(a.x, b.x), - amount.mix(a.y, b.y), - ) - -extern InitWindow : func(width:Int32, height:Int32, title:CString) -extern SetTargetFPS : func(fps:Int32) -extern WindowShouldClose : func(->Bool) -extern GetFrameTime : func(->Num32) -extern BeginDrawing : func() -extern EndDrawing : func() -extern CloseWindow : func() -extern ClearBackground : func(color:Color) -extern DrawRectangle : func(x,y,width,height:Int32, color:Color) -extern DrawRectangleRec : func(rec:Rectangle, color:Color) -extern DrawRectangleV : func(pos:Vector2, size:Vector2, color:Color) -extern DrawText : func(text:CString, x,y:Int32, text_height:Int32, color:Color) -extern GetScreenWidth : func(->Int32) -extern GetScreenHeight : func(->Int32) diff --git a/examples/game/world.tm b/examples/game/world.tm deleted file mode 100644 index 0de8ea4b..00000000 --- a/examples/game/world.tm +++ /dev/null @@ -1,88 +0,0 @@ -# This file defines a World object for keeping track of everything, as well -# as the collision logic. -use ./player.tm -use ./raylib.tm -use ./box.tm - -# Return a displacement relative to `a` that will push it out of `b` -func solve_overlap(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2 -> Vector2) - a_left := a_pos.x - a_right := a_pos.x + a_size.x - a_top := a_pos.y - a_bottom := a_pos.y + a_size.y - - b_left := b_pos.x - b_right := b_pos.x + b_size.x - b_top := b_pos.y - b_bottom := b_pos.y + b_size.y - - # Calculate the overlap in each dimension - overlap_x := (a_right _min_ b_right) - (a_left _max_ b_left) - overlap_y := (a_bottom _min_ b_bottom) - (a_top _max_ b_top) - - # If either axis is not overlapping, then there is no collision: - if overlap_x <= 0 or overlap_y <= 0 - return Vector2(0, 0) - - if overlap_x < overlap_y - if a_right > b_left and a_right < b_right - return Vector2(-(overlap_x), 0) - else if a_left < b_right and a_left > b_left - return Vector2(overlap_x, 0) - else - if a_top < b_bottom and a_top > b_top - return Vector2(0, overlap_y) - else if a_bottom > b_top and a_bottom < b_bottom - return Vector2(0, -overlap_y) - - return Vector2(0, 0) - -struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=Num32(0.0), won=no) - DT := (Num32(1.)/Num32(60.)) - STIFFNESS := Num32(0.3) - - func update(w:@World, dt:Num32) - w.dt_accum += dt - while w.dt_accum > 0 - w.update_once() - w.dt_accum -= World.DT - - func update_once(w:@World) - w.player.update() - - if solve_overlap(w.player.pos, Player.SIZE, w.goal.pos, w.goal.size) != Vector2(0,0) - w.won = yes - - # Resolve player overlapping with any boxes: - for i in 3 - for b in w.boxes - w.player.pos += World.STIFFNESS * solve_overlap(w.player.pos, Player.SIZE, b.pos, b.size) - - func draw(w:@World) - for b in w.boxes - b.draw() - w.goal.draw() - w.player.draw() - - if w.won - DrawText(CString("WINNER"), GetScreenWidth()/Int32(2)-Int32(48*3), GetScreenHeight()/Int32(2)-Int32(24), 48, Color(0,0,0)) - - func load_map(w:@World, map:Text) - if map.has("[]") - map = map.translate({"[]"="#", "@ "="@", " "=" "}) - w.boxes = @[] - box_size := Vector2(50., 50.) - for y,line in map.lines() - for x,cell in line.split() - if cell == "#" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - box := @Box(pos, size=box_size, color=Color(0x80,0x80,0x80)) - w.boxes.insert(box) - else if cell == "@" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - pos += box_size/Num32(2) - Player.SIZE/Num32(2) - w.player = @Player(pos,pos) - else if cell == "?" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - w.goal.pos = pos - diff --git a/examples/hello.tm b/examples/hello.tm new file mode 100644 index 00000000..09ee3ab4 --- /dev/null +++ b/examples/hello.tm @@ -0,0 +1,3 @@ +# A simple hello world program: +func main() + say("Hello world!") diff --git a/examples/http-server/CHANGES.md b/examples/http-server/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/http-server/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/http-server/README.md b/examples/http-server/README.md deleted file mode 100644 index 78c8d793..00000000 --- a/examples/http-server/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# HTTP Server - -This is a simple multithreaded Tomo HTTP server that can be run like this: - -``` -tomo -e http-server.tm -./http-server ./sample-site -``` diff --git a/examples/http-server/connection-queue.tm b/examples/http-server/connection-queue.tm deleted file mode 100644 index c56069e1..00000000 --- a/examples/http-server/connection-queue.tm +++ /dev/null @@ -1,25 +0,0 @@ -use pthreads - -func _assert_success(name:Text, val:Int32; inline) - fail("$name() failed!") if val < 0 - -struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(), _cond=pthread_cond_t.new()) - func enqueue(queue:ConnectionQueue, connection:Int32) - queue._mutex.lock() - queue._connections.insert(connection) - queue._mutex.unlock() - queue._cond.signal() - - - func dequeue(queue:ConnectionQueue -> Int32) - conn : Int32? - - queue._mutex.lock() - - while queue._connections.length == 0 - queue._cond.wait(queue._mutex) - - conn = queue._connections.pop(1) - queue._mutex.unlock() - queue._cond.signal() - return conn! diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm deleted file mode 100644 index 8e8aff7e..00000000 --- a/examples/http-server/http-server.tm +++ /dev/null @@ -1,149 +0,0 @@ -#!/bin/env tomo - -# This file provides an HTTP server module and standalone executable - -use <stdio.h> -use <stdlib.h> -use <string.h> -use <unistd.h> -use <arpa/inet.h> -use <err.h> - -use commands -use pthreads -use patterns - -use ./connection-queue.tm - -func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16) - connections := ConnectionQueue() - workers : &[@pthread_t] - for i in num_threads - workers.insert(pthread_t.new(func() - repeat - connection := connections.dequeue() - request_text := C_code:Text( - Text_t request = EMPTY_TEXT; - char buf[1024] = {}; - for (ssize_t n; (n = read(@connection, buf, sizeof(buf) - 1)) > 0; ) { - buf[n] = 0; - request = Text$concat(request, Text$from_strn(buf, n)); - if (request.length > 1000000 || strstr(buf, "\r\n\r\n")) - break; - } - request - ) - - request := HTTPRequest.from_text(request_text) or skip - response := handler(request).bytes() - C_code { - if (@response.stride != 1) - List$compact(&@response, 1); - write(@connection, @response.data, @response.length); - close(@connection); - } - )) - - - sock := C_code:Int32( - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) err(1, "Couldn't connect to socket!"); - - int opt = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) - err(1, "Couldn't set socket option"); - - struct sockaddr_in addr = {AF_INET, htons(@port), INADDR_ANY}; - if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) - err(1, "Couldn't bind to socket"); - if (listen(s, 8) < 0) - err(1, "Couldn't listen on socket"); - - s - ) - - repeat - conn := C_code:Int32(accept(@sock, NULL, NULL)) - stop if conn < 0 - connections.enqueue(conn) - - say("Shutting down...") - for w in workers - w.cancel() - -struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Text) - func from_text(text:Text -> HTTPRequest?) - m := text.pattern_captures($Pat'{word} {..} HTTP/{..}{crlf}{..}') or return none - method := m[1] - path := m[2].replace_pattern($Pat'{2+ /}', '/') - version := m[3] - rest := m[-1].pattern_captures($Pat/{..}{2 crlf}{0+ ..}/) or return none - headers := rest[1].split_pattern($Pat/{crlf}/) - body := rest[-1] - return HTTPRequest(method, path, version, headers, body) - -struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers:{Text=Text}={}) - func bytes(r:HTTPResponse -> [Byte]) - body_bytes := r.body.bytes() - extra_headers := (++: "$k: $v\r\n" for k,v in r.headers) or "" - return " - HTTP/1.1 $(r.status) OK\r - Content-Length: $(body_bytes.length + 2)\r - Content-Type: $(r.content_type)\r - Connection: close\r - $extra_headers - \r\n - ".bytes() ++ body_bytes - -func _content_type(file:Path -> Text) - when file.extension() is "html" return "text/html" - is "tm" return "text/html" - is "js" return "text/javascript" - is "css" return "text/css" - else return "text/plain" - -enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text)) - func respond(entry:RouteEntry, request:HTTPRequest -> HTTPResponse) - when entry is ServeFile(file) - body := if file.can_execute() - Command(Text(file)).get_output()! - else - file.read()! - return HTTPResponse(body, content_type=_content_type(file)) - is Redirect(destination) - return HTTPResponse("Found", 302, headers={"Location"=destination}) - -func load_routes(directory:Path -> {Text=RouteEntry}) - routes : &{Text=RouteEntry} - for file in (directory ++ (./*)).glob() - skip unless file.is_file() - contents := file.read() or skip - server_path := "/" ++ "/".join(file.relative_to(directory).components) - if file.base_name() == "index.html" - canonical := server_path.without_suffix("index.html") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else if file.extension() == "html" - canonical := server_path.without_suffix(".html") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else if file.extension() == "tm" - canonical := server_path.without_suffix(".tm") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else - routes[server_path] = ServeFile(file) - return routes[] - -func main(directory:Path, port=Int32(8080)) - say("Serving on port $port") - routes := load_routes(directory) - say("Hosting: $routes") - - serve(port, func(request:HTTPRequest) - if handler := routes[request.path] - return handler.respond(request) - else - return HTTPResponse("Not found!", 404) - ) - diff --git a/examples/http-server/modules.ini b/examples/http-server/modules.ini deleted file mode 100644 index 171e11d2..00000000 --- a/examples/http-server/modules.ini +++ /dev/null @@ -1,8 +0,0 @@ -[pthreads] -version=v1.0 - -[patterns] -version=v1.0 - -[commands] -version=v1.0 diff --git a/examples/http-server/sample-site/foo.html b/examples/http-server/sample-site/foo.html deleted file mode 100644 index 162a7146..00000000 --- a/examples/http-server/sample-site/foo.html +++ /dev/null @@ -1,6 +0,0 @@ -<!DOCTYPE HTML> -<html> - <body> - This is the <b>foo</b> page. - </body> -</html> diff --git a/examples/http-server/sample-site/hello.txt b/examples/http-server/sample-site/hello.txt deleted file mode 100644 index e965047a..00000000 --- a/examples/http-server/sample-site/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello diff --git a/examples/http-server/sample-site/index.html b/examples/http-server/sample-site/index.html deleted file mode 100644 index 8e1573bb..00000000 --- a/examples/http-server/sample-site/index.html +++ /dev/null @@ -1,16 +0,0 @@ -<!DOCTYPE HTML> -<html> - <head> - <title>HTTP Example</title> - <link rel="stylesheet" href="styles.css"> - </head> - <body> - <p> - Hello <b>world!</b> - </p> - - <p> - Try going to <a href="/random">/random</a> or <a href="/foo">/foo</a> or <a href="/hello.txt">/hello.txt</a> - </p> - </body> -</html> diff --git a/examples/http-server/sample-site/random.tm b/examples/http-server/sample-site/random.tm deleted file mode 100755 index 153ac2af..00000000 --- a/examples/http-server/sample-site/random.tm +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/env tomo -use random - -func main() - say(" - <!DOCTYPE HTML> - <html> - <head> - <title>Random Number</title> - <link rel="stylesheet" href="styles.css"> - </head> - <body> - <h1>Random Number</h1> - Your random number is: $(random.int(1,100)) - </body> - </html> - ") diff --git a/examples/http-server/sample-site/styles.css b/examples/http-server/sample-site/styles.css deleted file mode 100644 index f15d25de..00000000 --- a/examples/http-server/sample-site/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -body{ - margin:40px auto; - max-width:650px; - line-height:1.6; - font-size:18px; - color:#444; - padding:0 10px; -} -h1,h2,h3{ - line-height:1.2 -} diff --git a/examples/http/CHANGES.md b/examples/http/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/http/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/http/http.tm b/examples/http/http.tm deleted file mode 100644 index 3fe41ae2..00000000 --- a/examples/http/http.tm +++ /dev/null @@ -1,111 +0,0 @@ -# A simple HTTP library built using CURL - -use -lcurl -use <curl/curl.h> - -struct HTTPResponse(code:Int, body:Text) - -enum _Method(GET, POST, PUT, PATCH, DELETE) - -func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse) - chunks : @[Text] - save_chunk := func(chunk:CString, size:Int64, n:Int64) - chunks.insert(C_code:Text(Text$from_strn(@chunk, @size*@n))) - return n*size - - C_code { - CURL *curl = curl_easy_init(); - struct curl_slist *chunk = NULL; - curl_easy_setopt(curl, CURLOPT_URL, @(CString(url))); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, @save_chunk.fn); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, @save_chunk.userdata); - } - - defer - C_code { - if (chunk) - curl_slist_free_all(chunk); - curl_easy_cleanup(curl); - } - - when method is POST - C_code { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - } - if posting := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(posting))); - } - is PUT - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - } - if putting := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(putting))); - } - is PATCH - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - } - if patching := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(patching))); - } - is DELETE - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - } - else - pass - - for header in headers - C_code { - chunk = curl_slist_append(chunk, @(CString(header))); - } - - C_code { - if (chunk) - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); - } - - code := Int64(0) - C_code { - 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); - } - return HTTPResponse(Int(code), "".join(chunks)) - -func get(url:Text, headers:[Text]=[] -> HTTPResponse) - return _send(GET, url, none, 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 patch(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(PATCH, url, data, headers) - -func delete(url:Text, data:Text?=none, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(DELETE, url, data, headers) - -func main() - say("GET:") - say(get("https://httpbin.org/get").body) - say("Waiting 1sec...") - sleep(1) - say("POST:") - say(post("https://httpbin.org/post", `{"key": "value"}`).body) - say("Waiting 1sec...") - sleep(1) - say("PUT:") - say(put("https://httpbin.org/put", `{"key": "value"}`).body) - say("Waiting 1sec...") - sleep(1) - say("DELETE:") - say(delete("https://httpbin.org/delete").body) diff --git a/examples/ini/CHANGES.md b/examples/ini/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/ini/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm deleted file mode 100644 index 4dc27725..00000000 --- a/examples/ini/ini.tm +++ /dev/null @@ -1,61 +0,0 @@ - -use patterns - -_USAGE := " - Usage: ini <filename> "[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)\[]") - sections : @{Text=@{Text=Text}} - current_section : @{Text=Text} - - # Line wraps: - 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_pattern($Pat/[?]/) - section_name := line.replace($Pat/[?]/, "@1").trim().lower() - current_section = @{} - sections[section_name] = current_section - 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[]} - -func main(path:Path, key:Text?) - keys := (key or "").split($|/|) - if keys.length > 2 - exit(" - Too many arguments! - $_USAGE - ") - - data := parse_ini(path) - if keys.length < 1 or keys[1] == '*' - say("$data") - return - - section := keys[1].lower() - section_data := data[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] == '*' - say("$section_data") - return - - section_key := keys[2].lower() - value := section_data[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/modules.ini b/examples/ini/modules.ini deleted file mode 100644 index fb52a859..00000000 --- a/examples/ini/modules.ini +++ /dev/null @@ -1,2 +0,0 @@ -[patterns] -version=v1.0 diff --git a/examples/ini/test.ini b/examples/ini/test.ini deleted file mode 100644 index 782dc76f..00000000 --- a/examples/ini/test.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/log/CHANGES.md b/examples/log/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/log/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/log/log.tm b/examples/log/log.tm deleted file mode 100644 index 4b7893fd..00000000 --- a/examples/log/log.tm +++ /dev/null @@ -1,50 +0,0 @@ -use <time.h> -use <stdio.h> - -timestamp_format := CString("%F %T") - -logfiles : @|Path| - -func _timestamp(->Text) - c_str := C_code: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/vectors/CHANGES.md b/examples/vectors/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/vectors/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/vectors/vectors.tm b/examples/vectors/vectors.tm deleted file mode 100644 index 1fd0c4fa..00000000 --- a/examples/vectors/vectors.tm +++ /dev/null @@ -1,136 +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->Vec2; inline) - return Vec2(a.x+b.x, a.y+b.y) - func minus(a,b:Vec2->Vec2; inline) - return Vec2(a.x-b.x, a.y-b.y) - func times(a,b:Vec2->Vec2; inline) - return Vec2(a.x*b.x, a.y*b.y) - func negative(v:Vec2->Vec2; inline) - return Vec2(-v.x, -v.y) - func dot(a,b:Vec2->Num; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:Vec2->Num; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:Vec2, k:Num->Vec2; inline) - return Vec2(v.x*k, v.y*k) - func divided_by(v:Vec2, divisor:Num->Vec2; inline) - return Vec2(v.x/divisor, v.y/divisor) - func length(v:Vec2->Num; inline) - return (v.x*v.x + v.y*v.y).sqrt() - func dist(a,b:Vec2->Num; inline) - return a.minus(b).length() - func angle(v:Vec2->Num; inline) - return Num.atan2(v.y, v.x) - func norm(v:Vec2->Vec2; inline) - if v.x == 0 and v.y == 0 - return v - len := v.length() - return Vec2(v.x/len, v.y/len) - func rotated(v:Vec2, radians:Num -> Vec2) - cos := radians.cos() or return v - sin := radians.sin() or return v - return Vec2(cos*v.x - sin*v.y, sin*v.x + cos*v.y) - 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->Vec3; inline) - return Vec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:Vec3->Vec3; inline) - return Vec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:Vec3->Vec3; inline) - return Vec3(a.x*b.x, a.y*b.y, a.z*b.z) - func negative(v:Vec3->Vec3; inline) - return Vec3(-v.x, -v.y, -v.z) - func dot(a,b:Vec3->Num; inline) - 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->Vec3; inline) - 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->Vec3; inline) - return Vec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:Vec3, divisor:Num->Vec3; inline) - return Vec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:Vec3->Num; inline) - return (v.x*v.x + v.y*v.y + v.z*v.z).sqrt() - func dist(a,b:Vec3->Num; inline) - return a.minus(b).length() - func norm(v:Vec3->Vec3; inline) - 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->IVec2; inline) - return IVec2(a.x+b.x, a.y+b.y) - func minus(a,b:IVec2->IVec2; inline) - return IVec2(a.x-b.x, a.y-b.y) - func times(a,b:IVec2->IVec2; inline) - return IVec2(a.x*b.x, a.y*b.y) - func negative(v:IVec2->IVec2; inline) - return IVec2(-v.x, -v.y) - func dot(a,b:IVec2->Int; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:IVec2->Int; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:IVec2, k:Int->IVec2; inline) - return IVec2(v.x*k, v.y*k) - func divided_by(v:IVec2, divisor:Int->IVec2; inline) - return IVec2(v.x/divisor, v.y/divisor) - func length(v:IVec2->Num; inline) - x := Num(v.x) - y := Num(v.y) - return Num.sqrt(x*x + y*y) - func dist(a,b:IVec2->Num; inline) - return a.minus(b).length() - func angle(v:IVec2->Num; inline) - return Num.atan2(Num(v.y), Num(v.x)) - -struct IVec3(x,y,z:Int) - ZERO := IVec3(0, 0, 0) - func plus(a,b:IVec3->IVec3; inline) - return IVec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:IVec3->IVec3; inline) - return IVec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:IVec3->IVec3; inline) - return IVec3(a.x*b.x, a.y*b.y, a.z*b.z) - func negative(v:IVec3->IVec3; inline) - return IVec3(-v.x, -v.y, -v.z) - func dot(a,b:IVec3->Int; inline) - 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->IVec3; inline) - 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->IVec3; inline) - return IVec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:IVec3, divisor:Int->IVec3; inline) - return IVec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:IVec3->Num; inline) - x := Num(v.x) - y := Num(v.y) - z := Num(v.z) - return Num.sqrt(x*x + y*y + z*z) - func dist(a,b:IVec3->Num; inline) - 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/CHANGES.md b/examples/wrap/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/wrap/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm deleted file mode 100644 index bae01739..00000000 --- a/examples/wrap/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 != "" then 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 != "" then line ++= " " - line ++= ((++: letters.to(split)) or "") ++ hyphen - letters = letters.from(split + 1) - else if line == "" - # Force split word without hyphenation: - if line != "" then line ++= " " - line ++= (++: letters.to(line_space)) or "" - letters = letters.from(line_space + 1) - else - pass # Move to next line - - lines.insert(line) - line = "" - - if letters.length > 0 - if line != "" then line ++= " " - line ++= (++: letters) or "" - - if line != "" - lines.insert(line) - - return "\n".join(lines) - -func _can_fit_word(line:Text, letters:[Text], width:Int -> Bool; inline) - 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") - - 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") |
