thorn
a pure lazy functional programming language to make ASCII art animations (and other things too)
Rust★ 50⑂ 2 forksGPL-3.0updated 4 days ago
git clone https://github.com/olekawaii/thorn.gitolekawaii/thornREADME.mdfork it — it’s yours
Read this file the way it was intended: https://codeberg.org/olekawaii/thorn/raw/branch/main/README -------------------------- INTRODUCTION ---------------------------- ,. ,. ,A$$$, .A$$c f"Y$$$$ ,&M$$$$L thorn is a pure, lazy, statically-typed $$$$p" "$$$$ functional programming language for $$$$ $$$$ making colorful hand-drown ASCII art $$$$ $$$$ animations. $$$$ $$$f $$$$ .$$F It's still in active development. If $$$$ ,$$' you encounter any bugs or issues, $$$$ ,B' please let me know. Send me an email to $$$R,f" olekawaii@yahoo.com or make an issue $RY*' at https://codeberg.org/olekawaii/thorn .$F" eF` Make sure to check out some examples !$ _ in examples/ and std/ 'e. ,$$; "*wew7*" ------------------------------ WHY --------------------------------- What's the point of this language? The point is to have * Lovely error messages * Cool syntax * Easy serialization and deserialization * Simplicity and no modules (I encourage circular imports) A goal I have is to make the thorn source code as pretty as the actual ASCII art. See for yourself (snippet from examples/dragon/main.th) define center_head as the list frame loop art xii ix ) ( ...7..7..... ) ( ....7..7.... ) ( .....7..7... ((__)) ..776677.... ((__)) ...776677... ((__)) ....776677.. /o..o\ ..636636.... /o..o\ ...636636... /o..o\ ....636636.. ( -vv- ) .6|6776|6... ( -vv- ) ..6|6776|6.. ( -vv- ) ...6|6776|6. \ __. \ ..6|666|6... ) __ ( ...6|66|6... / .__ / ...6|666|6.. ) \ ...6|||||6.. / \ ..6||||||6.. / ( ..6|||||6... / \ ..6|||||||6. / \ .6||||||||6. / \ .6|||||||6.. ' ).6|||||||||6( )6||||||||||6( ` 6|||||||||6. ' ..||||||||6. .||||||||||. ` .6||||||||.. The source code looks nice because * No special characters are allowed in the code (this includes paranthesis) to make a nice contrast with the ASCII art blocks, which are made up almost entirely of special charecters. * Drawings are isolated in their own blocks, so they're never interrupted by escaped characters or colors. The paranthesis are not needed because * If you know the types of everything, function arguments can be grouped with prefix Polish notation. 3 * (1 + 2) -> * 3 + 1 2 * Indentation acts like paranthesis. This allows you to pass lambdas and match expressions directly as function arguments. It can also help with type inference. The error messages are lovely because * The errors show the highlighted line, an explenation, the previous line for context, and all the other stuff you'd expect. * You get both compile-time and run-time errors in the same format. That includes runtime errors like evaluation of an undefined or bottom expression. * There are no paranthesis, meaning all errors are found left to right. If you get, say, a type error at the end of a line, you know that everything that came before it was correct, not needing to worry about it. There's also no concept of misplacing paranthesis, which are a pain to debug in Haskell. * There are no warnings, only errors. Unused variables and bad indentation are caught before an ugly error message can surface. -------------------------- INSTALLATION ---------------------------- thorn has few dependencies. Assuming you have cargo and ghc, you can build it with $ ./build.sh This installs thorn (the interpreter), thorn-to-sh, and thorn-to-gif to ~/.local/bin (make sure it's in your $PATH). It also installs the fonts used by thorn-to-gif to ~/.local/share/thorn/fonts _ ___ _ If you have nix, there's a flake / \ \ \ / \ you can play around with \ \ \ ' / ---` `---\ / , $ nix develop /___________\ \ / \ / / \ / / or install it with /'''' / \/ '''\ \--. /\ / ----/ $ nix profile add . / / \ / / \ / \ \------------ In the future, I will package ` / \ ___ __/ thorn for nixpkgs. / , \ \ \ \_/ \__\ \_/ thorn works like a calculator. It parses and compiles the source code into an internal representation, but afterwards also executes it, printing the evaluated main function. $ thorn . | thorn-to-sh | sh # shell animation $ thorn . | thorn-to-gif | ffplay - # gif Note that thorn expects to find a main.th file in the given directory or in any of its parent directories. That directory is the root of your project. ------------------------ SYNTAX OVERVIEW --------------------------- -- this is a comment DECLARATIONS forall NAME... DECLARATION * declare any local type variables define NAME as EXPRESSION * define a global variable type NAME contains * type declaration. NAME is a data NAME TYPE... constructor and TYPE are its arguments ... EXPRESSIONS the TYPE EXPRESSION * type annotation for rest of block lambda PATTERN EXPRESSION * lambda expression undefined * undefined expression match EXPRESSION * pattern matching case PATTERN EXPRESSION ... PATTERNS NAME * capture value into variable name _ * drop value CONSTRUCTOR PATTERNS * match on constructor and its arguments bind NAME PATTERN * in addition, capture value in NAME (@) either PATTERN PATTERN * match either pattern (|) FUNCTION APPLICATION This code defines a function that applies a function to an integer. The main body, if with paranthesis, would be (apply (add one) three). define apply as the fn fn int int fn int int undefined define main as the int apply add one three What if we wanted to pass a lambda instead of the (add one)? We can't do (apply (lambda x add one x) three) because there are no paranthesis. Instead, we need to do define main is the int apply lambda x add one x three Indentation acts like paranthesis. ------------------------ ALGEBRAIC TYPES --------------------------- Thorn has algebraic types and generics. The forall keyword is used to declare any type variables. Here's a demo of the list and option types type bool contains true false forall a type list contains nil cons a list a forall a type option contains none some a define first_bool as the fn list bool option bool lambda input_list match input_list case nil none case cons head _tail some head The lambda keyword starts a lambda expression, followed by a pattern, in which the input value will be placed. The match expression patternmatches on a value, returning the first branch that matched. In the last line, `cons` is a data constructor and `head/tail` are variable names for the `a` and `list a` in the list definition. _tail is prepended with an underscore to drop the value. define main as the option bool first_bool cons true cons false nil output: some true (thorn's output, if the above is in main.th) define main as the option bool first_bool nil output: none ------------------------- DESTRUCTURING ---------------------------- Consider the following function that finds the distance between two points. Ignore the undefined types/functions. type point contains point float float define distance as the fn point fn point float lambda pa lambda pb match tuple pa pb case tuple point xa ya point xb yb -- destructuring pa,pb sqrt sum squared diff xb xa squared diff yb ya This is very verbose because we're patternmatching on all the points to get their x and y values. However, because the point type only contains one variant, we can actually patternmatch directly in the lambda expression like so define distance as the fn point fn point float lambda point xa ya lambda point xb yb sqrt sum squared diff xb xa squared diff yb ya In fact, lambda, match, and (in the future) let expressions all share the same syntax and semantics for capturing variables. More patterns can be found in the syntax overview section. --------------------------- RECURSION ------------------------------ There is no mutation and therefore there are no loops. The only way to loop is to recurse or use a function that recurses. define main as the nat sum three +----------------------+ succ one | one = 1 | | succ one = 2 | type nat contains | succ (succ one) = 3 | one | and so on | succ nat +----------------------+ define three as the nat succ succ one define sum as the fn nat fn nat nat lambda x match x case one succ case succ a lambda y succ sum a y -- recursive output: succ succ succ succ one By the way, if you look at the second to last line, the `one` branch returns a `succ` while the 'succ a' branch returs a lambda expression. This is because the `succ` data constructor is a function. (succ one) is of type (nat) but (succ) is of type (fn nat nat). All data constructors are curried. ------------------ LAZY EVALUATION & THE REPL -------------------- The whole language is lazily evaluated. Expressions are only evaluated when patternmatched on and evaluation starts at the main function. Here are some examples. I'll use the thorn repl to define and test them. >>> type bool contains >>> true .----.----.----. >>> false [' |' |' ] >>> .--. : : .--. >>> type nat contains |' |---.----.---|' | >>> one | |___|____|___| | >>> succ nat :__| :__| >>> >>> define infinity as the nat succ infinity >>> >>> forall a b type tuple contains tuple a b >>> >>> define are_equal as the >>> fn nat fn nat bool >>> lambda x lambda y match tuple x y >>> case tuple one one true >>> case tuple succ a succ b are_equal a b >>> case _ false >>> >>> -- Let's see what happens when we evaluate these expressions >>> >>> the bool are_equal -- Despite infinity being infinitely >>> succ succ succ one -- large, we can still work with it >>> infinity -- because it's evaluated lazily. the bool false >>> the bool are_equal -- The program doesn't eat memory as >>> infinity -- it hangs. >>> infinity -- *hangs at runtime* >>> the bool are_equal -- undefined is a keyword. It can >>> succ one -- be used in place of any >>> succ succ succ undefined -- expression. the bool false >>> the bool are_equal -- If undefined is evaluated, the >>> succ succ succ one -- program crashes with an error >>> succ succ undefined -- pointing to it. RUNTIME ERROR in /tmp/thorn-repl.th:3:15 | succ succ succ one -- program crashes with an error 3 | succ succ undefined -- pointing to it. ^^^^^^^^^ entered undefined code evaluated an undefined expression >>> ^D You can try all of these interactively in the thorn repl with $ thorn --repl Note that thorn is not a scripting language. The repl just lets you use it like one, similar to GHCi for Haskell. --------------------------- ART BLOCKS ----------------------------- The art macro is expanded into regular thorn code during the tokenization phase. This code defines an animation of a swinging tail. The animation is 6x4 in size and has 2 frames. include video -- finds and imports video.th define main as the list frame dragon_tail define dragon_tail as art vi iv \ ).6|||6( / 6|||6. ) / ..6|6. \ ( .6|6.. ( / .6|6.. \ ) ..6|6. V ..6... V ...6.. Every frame is made up of who rectangles, one with the ASCII art and the other with the corresponding colors. Colors 0-7 correspond to ANSI colors black-white and `|`/`.` distinguish between spaces and transparency. The `vi iv` after the art keyword are the width and height of each frame in roman numerals. Note that the indentation rules are different for art blocks. if the indentation of the line with 'art' is zero, so is the art's indentation. Otherwise it's the line's indentation + 4. Yes, the indentation length is hardcoded to 4. This improves error messages. There is special syntax for including local/global variables in the art. To use a color variable you just replace the color number with the variable name like so (colors defined in std/art/color.th) define dragon_tail as the fn color list frame -- expecting the color as lambda c art vi iv -- a function argument \ ).c|||c( / c|||c. ) / ..c|c. \ ( .c|c.. ( / .c|c.. \ ) ..c|c. V ..c... V ...c.. Now, instead of the tail being hardcoded cyan, the calling function decides its color. Here's the table of supported art syntax +------+-------+-----------------------------------------+ | left | right | effect | +------+-------+-----------------------------------------+ | CHAR | 0 | Grey | | CHAR | 1 | Red | | CHAR | 2 | Green | | CHAR | 3 | Yellow | | CHAR | 4 | Blue | | CHAR | 5 | Magenta | | CHAR | 6 | Cyan | | CHAR | 7 | White | | CHAR | VAR | Uses VAR as the color | | | | | Space cell | | | . | Transparent | | VAR | $ | uses VAR as the cell (ascii + color) | | VAR | # | layers the frame VAR on top | | VAR | ^ | applies fn VAR to the cell below | +------+-------+-----------------------------------------+ where VAR is a single-letter variable name CHAR is a visible ASCII character For complex videos with many colors, it's better to pass a single palette than all the colors individually. You can do that with a custom palette type. include video define main as the list frame dragon_roar default_dragon_p type dragon_p contains dragon_p color -- skin color -- bone color -- eye color -- wing define default_dragon_p as the dragon_p dragon_p cyan white yellow green define dragon_roar as the fn dragon_p list frame lambda dragon_p s b e _ entirely shift south art xiii vii . b............. b............ ),__ .bbss........ ),__ .bbss........ ( o`---~~^;..s|essssssss ( o`---~~^;..s|essssssss / -^V^V^V .s|||sbbbbbb. / /V'V'V .s||||sbbbbb. / `-----.-' s||sssssssss./ \ \,A,A s||s||sbbbb.. / ||s.......... / `----.-' ||s.ssssssss. ............. ............. . b............ ),__ .bbss........ ( o`---~~^;..s|essssssss / /V'V'V .s||||sbbbbb. / ( s||||s....... \ \,A,A |||s||sbbbb.. `----.-' ....ssssssss. You can see how the core types are implemented in std/ . There are many useful functions in video.th, list.th, and frame.th . To find the definition of a function, just run $ grep -Rn 'define function_name ' ----------------------------- OUTPUT ------------------------------- When evaluating a program, the output is the fully-evaluated main function, a tree of data constructors, a string of words when printed out. In fact, if you copy-paste the output into the main function, you will get the same output. To turn this output into something you actually want, you have to interpret it with an external program. thorn tries to adhere to the UNIX philosophy. All programs do one thing and their outputs are intended to be inputs for other programs $ thorn --eval 'the list frame chess_set' | thorn-to-sh | sh _:_ n,_ o o o c __!__ o n,_ __ _ __ /. \ / \ \v|v/ \_ _/ / \ /. \ __ _ __ [ U U ] (_/) \ ( /) <...> <...> ( /) (_/) \ [ U U ] | -| / _/ <...> )_( )_( <...> / _/ | -| |___| _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ |___| (_____) (_____) (_____) (_____) (_____) (_____) (_____) (_____) _ _ _ _ _ _ _ _ ( `) ( `) ( `) ( `) ( `) ( `) ( `) ( `) <...> <...> <...> <...> <...> <...> <...> <...> _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ _/ \_ (_____) (_____) (_____) (_____) (_____) (_____) (_____) (_____)
more like this
emoticon_kaomoji_dataset
Dataset of 62,000 text emoticons and kaomojis with labels.
Jupyter Notebook★ 50