├── LICENSE ├── README.md ├── atlas ├── docs ├── IO.md ├── circular.md ├── examples.md ├── happenings.md ├── syntax.md ├── try_it.md ├── types.md └── vectorization.md ├── error.rb ├── escape.rb ├── infer.rb ├── ir.rb ├── lazylib.rb ├── lex.rb ├── ops.rb ├── parse.rb ├── repl.rb ├── spec.rb ├── test ├── all ├── behavior_tests.atl ├── check_example.rb ├── examples │ ├── ans.test │ ├── circular_start.test │ ├── circular_vars2.test │ ├── commands.test │ ├── comment.test │ ├── duplicate_assignment.test │ ├── empty_char_error.test │ ├── errors_are_cached_too.test │ ├── help.test │ ├── input_lines.test │ ├── int_columns.test │ ├── invalid_let.test │ ├── invalid_set.test │ ├── large_prog.test │ ├── multiline_commands_and_strs.test │ ├── multiple_type_errors.test │ ├── nested_invalid_ref.test │ ├── newline_char.test │ ├── non_binary_error.test │ ├── read_binchar.test │ ├── saves.test │ ├── shinh_313eq515.test │ ├── shinh_6n.test │ ├── shinh_abbccc.test │ ├── shinh_abbcccdddd.test │ ├── shinh_accumulate_values_in_reverse.test │ ├── shinh_baby_shark.test │ ├── shinh_base2to36.test │ ├── shinh_collatz.test │ ├── shinh_colosseum_sequence.test │ ├── shinh_count_in_base_2.test │ ├── shinh_fizzbuzz.test │ ├── shinh_hello_golfers.test │ ├── shinh_length_of_fizz_buzz.test │ ├── shinh_letterbox.test │ ├── shinh_make_100.test │ ├── shinh_polarized_political.test │ ├── shinh_positions_of_upper.test │ ├── shinh_range_analysis.test │ ├── shinh_stop_five_after.test │ ├── stack_overlfow_error.test │ ├── t1.test │ ├── test_output_different_types.test │ ├── test_unset_var_warning.test │ ├── triple_use.test │ ├── undefined_for_base.test │ ├── unset_identifier_error.test │ ├── unterminated_string_error.test │ └── utf8.test ├── gen_crcl_example.rb ├── golf_test_expected.txt ├── golf_test_input.txt ├── repl_test_expected.txt ├── repl_test_input.txt ├── test_behavior.rb ├── test_docs.rb ├── test_examples.rb ├── test_fuzz.rb ├── test_increasing.rb ├── test_op_examples.rb ├── test_parse.rb ├── test_repl.sh ├── test_vec.rb └── update_date.rb ├── type.rb ├── version.rb └── web ├── generate_site.rb ├── op_notes.rb ├── package.rb ├── quickref.rb └── site ├── quickref.css └── style.css /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Darren Smith 2 | 3 | You may use this code freely for noncommercial use. 4 | 5 | I can't imagine how this code would be useful for commercial use, but in the event that you would like to make money off of it, I would want a small cut so please email me so we can work something out. 6 | 7 | I think Altas has some cool new ideas and I hope that they are used in future languages, please credit Atlas if you use specific ideas or techniques pioneered in it. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This information is also available at [golfscript.com/atlas](http://www.golfscript.com/atlas) look there for the quickref as well. If linking to Atlas please link to the site rather than github. 2 | 3 | ------ 4 | 5 | Atlas is an esoteric programming language designed to show off the synergy between laziness and vectorization which allow for concise _circular programming_ - an unsung but powerful technique - nothing else is even needed! 6 | 7 | ## Latest news: 8 | Atlas no longer being maintained (March 31, 2025). [(see all/more info)](docs/happenings.md) 9 | 10 | ## Features 11 | 12 | - Purely functional 13 | - Lazy lists are the only data structure 14 | - Each op can be represented by one of the 32 ASCII symbols (e.g. `#` `$`, etc.) and also by name 15 | - No control structures or way to create functions 16 | - Statically typed - but no need or way to specify type 17 | - Minimal basis rivaling the SK calculus possible 18 | - Left to right infix notation 19 | - Concise despite simplicity 20 | - Not practical 21 | 22 | ## Example 23 | 24 | This page is intended to give you an overview of the main ideas while being easy to understand. Here's an example of the power of the language without such a limitation: 25 | 26 | 1:101{,%3@5~^Fizz@,Buzz_|a 27 | ────────────────────────────────── 28 | 1 29 | 2 30 | Fizz 31 | 4 32 | Buzz 33 | Fizz 34 | 7 35 | 8 36 | Fizz 37 | Buzz 38 | 11 39 | Fizz 40 | 13 41 | 14 42 | FizzBuzz 43 | 16 44 | ... 45 | 46 | And no, those brackets are not a block but their own instructions (push, pop). 47 | 48 | ## Intro 49 | 50 | ### Vectorization 51 | 52 | Atlas is vectorized, this means that operations performed on vectors (which are just lists) do that operation to each element. 53 | 54 | 1,2,3.- 55 | ────────────────────────────────── 56 | -1 -2 -3 57 | 58 | Here we see that a list was constructed with `,`'s, then it was turned into a vector with `.` and then negated with `-`. Then implicitly printed nicely with a space between each number. 59 | 60 | It may seem odd that the `-` is postfix, but evaluation for all ops is left to right. 61 | 62 | Vector is a separate type from list to allows ops that take arbitrary types to know which depth to vectorize at. For example, does `head` on a list of lists mean to take the first element of the whole list or the first element of each list? It would mean the former, you would use a vector of lists for the latter. 63 | 64 | Atlas also can auto vectorize, meaning that an arg is automatically converted to a vector when an op receives a higher rank (list nest depth) type then what they expect. So we could have just written: 65 | 66 | 1,2,3- 67 | ────────────────────────────────── 68 | -1 -2 -3 69 | 70 | This also works with binary ops, repeating a non-vector arg as needed: 71 | 72 | (1,2,3)+(4,5,6) 73 | (1,2,3)+2 74 | ────────────────────────────────── 75 | 5 7 9 76 | 3 4 5 77 | 78 | You can always explicitly do these things but as you gain more experience it may be worth better understanding when it can be left implicit. For more on vectorization, check the [vectorization doc](docs/vectorization.md). 79 | 80 | ### Laziness 81 | 82 | Atlas is also lazy, this means that values are not computed if they are not used, even individual elements of a list. This is possible because there are no operations that cause side effects. 83 | 84 | One huge benefit of laziness is that streams and lists can be unified to a single data type. For example, `input` refers to stdin and is like a stream in a normal language, but in Atlas it is a regular value. 85 | 86 | input + 1 87 | 88 | Just adds 1 to each character you input (`a` becomes `b` etc.), as you input it, for any number of lines. 89 | 90 | For more information on laziness check [wikipedia](https://en.wikipedia.org/wiki/Lazy_evaluation) or try out Haskell. 91 | 92 | ### Circular Programming 93 | 94 | Circular programming is the use of a value before it has been computed. It is something that you cannot do in an eager language. The simplest example would be this: 95 | 96 | let a=1_a 97 | a 98 | ────────────────────────────────── 99 | 1 1 1 1 1 1 1 1 1 1 1 1 1... 100 | 101 | That list is infinite but truncated for display purposes. We are just saying that `a` is 1 prepended to `a`. You could think of `a` as a recursive function that takes no arguments and memoizes its result. `a` is infinite but only takes a constant amount of memory. 102 | 103 | Infinite lists are no problem so long as we don't try to access all elements, that would be an infinite loop. 104 | 105 | Let's see a more complicated example: 106 | 107 | let a=0_(a+1) 108 | a 109 | ────────────────────────────────── 110 | 0 1 2 3 4 5 6 7 8 9 10 11 12... 111 | 112 | How does it work? It is just saying that `a` is 0 prepended to the vectorized addition of `a` and 1, so the next element would be 0+1 and the next 1 more than that and so on. 113 | 114 | We can even compute the fibonacci sequence this way: 115 | 116 | let f=1_1_(f tail+f) 117 | f 118 | ────────────────────────────────── 119 | 1 1 2 3 5 8 13 21 34 55 89... 120 | 121 | An example of this in Haskell was my first exposure to circular programming, and at first I just dismissed it as some special case magic. But there is no special case, neither Haskell nor Atlas treat that code any differently than a non-circular program, it just computes list elements lazily like always. 122 | 123 | This technique of circular programming is legitimately useful and often the simplest and most efficient way to describe sequences in lazy languages. 124 | 125 | FYI circular programming can also refer to using tuples in a circular manner (this is actually the more common usage of the term), but Atlas does not have tuples and this technique is less mind blowing. [Here is a tutorial on that in Haskell](http://www.cs.umd.edu/class/spring2019/cmsc388F/assignments/circular-programming.html). 126 | 127 | ### Folding 128 | 129 | Circular programming can be used to do a scan or a fold. Here is an example of that: 130 | 131 | let x=1,2,3,4,5 132 | let a=0_(x+a) 133 | a 134 | ────────────────────────────────── 135 | 0 1 3 6 10 15 136 | 137 | Here's how it works: 138 | 139 | x: 1 2 3 4 5 140 | a: 0 1 3 6 10 15 141 | x+a: 1 3 6 10 15 142 | 0_(x+a): 0 1 3 6 10 15 143 | 144 | We can see that `x+a` computes the next prefix sum if the previous values of `a` are the previous prefix sums. So by starting from 0 and using induction it works. 145 | 146 | A scan by any function can be computed this way, a fold would just be taking the last element of the result. 147 | 148 | Experienced functional programmers may notice this is a scanl/foldl. There are some cases where a foldr would be more useful because it may terminate a "loop" early, for example if you wanted to check if all elements of a list are truthy but stop as soon as you find a falsey element. Interestingly, a foldr is possible too, check out the [doc on circular programming](docs/circular.md) to see how that works. 149 | 150 | ## Beyond 151 | 152 | At this point you know enough to play with Atlas and practice circular programming. The implications of these concepts have depth beyond what this intro has covered and Atlas does have a few more features that you might want to learn if you want to start writing more complicated or succinct programs. Check out the docs for more info. 153 | -------------------------------------------------------------------------------- /atlas: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "repl.rb" 4 | 5 | if ARGV.delete("-l") 6 | # [Num] prints as 1\n2\n3 (default from file) 7 | $line_mode = true 8 | end 9 | if ARGV.delete("-L") 10 | # [Num] prints as 1 2 3 (default in repl) 11 | $line_mode = false 12 | end 13 | 14 | repl 15 | -------------------------------------------------------------------------------- /docs/IO.md: -------------------------------------------------------------------------------- 1 | # IO 2 | 3 | As a pure language, Atlas has no operators for doing input or output, however it is fairly easy to accomplish these tasks. 4 | 5 | ## Output 6 | 7 | Since an Atlas program is essentially the same as typing it into a REPL. Each line is printed as it is defined. Assignments are not printed (unless it is the last line in a program). Before printing values they are first converted to strings by first joining with spaces, then newlines then double newlines depending on list depth. 8 | 9 | Infinite list will print until they error. 10 | 11 | ## Input 12 | 13 | The `$` token is stdin as a vector of strings, where each vector element is one line of input. Since Atlas is lazy you can accomplish interactive IO even though `$` is just a regular value. 14 | 15 | One note about lazy IO is that it will output anything as soon as it can, so if you wanted to write a program that asked for you name and then said hello to you: 16 | 17 | "Enter your name: " 18 | "Hello, " ($% head) 19 | 20 | Would print the hello before you typed anything because that will always precede whatever you input. 21 | 22 | One way around that is to trick laziness into thinking the hello depends on your input. A simple but hacky way is to reverse the result twice. 23 | 24 | "Enter your name: " 25 | "Hello, " ($% head) reverse reverse 26 | 27 | Simpler would be to just write 28 | 29 | "Enter your name: " 30 | "Hello, " $ 31 | 32 | That won't prematurely print hello, because you could input 0 lines and the implicit append operation is vectorized (since $ is a vector) and so it would say hello to however many names (lines) you input, possibly 0. 33 | 34 | ## Ints 35 | 36 | To get ints just use the `read` op (`&`) on `$`. 37 | 38 | ## Shorthand 39 | 40 | There is currently a shorthand for getting a column of ints from stdin and that is to use an unmatched `}`. This feels a bit hacky, but it is highly useful for the repetitive task of parsing input. 41 | 42 | -------------------------------------------------------------------------------- /docs/circular.md: -------------------------------------------------------------------------------- 1 | # More Circular Programming 2 | 3 | This doc is an unfinished state 4 | 5 | ## Transpose 6 | 7 | How can we transpose a list defined as so? 8 | 9 | let a=(1,2,3,4),(5,6,7,8) 10 | a 11 | ────────────────────────────────── 12 | 1 2 3 4 13 | 5 6 7 8 14 | 15 | The first row will be the heads of each row of `a`, which can be gotten with `.` and `head` 16 | 17 | let a=(1,2,3,4),(5,6,7,8) 18 | a.head 19 | ────────────────────────────────── 20 | 1 5 21 | 22 | Note the `.` which takes one arg and vectorizes it. Just `head` would have given the first row. You may have been expecting a column of 1 5 instead of a row, but the heads of each element is just a 1D list and so it displays as such. 23 | 24 | The next row should be the heads of the tails: 25 | 26 | let a=(1,2,3,4),(5,6,7,8) 27 | a.tail head 28 | ────────────────────────────────── 29 | 2 6 30 | 31 | And the next row would be the head of the tail of the tails. So essentially to transpose we want the heads of the repeated tailings of a 2D list, which we can do with circular programming of course. 32 | 33 | let a=(1,2,3,4),(5,6,7,8) 34 | let tails=tails..tail%%`a 35 | tails 36 | ────────────────────────────────── 37 | 1 2 3 4 38 | 5 6 7 8 39 | 40 | 2 3 4 41 | 6 7 8 42 | 43 | 3 4 44 | 7 8 45 | 46 | 4 47 | 8 48 | 49 | 50 | 51 | 52 | 2:18 (tail) tail on empty list (DynamicError) 53 | 54 | Here the `..tail%%` means perform the tail operation two levels deep. See the Vectorization section for more info. 55 | 56 | It is worth mentioning that this output is a 3D list, which is really just a list of list of a list, there is nothing special about nested lists, they are just lists. The separators for output are different however which makes them display nicely. You can also use the `show` op to display things like Haskell's show function. 57 | 58 | Also note the error. It would occur for the same program in Haskell too: 59 | 60 | tails=map (map tail) (a:tails) 61 | 62 | Anytime we see something of the form `var = something : var` it is defining an infinite list. This list clearly can't be infinite though, hence the error. It can be avoided by taking elements of length equal to the first row. 63 | 64 | let a=(1,2,3,4),(5,6,7,8) 65 | let tails=tails..tail%%`a 66 | tails take (a head len) 67 | ────────────────────────────────── 68 | 1 2 3 4 69 | 5 6 7 8 70 | 71 | 2 3 4 72 | 6 7 8 73 | 74 | 3 4 75 | 7 8 76 | 77 | 4 78 | 8 79 | 80 | `const` is a simply a function that takes two args and returns the first. But since it is zippedWith, it has the effect shortening the first list if the ignored arg is shorter. `head a` is the length we want. 81 | 82 | I have some ideas about creating an op to catch errors and truncate lists, but for now this manual step is required. 83 | 84 | To get the transpose now we just need to take the heads of each list: 85 | 86 | let a=(1,2,3,4),(5,6,7,8) 87 | let tails=tails..tail%%`a 88 | tails take (a head len)..head 89 | ────────────────────────────────── 90 | 1 5 91 | 2 6 92 | 3 7 93 | 4 8 94 | 95 | ## Scan on 2D lists 96 | 97 | We've seen how to do scanl on a list, but how does it work on 2D lists? 98 | 99 | let a=(1,2,3,4),(5,6,7,8) 100 | let b=a+b%%`(0,%) 101 | b. take 10 102 | ────────────────────────────────── 103 | 0 0 0 0 0 0 0 0 0 0 104 | 1 2 3 4 105 | 6 8 10 12 106 | 107 | The same way, we just have to start with a list of 0s instead of one and unvectorize the cons. I did a `10 take` purely for display purposes. 108 | 109 | That was easy, but what if we wanted to do it on rows instead of columns without transposing twice? 110 | 111 | We can do a zipped append: 112 | 113 | let a=(1,2,3,4),(5,6,7,8) 114 | let b=a+b`0 115 | b 116 | ────────────────────────────────── 117 | 0 1 3 6 10 118 | 0 5 11 18 26 119 | 120 | This doesn't work if we port it to Haskell! It infinitely loops: 121 | 122 | let b=zipWith (:) (repeat 0) (zipWith(zipWith (+))a b) 123 | 124 | The reason is because the first zipWith needs to know that both args are non empty for the result to be non empty. And when checking if the second arg is empty, that depends on if both `a` and `b` are non empty. But checking if `b` is non empty, is the very thing we were trying to decided in the first place since it is the result of the first zipWith. `b` is non empty if `a` and `b` are non empty. Haskell deals with this in the simplest way, but in this particular case it is definitely not the most useful way. 125 | 126 | Essentially what we really want is the "greatest fixed point". In general the greatest fixed point isn't clearly defined or efficient to compute. In this case it is both though, and Atlas finds it. The way it works is if it finds that there is a self dependency on a zipWith result, it has "faith" the result will be non empty. Afterwards it checks that the result was in fact non empty, otherwise it throws an infinite loop error (which is what it would have done with out the faith attempt in the first place). I'd like to generalize this logic to work for finding any type of greatest fixed point, but it will be more difficult for other cases as it would require back tracking in some cases. 127 | 128 | ## Map and Nested Map 129 | 130 | It's not obvious that we don't need map to map. But it is quite easy to eliminate the need: just use the list you wish to map over where you would use the map arg. 131 | 132 | For example: 133 | 134 | let a = [1,2,3] 135 | in map (\i -> (i + 2) * 3) a 136 | 137 | In Atlas is: 138 | 139 | let a = 1,2,3 140 | a+2*3 141 | ────────────────────────────────── 142 | 9 12 15 143 | 144 | If you need to use the map arg multiple times, that is fine. 145 | 146 | let a = [1,2,3] 147 | in map (\i -> i * (i - 1) / 2) 148 | 149 | In Atlas is: 150 | 151 | let a = 1,2,3 152 | a*(a-1)/2 153 | ────────────────────────────────── 154 | 0 1 3 155 | 156 | This same idea also replaces the need for any explicit zipWith of a complex function. A zipWith of a complex function is just a series zipWiths of a simple function. This is one reason why languages like APL are so concise, they never need map or zipWith and these are extremely common operations. 157 | 158 | It is worth noting that if you are using an operation that could be applied at different depths (for example head of a 2D list), you will need to use `!`'s the proper number of times to apply them at the right depth. `*` never needs `!` since it can only be applied to scalars. 159 | 160 | Ok, so that's great, but this doesn't work if we need to do nested maps, for example generating a multiplication table: 161 | 162 | map (\i -> map (\j -> i*j) [1,2,3]) [1,2,3] 163 | 164 | Won't work directly: 165 | 166 | (1,2,3) * (1,2,3) 167 | ────────────────────────────────── 168 | 1 4 9 169 | The reason is because vectorization zips instead of doing a 'cartesian product'. 170 | 171 | Doing a cartesian product is easy though. We just replicate each list in a different dimension 172 | 173 | 1,2,3,% take 3 174 | " and " 175 | 1,2,3.,% take 3 176 | ────────────────────────────────── 177 | 1 2 3 178 | 1 2 3 179 | 1 2 3 180 | and 181 | 1 1 1 182 | 2 2 2 183 | 3 3 3 184 | 185 | `,` means repeat, but it could have been done using our circular technique for creating infinite lists. Also the `3 take` is not needed because each list will take the shorter of the two and they are replicated in different directions with the other dimension still being 3. So the final program can be: 186 | 187 | 1,2,3, * (1,2,3.,) 188 | ────────────────────────────────── 189 | 1 2 3 190 | 2 4 6 191 | 3 6 9 192 | 193 | This technique can do any degree of nesting with any dimension lists. Essentially you need to think of each operation in a computation taking place at a location in nD space, where n is the number of nested loops. And despite the name you can use a cartesian product on any operation not just multiplication. 194 | 195 | Note for code golfers, the left `,` isn't needed since it knows it needs a 2D list. We could have written 196 | 197 | 1,2,3*(1,2,3.,) 198 | ────────────────────────────────── 199 | 1 2 3 200 | 2 4 6 201 | 3 6 9 202 | 203 | or even shorter by saving the 1,2,3 to the next available var 204 | 205 | 1,2,3{.,*a 206 | ────────────────────────────────── 207 | 1 2 3 208 | 2 4 6 209 | 3 6 9 210 | 211 | 212 | 213 | ## Foldr continued 214 | 215 | One option around this is to define two different zipWiths, one that only checks if left is empty and a different version that only checks if the right is empty. So in this case we would need to use zipWithL which doesn't check if the right arg is empty, only the left. These zipWiths would throw an error if the opposite side was the shorter list (rather than truncate). This isn't ideal because it puts the burden on the user to choose the correct zipWith and it actually still doesn't work because `++` still needs to know when the left operand is empty, but it would work if Haskell constructed lists symmetrically with `++` as a primitive instead of cons. 216 | 217 | There is a solution though! We can define a function that adds an infinite list to the end of a list and construct it in such a way that it doesn't need to check anything if asked if empty, since it can never be empty even after tailing it. This allows zip's empty check to immediately succeed. Here is that function in Haskell: 218 | 219 | pad :: [a] -> a -> [a] 220 | pad a v = h : pad t v 221 | where 222 | ~(h,t) = 223 | if null a 224 | then (v,[]) 225 | else (head a,tail a) 226 | 227 | TODO update this with 1 arg version 228 | 229 | This function pads a list by adding an infinite list of a repeating element after it (e.g. `pad [1,2,3,4] 0` is `[1,2,3,4,0,0,0,...]`. But critically it starts by returning a list rather than first checking which case to return. 230 | 231 | Notice that it always returns `h : pad t v`, which is a non empty list, regardless of if `a` was empty or not, thus nothing needs to be computed when asked if the result is empty. It is only the contents of said list that depend on if `a` was empty. This is definitely not the most intuitive way to define this function, but it is the only way that is sufficiently lazy. 232 | 233 | Now we can write: 234 | 235 | a = [1,2,3,4] 236 | b = zipWith (+) a (tail (pad b 0)) 237 | 238 | And it works! 239 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## FizzBuzz 4 | 5 | The intro gives a somewhat short implementation of FizzBuzz with no explanation, here is that explanation. This is a fairly simple problem that doesn't even require any circular programing, it is a good example for learning vectors and other tricks however. 6 | 7 | First we generate the numbers 1 to 100 then we want to mod each of those by 3 and 5. We need to repeat each number so that it mods by 3 and 5, if we didn't then it would only mod 1 by 3 and 2 by 5. Since both lists are 1d. 8 | 9 | let r=1:101 10 | r,%(3,5) 11 | ────────────────────────────────── 12 | 1 1 13 | 2 2 14 | 0 3 15 | 1 4 16 | 2 0 17 | 0 1 18 | ... 19 | 20 | Now we can just take the `not` of each value to check if it was divisible or not 21 | 22 | let r=1:101 23 | r,%(3,5)~ 24 | ────────────────────────────────── 25 | 0 0 26 | 0 0 27 | 1 0 28 | 0 0 29 | 0 1 30 | 1 0 31 | ... 32 | 33 | Now we want to replicate the strings Fizz and Buzz that many times respectively. Here I have pretty printed it so that we can see the empty strings better. 34 | 35 | let r=1:101 36 | r,%(3,5)~^("Fizz","Buzz") p 37 | ────────────────────────────────── 38 | <<"","">,<"","">,<"Fizz","">,<"","">,<"","Buzz">,<"Fizz","">,<"","">... 39 | 40 | No we just need to concatenate each sublist, and if empty use the number instead. There is an op for concatenating, and the latter can be done via an `or` op (which you can see from its type signature in the quickref will coerce the different types). 41 | 42 | let r=1:101 43 | r,%(3,5)~^("Fizz","Buzz")_|r 44 | ────────────────────────────────── 45 | 1 46 | 2 47 | Fizz 48 | 4 49 | Buzz 50 | Fizz 51 | 7 52 | 8 53 | Fizz 54 | Buzz 55 | 11 56 | Fizz 57 | 13 58 | 14 59 | FizzBuzz 60 | 16 61 | ... 62 | 63 | Now to golf it. 64 | 65 | - The parens can be removed by adding a `@` before the op to increase its precedence. 66 | - `101` can be replaced by `CI` the roman numeral 67 | - The `r` is only used twice, so we can save it to next available var with `{` and get it with `a` 68 | - The quotes around `"Fizz"` and `"Buzz"` can be removed since unset identifiers default to their string value. 69 | - The `,` can be removed in `3@,5` since it is implicit. It can't between Fizz and Buzz because it would think we want to assign to the identifier Buzz. 70 | 71 | And we get: 72 | 73 | 1:CI{,%3@5~^Fizz@,Buzz_|a 74 | ────────────────────────────────── 75 | 1 76 | 2 77 | Fizz 78 | 4 79 | Buzz 80 | Fizz 81 | 7 82 | 8 83 | Fizz 84 | Buzz 85 | 11 86 | Fizz 87 | 13 88 | 14 89 | FizzBuzz 90 | 16 91 | ... 92 | 93 | ## Brainfuck 94 | 95 | A brainfuck interpreter seems like it would be very difficult to create in Atlas because it is the epitome of imperative and Atlas has no ability to do anything imperatively. None-the-less it can be done by using the techniques described in [the circular doc](circular.md) to simulate state over time. 96 | 97 | We will just worry about calculating the next state from the current state and let circular programming do the rest for us. 98 | 99 | - `+` `-` are simple, we just add or subtract from the previous state. 100 | - `<` `>` can not be directly implemented because Atlas does not have random access, but we can use a technique called zippered lists. We maintain the values right of the pointer in one list and the values to the left of the pointer in reverse order in another list. Now moving left and right can be accomplished by pushing and popping from the front of these lists. 101 | - `,` input, we will skip implementing this - it would not be difficult. 102 | - `.` output can be accomplished by returning a single output character or empty list at each step, the final output is just the concatenation of this. 103 | - `[` `]` are the trickiest to deal with, there are a couple strategies. When we encounter a `[` and the value is falsey we will search for the next matching `]` by treating brackets as 1 and -1 and finding when the cumulative sum = 0 again. When we encounter a `]` we will always pop from a stack that records where `[` were. 104 | 105 | It might be more concise or simpler conceptually to simulate random access using `drop` than using zippered lists but such an operation would be asymptotically slower. 106 | 107 | Note that you could use `$` for input rather than hard coding the input as `s=...`. 108 | 109 | Here is the code to calculate the next state (variables with `2` in the name) from the previous: 110 | 111 | let s="+[[-]]." 112 | 113 | -- initial state 114 | let c=s 115 | let ml=() -- memory to left of pointer (reversed) 116 | let mr=0,% -- memory to the right of pointer 117 | let m=0 -- value at pointer 118 | let b=() -- stack of code at ['s that we have entered 119 | 120 | -- character matches (for use in filters to simulate case statements below) 121 | let z=c[,.="><+-][" 122 | let r=z%/. -- reverse order of z 123 | 124 | -- next state 125 | let b2=r~(m&(c,S;_b);,b@>)[|b catch,b[ 126 | let next=(`0+('\-c{|<2&a))?[#]\c -- the next code after matching ] 127 | let c2=r~(m~&next;,b@[)[|c catch,c[ > 128 | let m2= z~(mr[;,ml@[,m@+1,m@-1),m [ 129 | let ml2=z~(m,ml,ml@>) , ml[ 130 | let mr2=z~(mr>,m@,mr) , mr[ 131 | 132 | -- show result 133 | "code" 134 | c2 135 | "memory" 136 | ml2/_m2_mr2[40*" " 137 | ml2#^" "_"^" 138 | "b" 139 | b2 p 140 | ────────────────────────────────── 141 | code 142 | [[-]]. 143 | memory 144 | 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 145 | ^ 146 | b 147 | [] 148 | 149 | This code can be tested by changing `s` and the initial state to see if it behaves correctly generating the next state. Once that is confirmed, we just need to set it up so that each state value is a circular vector based on its next state and initial value. 150 | 151 | let s="++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." 152 | let c=c2%`s. 153 | let ml=ml2^ 154 | let mr=mr2%`(0,%). 155 | let m=m2^ 156 | let b=b2^ 157 | 158 | let z=c[,="><+-][" 159 | let r=z%/. 160 | let b2=r~(m&b@`c@`S;,b@>)@f[|b catch,b[ 161 | let next=(`0+('\-c{|<2&a))?[#]\c 162 | let c2=r~(m~&next,b@[)[|c catch,c[ > 163 | let m2= z~(mr[;,ml@[,m@+1,m@-1),m [ 164 | let ml2=z~(ml`m,ml@>) , ml[ 165 | let mr2=z~(mr>,mr@`m) , mr[ 166 | c[='.~(c?m[)+'\0 167 | ────────────────────────────────── 168 | Hello World! 169 | 170 | The last line collect the output, chunk it while the code that remains is not empty, concat possible outputs, and turn them from numbers into characters. 171 | 172 | ### Golfed 173 | 174 | A bit of golfing gets it down to 163 characters, likely less readable than brainfuck itself. (Note actually this is longer for now since behavior of `|` has changed and catch is manually needed, but surely could be golfed out, also saves can be used instead of sets). 175 | 176 | let s="++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." 177 | >,r@`m~\z,r[%`0,@%.@r)[;,(l`m,l@>~\z,l[^@l[),m@+1,m@-1~\z,m[^@m~&((`0+('\-c{|<2&a))?[#]\c),(c[,.="><+-]["@z%/.@d~(m&b@`c@`S;,b@>)[|b catch,b[^@b[)~\d[|c catch,c[>%`s.@c[='.~(c?m[+'\0 178 | ────────────────────────────────── 179 | Hello World! 180 | 181 | I've written way shorter in Ruby, but Ruby is an imperative language and you can use reflection to just convert Brainfuck into Ruby. Atlas can't do either of those things and none of Atlas' more powerful primitives are of any use either here, it is the worst case scenario. But at least it is possible... -------------------------------------------------------------------------------- /docs/happenings.md: -------------------------------------------------------------------------------- 1 | # Happenings 2 | 3 | ## News 4 | 5 | - Atlas no longer being maintained (March 31, 2025). I like Atlas but it does lack some polish / finalization and I instead put my effort into a very similar language [iogii](../iogii) (which I do plan on maintaining). 6 | - Atlas available for use on [anarchy golf](http://golf.shinh.org/) in alpha mode (Jan 6, 2023) 7 | 8 | ## Community 9 | 10 | Bug reports, design thoughts, op ideas, painpoints, feedback, etc. would be greatly appreciated. Please raise an issue or email me with your thoughts. ` at golfscript.com`. 11 | 12 | There is also a [chatroom](https://chat.stackexchange.com/rooms/145966/golfscript-com) for discussing Atlas. 13 | 14 | Please contact me if you would like any Atlas related things linked to from here 15 | 16 | ## Related Ideas 17 | 18 | BTW there was a working Befunge-like 2D mode where you draw your program's graph rather than use variable names and parenthesis - it was smart about deducing connections so was probably even more concise. It was very interesting, but I removed it for now to focus on teaching circular programming rather than exploring a wacky idea (check out the very first commit of this language for a working prototype if you are curious). 19 | -------------------------------------------------------------------------------- /docs/syntax.md: -------------------------------------------------------------------------------- 1 | # Syntax 2 | 3 | Atlas uses infix syntax for all operations. It's precedence is left to right. Atlas tries to compromise between simplicity, familiarity, and conciseness. Sticking to math like precedence would be more familiar, but is actually quite complicated and inconsistent. Similar reasoning as APL is also used here in that there are too many ops to keep track of the precedence of each. It's actually very easy to get used to the lack of op specific precedence. 4 | 5 | All ops have a symbol version and named version. This is so that code can be written in a legible way (even to newcomers) but you can also make it short if you want to. I have no idea why APL and variants don't do this. Even the named versions are still written infix. For example: 6 | 7 | 4 add 5 8 | ────────────────────────────────── 9 | 9 10 | 11 | You can think of it like an OO languages `a.add(5)` if that helps. 12 | 13 | Atlas is actually a just a REPL calculator and so if you have multiple lines it is just treated as multiple expressions that are all printed. 14 | 15 | Single line comments are done with `--`. I recommend setting your syntax highlighting to be Haskell for `.atl` files for an easy keyboard shortcut. If you wanted to negate then subtract, just add then negate instead. 16 | 17 | 1+1--this is ignored 18 | ────────────────────────────────── 19 | 2 20 | 21 | 22 | Since all ops are overloaded as both unary and binary operators if there are multiple ops in a row, the last is the binary operator and the rest are unary (it is the only way that makes sense). 23 | 24 | 3-|+1 -- that is negate then abs value 25 | ────────────────────────────────── 26 | 4 27 | 28 | The `+` was a binary op, and the `-` and `|` are unary. 29 | 30 | Two expressions in a row without an explicit operation do an implicit op. This uses the `build` operator. You don't necessarily need a space to use this. This implicit operation is still left to right and equal precedence to other operations. 31 | 32 | 1+1 3*4 33 | -- Is parsed as: 34 | ((1+1) 3)*4 35 | -- And does a build 36 | ────────────────────────────────── 37 | 8 12 38 | 8 12 39 | 40 | If you want to use the implicit op following a unary op, it would look like you were trying to just do a binary op instead. To overcome this just be explicit and use `,`. 41 | 42 | 2-,3 43 | -- or you could use parenthesis 44 | (2-)3 45 | ────────────────────────────────── 46 | -2 3 47 | -2 3 48 | 49 | In addition to assignment, `=` is also used to test equality, it is only used as assignment if first on a line and the left hand side is an identifier. 50 | 51 | `()` is the empty list. 52 | 53 | () p 54 | ────────────────────────────────── 55 | [] 56 | 57 | Identifiers must start with a letter but then can have numbers or underscores. 58 | 59 | Parens do *not* need to be matched 60 | 61 | )p 62 | 2*(3+4 63 | ────────────────────────────────── 64 | [] 65 | 14 66 | 67 | `@` is an op modifier that flips the argument order of the next op. It can be used in a nested manner. 68 | 69 | 2*1@+1@+1 70 | 2*(1+(1+1)) 71 | ────────────────────────────────── 72 | 6 73 | 6 74 | 75 | It can also be used on unary ops. It will be done implicitly on unary ops if used on a binary op right after it. 76 | 77 | 2*1-@+1 78 | 2*((1-)+1) 79 | ────────────────────────────────── 80 | 0 81 | 0 82 | 83 | `@` is also an assignment that does not consume the value (if there is an identifier on the right). 84 | 85 | 1+2@a*a 86 | ────────────────────────────────── 87 | 9 88 | 89 | `\` is also a modifier that flips the op order of the previous op. 90 | 91 | 5-\8 92 | ────────────────────────────────── 93 | 3 94 | 95 | The reason for it being after the op is so that it can also be used as a regular unary op as well (`transpose`). 96 | 97 | Flip can be used on the implicit op. 98 | 99 | "hi"\"there" 100 | ────────────────────────────────── 101 | there 102 | hi 103 | 104 | The curly brackets are nice for avoiding normal variable assignments, but cannot help you write a circular program since they always copy the left value. To do that just use parenthesis with an implicit value. Instead of writing `a cons 1@a` we could just write: 105 | 106 | (cons 1) 107 | ────────────────────────────────── 108 | 1 1 1 1 1 1... 109 | 110 | `ans` is a special op that is the result of the previous line. 111 | 112 | 1+2 113 | ans- 114 | ────────────────────────────────── 115 | 3 116 | -3 117 | -------------------------------------------------------------------------------- /docs/try_it.md: -------------------------------------------------------------------------------- 1 | # Try it! 2 | 3 | You can download the source from github or packaged as one file. Then to start the repl, run: 4 | 5 | atlas 6 | 7 | To run from a file: 8 | 9 | atlas filename.atl 10 | 11 | You will need Ruby, I have tested it to work with 2.7 and 3.1. 12 | 13 | ## Repl 14 | 15 | Using it in non repl mode is actually very similar to just piping each line of your file into the repl. One difference is that in the repl a list or vector of numbers will print with a space between them instead of a newline. You can still use the `print` command to see how it would print in non repl mode. Or use the `-l` flag. You may also use the `-L` flag to make the non repl print like the repl. 16 | 17 | # Debug Features 18 | 19 | These commands may be useful for debugging: 20 | 21 | - `help` see op's info (`help +`) 22 | - `version` see atlas version 23 | - `type` see expression type (`type 1+2 -> Num`) 24 | - `p` pretty print value (`p 1,2 -> [1,2]`) 25 | - `print` print a value as it would be done implicitly in file mode -------------------------------------------------------------------------------- /docs/types.md: -------------------------------------------------------------------------------- 1 | # Types 2 | 3 | There are 5 types in Atlas. 4 | - Numbers (arbitrary precision integers and double precision floating point numbers). 5 | - Chars which are just integers that may have a different set of operations allowed on them including how they are displayed. Construct them using a single leading `'`. 6 | - Lists, which may also be of other lists. A list of a list of integers is what I call a 2D list AKA a rank 2 list. This is not a matrix, each sublist may have a different length. 7 | - Vectors, which are just list that prefer to apply operands to their elements instead of the list as a whole. See the [vectorization](vectorization.md) section for information about how automatic vectorization rules. 8 | - A, the unknown type. The empty list `()` is a list of this type. 9 | 10 | Strings are just lists of characters. 11 | 12 | 123 -- This is an integer 13 | 12.3 -- This is a float 14 | 'x -- This is a char 15 | "abc" -- This is a string, aka list of chars 16 | 1,2,3 -- This is a list of integers constructed via the build operator twice. 17 | () p -- This is an empty list pretty printed 18 | ────────────────────────────────── 19 | 123 20 | 12.3 21 | x 22 | abc 23 | 1 2 3 24 | [] 25 | 26 | Some escapes are possible in chars/strings: 27 | 28 | `\0` `\n` `\"` `\\` `\x[0-9a-f][0-9a-f]` 29 | 30 | You may also use any unicode character, the Atlas files are assumed to be UTF-8 encoded. 31 | 32 | Numbers are truthy if >0, chars if non whitespace, lists if non empty. This is only used by operators like `and`, `or`, `filter`, `not`, etc. 33 | 34 | Atlas is statically typed. Inference works in a top down fashion. You shouldn't have to think about it or even notice it since you never need to specify type. The only thing it prevents is heterogeneous lists, but there are major advantages to homogeneous lists when it comes to implicit vectorization. Homogeneous lists could be done dynamically, but static typing is useful for circular programs it allows for selecting of op behavior and vectorization before evaluating the args. 35 | 36 | I'm very happy with how inference turned out. You will never need to specify a type or get an error message about an ambiguous type, etc. It should feel as easy as dynamic typing but with better error messages and more consistent op overloading / coercion. 37 | 38 | Unknown needs to be it's own type (as opposed to solving a type variable like Haskell) in order for top down type inference to work (which is what Atlas uses). It becomes the smallest rank type it can when used in ops with type constraints like `append`. You may also see this type displayed as the arg type in invalid circular program's error messages, this is because the type of all values in a circular definition are considered unknown until they can be inferred. If they are never inferred their type remains unknown. It is not possible to construct program that would do anything except infinite loop or error if a value of type unknown is used, so this is not a limitation of the inference. 39 | 40 | Doing type inference on circular programs that can have implicit vectorization was a very tricky problem to solve, and the current implementation is imperfect. There is no need to understand how it works, but I will explain it, mostly so that I can read this when I forget. 41 | 42 | For non circular programs, inference is trivial and works in a top down fashion, but for circular programs there is nowhere to start. It starts anywhere in the circle with a guess that its arg types are type scalar unknown and recomputes the type of the resulting op, if the result is different than before it then recomputes types that depended on that recursively. 43 | 44 | So long as there is no oscillation between types this process will eventually terminate with the smallest possible ranks for each node (or try to construct an infinite type - which is an invalid program). So how do we know no oscillation is possible? Ops are chosen to obey a couple of invariants: 45 | 46 | - Base elements could actually oscillate, consider the code `'b-a`, it will return an int if `a` is a char, but a char if `a` is an int. However this doesn't matter because any circular program at the scalar level would definitely be an infinite loop. 47 | - List rank can only increase or stay the same if their argument's list rank increase. 48 | - Vector rank can only increase or stay the same if their argument's vector ranks increase. And they may not change the list rank of the result. 49 | 50 | This last bullet is important since decreasing list rank can increase vector rank. Consider the code: 51 | 52 | "abcd" = 'c len p 53 | ────────────────────────────────── 54 | <0,0,1,0> 55 | 56 | If we increase the right arg to a string: 57 | 58 | "abcd" = "c" len 59 | ────────────────────────────────── 60 | 0 61 | 62 | The result has the same list rank (0), but a lower vector rank. But since there is no way to convert this vector rank decrease back into a list rank decrease, it is safe. Any op that would unvectorize would also just promote if the vector rank was too low. 63 | 64 | This invariants are chosen because changing list rank could increase the resulting vector rank (e.g. scalar ops that auto vectorize) or decrease vector rank (e.g. the equality test mentioned above). 65 | 66 | There is a flaw in the current inference algorithm in that it simultaneously finds the list and vector ranks, whereas it should find the list rank and then the vector rank, since the vector rank could temporarily be higher than the lowest possible while finding the minimum list ranks. In practice this flaw is very rarely encountered and could be worked around, although it would be very annoying. TODO fix it. 67 | 68 | One could easily violate these invariants when designing op behavior and auto vectorization rules. For example suppose you created an op that removed all vectorization (2d vector would return a 2d list for example). This would violate a rule because changing vector rank would change the resulting list rank. The current `unvec` op always removes exactly 1 layer. 69 | -------------------------------------------------------------------------------- /docs/vectorization.md: -------------------------------------------------------------------------------- 1 | # Vectorization 2 | 3 | Any list can be turned into a vector by using the `.` operator. A vector is just a list, but it prefers to apply operands to its elements instead of the list as a whole. 4 | 5 | "abc","123" len 6 | "abc","123". len 7 | ────────────────────────────────── 8 | 2 9 | 3 3 10 | 11 | Automatic vectorization can occur to fit the type specification of an operator. For example, the type of `+` is `Int Int -> Int` and so if you give it a list for any of the arguments, their rank is too high and so it vectorizes said argument to bring it down. It is essentially performing a `map`. 12 | 13 | 1,2,3.+4 p 14 | 1,2,3+4 p 15 | ────────────────────────────────── 16 | <5,6,7> 17 | <5,6,7> 18 | 19 | Note that the type is a vector not a list, even though `1,2,3` is a list, it was first vectorized. You can convert a vector back to a list using `%`. But usually this isn't needed because unvectorization is also automatically done if an arguments rank is too low. 20 | 21 | 1,2,3+4% len 22 | 1,2,3+4 len 23 | ────────────────────────────────── 24 | 3 25 | 3 26 | 27 | If two vectors are present it pairs them off an performs the operation on each pair. It is essentially performing a `zipWith` 28 | 29 | (1,2,3) + (2,4,6) 30 | ────────────────────────────────── 31 | 3 6 9 32 | 33 | Longer lists are truncated to that of the smaller (this isn't the case for all vectorized languages, but useful in Atlas since we frequently use infinite lists). 34 | 35 | (1,2) + (2,4,6) 36 | ────────────────────────────────── 37 | 3 6 38 | 39 | Automatic vectorization can work on non scalar arguments as well. 40 | 41 | "1 2 3","4 5" read p 42 | ────────────────────────────────── 43 | <[1,2,3],[4,5]> 44 | 45 | Read expects a string, but was given a list of strings so it vectorizes. Read returns a list of ints always, so that part is just normal operation. 46 | 47 | It can even work on more complicated types like the type of append (`[a] [a] -> [a]`). 48 | 49 | "abc","xyz" append "123" p 50 | ────────────────────────────────── 51 | <"abc123","xyz123"> 52 | 53 | Automatic Vectorization can only lower rank, sometimes it needs to be raised. For example transpose works on 2D lists, but if you give it a 1D list it needs to become a 2D list first, by just making it a list with a single element (the original list). I call this promotion. 54 | 55 | "123" \ p 56 | ────────────────────────────────── 57 | ["1","2","3"] 58 | 59 | Automatic promotion and vectorization can both be done implicitly together. For example: 60 | 61 | 'a take (1,0) p 62 | ────────────────────────────────── 63 | <"a",""> 64 | 65 | The `(1,0)` is vectorized and the `'a` is promoted to a string. 66 | 67 | Unvectorization is preferred to promotion. That is why the earlier example `1,2,3+4 len` returned `3` instead of `[1,1,1]`. 68 | 69 | There is one exception to these rules which is for `,` and this is to enable intuitive list construction from data. This is how `"abc","123","xyz"` creates a list of strings. Without preferring promotion over vectorization of the first arg, `,` would need to be type `a a -> [a]` to get the first use to work and type `[a] a -> [a]` to get the second op to work as well. `,` just prefers to promote once rather than automatically vectorize the first arg, you can still vectorize that arg, you will just need to do so explicitly. 70 | -------------------------------------------------------------------------------- /error.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | def warn(msg, from=nil) 3 | STDERR.puts to_location(from) + msg + " (\e[31mWarning\e[0m)" 4 | end 5 | 6 | def to_location(from) 7 | token = case from 8 | when Token 9 | from 10 | when AST 11 | from.token 12 | when IR 13 | from.from.token 14 | when NilClass 15 | nil 16 | else 17 | raise "unknown location type %p " % from 18 | end 19 | 20 | if token 21 | "%s:%s (%s) " % [token.line_no||"?", token.char_no||"?", token.str] 22 | else 23 | "" 24 | end 25 | end 26 | 27 | class AtlasError < StandardError 28 | def initialize(message,from) 29 | @message = message 30 | @from = from 31 | end 32 | def message 33 | to_location(@from) + @message + " (\e[31m#{class_name}\e[0m)" 34 | end 35 | def class_name 36 | self.class.to_s 37 | end 38 | end 39 | 40 | class DynamicError < AtlasError 41 | end 42 | 43 | class InfiniteLoopError < AtlasError 44 | attr_reader :source 45 | def initialize(message,source,token) 46 | @source = source # no longer needed, cleanup 47 | super(message,token) 48 | end 49 | end 50 | 51 | class StaticError < AtlasError 52 | end 53 | 54 | # Named this way to avoid conflicting with Ruby's TypeError 55 | class AtlasTypeError < StaticError 56 | def class_name 57 | "TypeError" 58 | end 59 | end 60 | 61 | class ParseError < StaticError 62 | end 63 | 64 | class LexError < StaticError 65 | end 66 | -------------------------------------------------------------------------------- /escape.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | def inspect_char(char) 3 | return "'\"" if char=='"'.ord # Don't escape this char (we would in a string) 4 | "'" + escape_str_char(char) 5 | end 6 | 7 | def escape_str_char(char) 8 | return "\\0" if char == "\0".ord 9 | return "\\n" if char == "\n".ord 10 | return "\\\\" if char == "\\".ord 11 | return "\\\"" if char == "\"".ord 12 | return char.to_i.chr if char >= " ".ord && char <= "~".ord # all ascii printables 13 | return "\\x0%s" % char.to_i.to_s(16) if char < 16 && char >= 0 14 | return "\\x%s" % (char%256).to_i.to_s(16) if char < 256 && char >= -2 15 | return "\\{%d}" % char 16 | end 17 | 18 | def parse_char(s) 19 | ans,offset=parse_str_char(s,0) 20 | raise "internal char error" if ans.size != 1 || offset != s.size 21 | ans[0].ord 22 | end 23 | 24 | def parse_str_char(s,i) # parse char in a string starting at position i 25 | if s[i]=="\\" 26 | if s.size <= i+1 27 | ["\\",i+1] 28 | elsif s[i+1] == "n" 29 | ["\n",i+2] 30 | elsif s[i+1] == "0" 31 | ["\0",i+2] 32 | elsif s[i+1] == "\\" 33 | ["\\",i+2] 34 | elsif s[i+1] == "\"" 35 | ["\"",i+2] 36 | elsif s[i+1,3] =~ /x[0-9a-fA-F][0-9a-fA-F]/ 37 | [s[i+2,2].to_i(16).chr,i+4] 38 | else 39 | ["\\",i+1] 40 | end 41 | else 42 | [s[i],i+1] 43 | end 44 | end 45 | 46 | def parse_str(s) 47 | i=0 48 | r="" 49 | while i 1 67 | # c=parse_char(s) 68 | # p [ci,c.ord,s] if c.ord != ci 69 | # } 70 | -------------------------------------------------------------------------------- /infer.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | 3 | def all_nodes(root) 4 | all = [] 5 | dfs(root){|node| all << node} 6 | all 7 | end 8 | 9 | module Status 10 | UNSEEN = 1 # in practice nil will be used 11 | PROCESSING = 2 12 | SEEN = 3 13 | end 14 | 15 | def dfs(root,cycle_fn:->_{},&post_fn) 16 | dfs_helper(root,[],cycle_fn,post_fn) 17 | end 18 | 19 | def dfs_helper(node,been,cycle_fn,post_fn) 20 | if been[node.id] == Status::SEEN 21 | 22 | elsif been[node.id] == Status::PROCESSING # cycle 23 | cycle_fn[node] 24 | else 25 | been[node.id] = Status::PROCESSING 26 | node.args.each{|arg| 27 | dfs_helper(arg,been,cycle_fn,post_fn) 28 | } 29 | post_fn[node] 30 | end 31 | been[node.id] = Status::SEEN 32 | return 33 | end 34 | 35 | def infer(root) 36 | all = all_nodes(root) 37 | q=[] 38 | # these are topologically sorted from post traversal dfs which gives a favorable order to start inference from 39 | all.each{|node| 40 | node.used_by = []; 41 | if node.type_with_vec_level == nil 42 | node.type_with_vec_level = UnknownV0 43 | node.in_q = true 44 | q << node 45 | end 46 | } 47 | all.each{|node|node.args.each{|arg| arg.used_by << node} } 48 | 49 | q.each{|node| # this uses q as a queue 50 | node.in_q = false 51 | prev_type = node.type_with_vec_level 52 | calc_type(node) 53 | if node.type_with_vec_level != prev_type && !node.last_error 54 | node.type_updates = (node.type_updates || 0) + 1 55 | if node.type_updates > 100 56 | if node.type.dim < 20 && node.vec_level < 20 57 | raise "congratulations you have found a program that does not find a fixed point for its type, please report this discovery - I am not sure if it possible and would like to know" 58 | end 59 | raise AtlasTypeError.new "cannot construct the infinite type" ,node 60 | end 61 | 62 | node.used_by.each{|dep| 63 | if !dep.in_q 64 | dep.in_q = true 65 | q << dep 66 | end 67 | } 68 | end 69 | } 70 | 71 | errors = [] 72 | dfs(root) { |node| 73 | if node.last_error 74 | errors << node.last_error if node.args.all?{|arg| arg.type_with_vec_level != nil } 75 | node.type_with_vec_level = nil 76 | end 77 | } 78 | errors[0...-1].each{|error| STDERR.puts error.message } 79 | raise errors[-1] if !errors.empty? 80 | root 81 | end 82 | 83 | # finds best matching type for a spec (multiple would be possible if overloading by rank) 84 | def match_type(types, arg_types) 85 | types = types.call if Proc === types 86 | fn_types = types.select{|fn_type| 87 | check_base_elem_constraints(fn_type.specs, arg_types) 88 | } 89 | return nil if fn_types.empty? 90 | if fn_types.size > 1 91 | fn_types.sort_by!{|fn_type| 92 | fn_type.specs.zip(arg_types).map{|spec,type| 93 | (spec.type.dim-type.dim).abs # only handles specs of exact type, but that is all there should be if overloading this way. 94 | } 95 | } 96 | end 97 | return fn_types[0] 98 | end 99 | 100 | def calc_type(node) 101 | node.last_error = nil 102 | fn_type = match_type(node.op.type, node.args.map(&:type)) 103 | return node.type_error "op is not defined for arg types: " + node.args.map{|arg|arg.type_with_vec_level.inspect}*',' if !fn_type 104 | node.type_with_vec_level = possible_types(node,fn_type) 105 | end 106 | 107 | def possible_types(node, fn_type) 108 | arg_types = node.args.map(&:type) 109 | vec_levels = node.args.map(&:vec_level) 110 | unvec_at_end = false 111 | 112 | vec_levels = vec_levels.zip(fn_type.specs,0..).map{|vec_level,spec,i| 113 | if spec.vec_of 114 | if vec_level == 0 115 | return node.type_error "vec level is 0, cannot lower" if node.op.name == "unvec" 116 | unvec_at_end = true if node.op.name == 'consDefault' && arg_types[0].dim > 0 117 | arg_types[i]-=1 # auto vec 118 | 0 119 | else 120 | vec_level - 1 121 | end 122 | else 123 | vec_level 124 | end 125 | } 126 | 127 | nargs = arg_types.size 128 | vars = solve_type_vars(arg_types, fn_type.specs) 129 | deficits = rank_deficits(arg_types, fn_type.specs, vars) 130 | rep_levels = [0]*nargs 131 | promote_levels = [0]*nargs 132 | 133 | # auto promote both if equal 134 | if node.op.name == "build" && deficits[1]==0 && deficits[0]==0 135 | # if any are unknown, only promote smaller or rank 1s 136 | all_known = !arg_types.any?(&:is_unknown) 137 | if all_known || arg_types[0].dim <= arg_types[1].dim 138 | promote_levels[0] += 1 139 | end 140 | if all_known || arg_types[1].dim <= arg_types[0].dim 141 | promote_levels[1] += 1 142 | end 143 | end 144 | 145 | # auto unvectorize 146 | nargs.times{|i| 147 | unvec = node.args[i].op.name == "vectorize" ? 0 : [[vec_levels[i], deficits[i]].min, 0].max 148 | unvec -= 1 if node.op.name == "build" && unvec > 0 149 | vec_levels[i] -= unvec 150 | deficits[i] -= unvec 151 | } 152 | 153 | # auto promote 154 | nargs.times{|i| 155 | promote = [0, deficits[i]].max 156 | if node.op.name == "build" && promote == 0 && deficits[1-i]<0 157 | promote_levels[i] += 1 158 | deficits[1-i] += 1 159 | elsif promote > 0 && node.op.no_promote 160 | return node.type_error "rank too low for arg #{i+1}" 161 | else 162 | promote_levels[i] += promote 163 | deficits[i] -= promote 164 | end 165 | } 166 | 167 | # auto vectorize 168 | nargs.times{|i| 169 | vec_levels[i] -= deficits[i] 170 | } 171 | 172 | zip_level = vec_levels.max || 0 173 | nargs.times{|i| 174 | rep_levels[i] = zip_level - vec_levels[i] 175 | return node.type_error "rank too high for arg #{i+1}" if rep_levels[i] > zip_level 176 | arg_types[i] += promote_levels[i] 177 | } 178 | 179 | node.zip_level = zip_level 180 | node.rep_levels = rep_levels 181 | node.promote_levels = promote_levels 182 | 183 | vars = solve_type_vars(arg_types, fn_type.specs) 184 | t = spec_to_type(fn_type.ret, vars) 185 | t.vec_level += zip_level 186 | if unvec_at_end 187 | t.vec_level -= 1 188 | t.type.dim += 1 189 | end 190 | t 191 | end 192 | 193 | def solve_type_vars(arg_types, specs) 194 | vars = {} # todo separate hash for ret and uses? 195 | 196 | arg_types.zip(specs) { |arg,spec| 197 | case spec 198 | when VarTypeSpec 199 | (vars[spec.var_name]||=[]) << arg - spec.extra_dims 200 | when ExactTypeSpec 201 | else 202 | error 203 | end 204 | } 205 | 206 | vars.each{|name,uses| 207 | max_min_dim = uses.reject(&:is_unknown).map(&:dim).min 208 | base_elems = uses.map(&:base_elem).uniq 209 | base_elem = if base_elems == [Unknown.base_elem] 210 | max_min_dim = uses.map(&:dim).max 211 | Unknown.base_elem 212 | else 213 | base_elems -= [Unknown.base_elem] 214 | base_elems[0] 215 | end 216 | 217 | vars[name] = Type.new([max_min_dim,0].max, base_elem) 218 | } 219 | vars 220 | end 221 | 222 | def rank_deficits(arg_types, specs, vars) 223 | arg_types.zip(specs).map{|arg,spec| 224 | spec_dim = case spec 225 | when VarTypeSpec 226 | vars[spec.var_name].max_pos_dim + spec.extra_dims 227 | when ExactTypeSpec 228 | spec.type.dim 229 | else 230 | error 231 | end 232 | if arg.is_unknown 233 | [spec_dim - arg.dim, 0].min 234 | else 235 | spec_dim - arg.dim 236 | end 237 | } 238 | end 239 | 240 | def check_base_elem_constraints(specs, arg_types) 241 | uses={} 242 | arg_types.zip(specs).all?{|type,spec| 243 | spec.check_base_elem(uses,type) 244 | } 245 | end 246 | -------------------------------------------------------------------------------- /ir.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | $ir_node_count = 0 3 | class IR < Struct.new( 4 | :op, # these set during construction 5 | :from, # ast 6 | :raw_args, # args with vars not expanded 7 | :args, # args with vars expanded 8 | :type_with_vec_level, # this and rest calculted in infer 9 | :zip_level, 10 | :promise, 11 | :id, # set during init 12 | :in_q, 13 | :used_by, 14 | :rep_levels, 15 | :promote_levels, 16 | :last_error, 17 | :type_updates, # for detecting inf type 18 | ) 19 | def initialize(*args) 20 | super(*args) 21 | self.id = $ir_node_count += 1 22 | end 23 | def type 24 | type_with_vec_level.type 25 | end 26 | def vec_level 27 | type_with_vec_level.vec_level 28 | end 29 | def type_error(msg) 30 | self.last_error ||= AtlasTypeError.new msg,self 31 | UnknownV0 32 | end 33 | end 34 | 35 | # todo check this logic 36 | # does do more work, as internal paren vars are created even if they can't be reached 37 | def update(new,old,context,parents) 38 | if old.op.name == "var" 39 | name = old.from.token.str 40 | return false if parents[name] 41 | parents[name] = true 42 | ans = new != context[old.from.token.str] || update(new,new,context,parents) 43 | parents[name] = false 44 | ans 45 | elsif old.op.name == "ans" 46 | false 47 | else 48 | changed = !new.args || new.args.zip(old.raw_args).any?{|new_arg,old_arg| 49 | update(new_arg,old_arg,context,parents) 50 | } 51 | old.args = old.promise = old.type_with_vec_level = nil if changed 52 | changed 53 | end 54 | end 55 | 56 | def to_ir(ast,context,last) 57 | context.each{|k,v| update(v,v,context,{}) } 58 | ir=create_ir_and_set_vars(ast,context,last) 59 | lookup_vars(ir,context) 60 | end 61 | 62 | def create_ir_and_set_vars(node,context,last) 63 | if node.op.name == "set" 64 | raise "only identifiers may be set" if node.args[1].op.name != "var" 65 | set(node.args[1].token, node.args[0], context,last) 66 | elsif node.op.name == "save" 67 | ir = create_ir_and_set_vars(node.args[0],context,last) 68 | vars = [*'a'..'z']-context.keys 69 | raise(StaticError.new("out of save vars", node)) if vars.empty? 70 | set(Token.new(vars[0]),ir,context,last) 71 | elsif node.op.name == "ans" 72 | raise StaticError.new("there is no last ans to refer to",node) if !last 73 | last 74 | else 75 | args=node.args.map{|arg|create_ir_and_set_vars(arg,context,last)} 76 | args.reverse! if node.is_flipped 77 | IR.new(node.op,node,args) 78 | end 79 | end 80 | 81 | def lookup_vars(node,context) 82 | return node if node.args 83 | if node.op.name == "var" 84 | name = node.from.token.str 85 | val = get(context, name, node.from) 86 | val = IR.new(UnknownOp,node.from,[]) if val == node # todo this isn't actually useful is it? 87 | lookup_vars(val,context) 88 | else 89 | node.args = :processing 90 | node.args = node.raw_args.map{|arg| lookup_vars(arg,context) } 91 | node 92 | end 93 | end 94 | 95 | def set(t,node,context,last) 96 | name = t.str 97 | $warn_on_unset_vars ||= name.size > 1 && !name[/_/] 98 | if Commands[name] || AllOps[name] 99 | warn("overwriting %p even though it is an op" % name, t) 100 | Ops0.delete(name) 101 | Ops1.delete(name) 102 | Ops2.delete(name) 103 | AllOps.delete(name) 104 | Commands.delete(name) 105 | end 106 | if AST === node 107 | ir = create_ir_and_set_vars(node,context,last) 108 | else # IR 109 | ir = node 110 | end 111 | context[name] = ir 112 | end 113 | 114 | def get(context,name,from) 115 | return context[name] if context[name] 116 | warn "using unset var",from if $warn_on_unset_vars 117 | if numeral = to_roman_numeral(name) 118 | type = Num 119 | impl = numeral 120 | elsif name.size>1 121 | if Commands[name] || AllOps[name] 122 | warn("using %p as identifier string even though it is an op" % name, from) 123 | end 124 | type = Str 125 | impl = str_to_lazy_list(name) 126 | else 127 | type = Char 128 | impl = name[0].ord 129 | end 130 | IR.new(create_op(name: "data",type: type,impl: impl),from,[]) 131 | end 132 | 133 | # this handles roman numerals in standard form 134 | # a gimmick to provide a nice way of reprsenting some common numbers in few characters 135 | RN = {"I"=>1,"V"=>5,"X"=>10,"L"=>50,"C"=>100,"D"=>500,"M"=>1000} 136 | def to_roman_numeral(s) 137 | return nil if s.chars.any?{|c|!RN[c]} || !(s =~ /^M{0,3}(CM|CD|D?C?{3})(XC|XL|L?X?{3})(IX|IV|V?I?{3})$/) 138 | sum=0 139 | s.length.times{|i| 140 | v=RN[s[i]] 141 | if i < s.length-1 && RN[s[i+1]] > v 142 | sum-=v 143 | else 144 | sum+=v 145 | end 146 | } 147 | sum 148 | end 149 | -------------------------------------------------------------------------------- /lazylib.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | def run(root) 3 | v = Promise.new{yield(make_promises(root))} 4 | print_string(v) 5 | end 6 | 7 | def make_promises(node) 8 | return node.promise if node.promise 9 | arg_types = node.args.zip(0..).map{|a,i|a.type + a.vec_level - node.zip_level + node.rep_levels[i] + node.promote_levels[i]} 10 | args = nil 11 | node.promise = Promise.new { 12 | zipn(node.zip_level, args, node.op.impl[arg_types, node]) 13 | } 14 | args = node.args.zip(0..).map{|arg,i| 15 | promoted = promoten(arg.vec_level, node.promote_levels[i], make_promises(arg)) 16 | repn(node.rep_levels[i], promoted) 17 | } 18 | node.promise 19 | end 20 | 21 | class Promise 22 | attr_accessor :expect_non_empty 23 | def initialize(&block) 24 | @impl=block 25 | end 26 | def empty 27 | value==[] 28 | end 29 | def value 30 | if Proc===@impl 31 | begin 32 | raise InfiniteLoopError.new "infinite loop detected",self,nil if @calculating # todo fix from location 33 | @calculating=true 34 | @impl=@impl[] 35 | rescue DynamicError => e 36 | @impl = e 37 | ensure 38 | # not really needed since new promises are created rather than reused 39 | @calculating=false 40 | end 41 | raise DynamicError.new "infinite loop was assumed to be non empty, but was empty",nil if expect_non_empty && @impl == [] 42 | end 43 | raise @impl if DynamicError===@impl 44 | @impl 45 | end 46 | end 47 | 48 | # Use this to avoid creating promises that are pointless because the value is constant or it will immediately be computed after construction. 49 | class Const < Struct.new(:value) 50 | def empty 51 | value==[] 52 | end 53 | end 54 | class Object 55 | def const 56 | Const.new(self) 57 | end 58 | end 59 | 60 | def take(n, a) 61 | return [] if n < 1 || a.empty 62 | [a.value[0], Promise.new{ take(n-1, a.value[1]) }] 63 | end 64 | 65 | def drop(n, a) 66 | while n>=1 && !a.empty 67 | n-=1 68 | a=a.value[1] 69 | end 70 | a.value 71 | end 72 | 73 | def range(a,b) 74 | return [] if a>=b 75 | [a.const, Promise.new{range(a+1,b)}] 76 | end 77 | 78 | def range_from(a) 79 | [a.const, Promise.new{range_from(a+1)}] 80 | end 81 | 82 | # this isn't as lazy as possible, but it gets to use hashes 83 | def occurence_count(a,h=Hash.new(-1)) 84 | return [] if a.empty 85 | [(h[to_strict_list(a.value[0])]+=1).const, Promise.new{occurence_count(a.value[1], h)}] 86 | end 87 | 88 | def filter(a,b,b_elem_type) 89 | return [] if a.empty || b.empty 90 | if truthy(b_elem_type,b.value[0]) 91 | [a.value[0],Promise.new{ filter(a.value[1],b.value[1],b_elem_type) }] 92 | else 93 | filter(a.value[1],b.value[1],b_elem_type) 94 | end 95 | end 96 | 97 | def sortby(a,b,t) 98 | fromby(sort(toby(a,b),t,true)) 99 | end 100 | 101 | def toby(a,b) 102 | return Null if a.empty || b.empty 103 | Promise.new{ [[a.value[0], b.value[0]], toby(a.value[1], b.value[1])] } 104 | end 105 | 106 | def fromby(a) 107 | return [] if a == [] 108 | [Promise.new{a[0][0].value}, Promise.new{fromby(a[1].value)}] 109 | end 110 | 111 | # It would be very interesting and useful to design a more lazy sorting algorithm 112 | # so that you can select ith element in O(n) total time after sorting a list. 113 | def sort(a,t,by=false) 114 | return [] if a.empty 115 | return a.value if a.value[1].empty 116 | n=len(a) 117 | left=take(n/2, a).const 118 | right=drop(n/2, a).const 119 | merge(sort(left,t,by),sort(right,t,by),t,by) 120 | end 121 | 122 | def merge(a,b,t,by) 123 | return b if a==[] 124 | return a if b==[] 125 | if (by ? spaceship(a[0][1], b[0][1], t) : spaceship(a[0], b[0],t)) <= 0 126 | [a[0], Promise.new{merge(a[1].value,b,t,by)}] 127 | else 128 | [b[0], Promise.new{merge(a,b[1].value,t,by)}] 129 | end 130 | end 131 | 132 | def to_strict_list(a,sofar=[]) 133 | a=a.value 134 | return a if !(Array===a) 135 | return sofar if a==[] 136 | to_strict_list(a[1],sofar< (value -> Promise) -> value 205 | def map(a,&b) 206 | a.empty ? [] : [Promise.new{b[a.value[0]]}, Promise.new{map(a.value[1],&b)}] 207 | end 208 | 209 | # value -> value 210 | # truncate as soon as encounter empty list 211 | def trunc(a) 212 | a.empty || a.value[0].empty ? [] : [a.value[0], Promise.new{trunc(a.value[1])}] 213 | end 214 | 215 | def transpose(a) 216 | return [] if a.empty 217 | return transpose(a.value[1]) if a.value[0].empty 218 | broken = trunc(a.value[1]).const 219 | hds = Promise.new{ map(broken){|v|v.value[0].value} } 220 | tls = Promise.new{ map(broken){|v|v.value[1].value} } 221 | [Promise.new{[a.value[0].value[0],hds]}, 222 | Promise.new{transpose [a.value[0].value[1],tls].const}] 223 | end 224 | 225 | def last(a) 226 | prev=nil 227 | until a.empty 228 | prev = a 229 | a = a.value[1] 230 | end 231 | raise DynamicError.new("empty last", nil) if prev == nil 232 | prev.value[0].value 233 | end 234 | 235 | # n = int number of dims to zip 236 | # a = args, [promise] 237 | # f = impl, promises -> value 238 | # returns value 239 | def zipn(n,a,f) 240 | return f[*a] if n <= 0 || a==[] 241 | faith = [] 242 | return [] if a.any?{|i| 243 | begin 244 | i.empty 245 | rescue InfiniteLoopError => e 246 | # gotta have faith 247 | # solves this type of problem: a=!:,0 +a ::1;2;:3;4 248 | faith << i 249 | false # not empty, for now... 250 | end 251 | } 252 | faith.each{|i| i.expect_non_empty = true } 253 | [Promise.new{zipn(n-1,a.map{|i|Promise.new{i.value[0].value}},f) }, 254 | Promise.new{zipn(n,a.map{|i|Promise.new{i.value[1].value}},f) }] 255 | end 256 | 257 | def repeat(a) 258 | ret = [a] 259 | ret << ret.const 260 | ret 261 | end 262 | 263 | def repn(n,a) 264 | if n<=0 265 | a 266 | else 267 | Promise.new{ repeat(repn(n-1,a)) } 268 | end 269 | end 270 | 271 | def promoten(vec_level,n,a) 272 | if n<=0 273 | a 274 | else 275 | Promise.new{zipn(vec_level,[a],-> av { 276 | [promoten(0,n-1,av),Null] 277 | })} 278 | end 279 | end 280 | 281 | def sum(a) 282 | return 0 if a.empty 283 | a.value[0].value+sum(a.value[1]) 284 | end 285 | 286 | def prod(a) 287 | return 1 if a.empty 288 | a.value[0].value*prod(a.value[1]) 289 | end 290 | 291 | # value -> value -> value 292 | def spaceship(a,b,t) 293 | if t.dim>0 294 | return 0 if a.empty && b.empty 295 | return -1 if a.empty 296 | return 1 if b.empty 297 | s0 = spaceship(a.value[0],b.value[0],t-1) 298 | return s0 if s0 != 0 299 | return spaceship(a.value[1],b.value[1],t) 300 | else 301 | a.value<=>b.value 302 | end 303 | end 304 | 305 | def to_base(a,b,sign) 306 | return [] if a==0 307 | raise DynamicError.new "base 0", nil if b==0 308 | digit = a%b*sign 309 | [digit.const, Promise.new{to_base((a-digit*sign)/b,b,sign)}] 310 | end 311 | 312 | def from_base(a,b) 313 | mult=1 314 | x=0 315 | until a.empty 316 | x+=a.value[0].value*mult 317 | mult*=b 318 | a=a.value[1] 319 | end 320 | x 321 | end 322 | 323 | def len(a) 324 | return 0 if a.empty 325 | return 1+len(a.value[1]) 326 | end 327 | 328 | # value -> Promise -> value 329 | def append(v,r) 330 | v.empty ? r.value : [v.value[0],Promise.new{append(v.value[1], r)}] 331 | end 332 | 333 | # promise -> promise -> bool -> (value -> promise -> ... -> value) -> value 334 | def concat_map(v,rhs,first=true,&b) 335 | if v.empty 336 | rhs.value 337 | else 338 | b[v.value[0],Promise.new{concat_map(v.value[1],rhs,false,&b)},first] 339 | end 340 | end 341 | 342 | def concat(a) 343 | concat_map(a,Null){|i,r,first|append(i,r)} 344 | end 345 | 346 | def inspect_value(t,value,zip_level) 347 | inspect_value_h(t,value,Null,zip_level) 348 | end 349 | 350 | def inspect_value_h(t,value,rhs,zip_level) 351 | if t==Str && zip_level <= 0 352 | ['"'.ord.const, Promise.new{ 353 | concat_map(value,Promise.new{str_to_lazy_list('"',rhs)}){|v,r,first| 354 | str_to_lazy_list(escape_str_char(v.value),r) 355 | } 356 | }] 357 | elsif t==Num 358 | str_to_lazy_list(value.value.to_s,rhs) 359 | elsif t==Char 360 | str_to_lazy_list(inspect_char(value.value),rhs) 361 | else #List 362 | [(zip_level>0?"<":"[").ord.const, Promise.new{ 363 | concat_map(value,Promise.new{str_to_lazy_list((zip_level>0?">":"]"),rhs)}){|v,r,first| 364 | first ? 365 | inspect_value_h(t-1,v,r,zip_level-1) : 366 | [','.ord.const,Promise.new{inspect_value_h(t-1,v,r,zip_level-1)}] 367 | } 368 | }] 369 | end 370 | end 371 | 372 | # convert a from int to str if tb == str and ta == int, but possibly vectorized 373 | def coerce2s(ta, a, tb) 374 | return a if ta==tb || tb.is_unknown || ta.is_unknown #?? 375 | case [ta.base_elem,tb.base_elem] 376 | when [:num,:char] 377 | raise if ta.dim+1 != tb.dim 378 | return Promise.new{zipn(ta.dim,[a],->av{str_to_lazy_list(av.value.to_s)})} 379 | when [:char,:num] 380 | raise if ta.dim != tb.dim+1 381 | return a 382 | else 383 | raise "coerce of %p %p not supported"%[ta,tb] 384 | end 385 | end 386 | 387 | def to_string(t, value, line_mode) 388 | # print 1d lists on new lines 389 | dim = line_mode && t == Num+1 || t == Str+1 ? 2 : t.string_dim 390 | added_newline = dim <= 1 && !value.empty ? Newline : Null 391 | to_string_h(t,value,dim,added_newline) 392 | end 393 | 394 | def to_string_h(t, value, dim, rhs) 395 | if t == Num 396 | inspect_value_h(t, value, rhs, 0) 397 | elsif t == Char 398 | [value, rhs] 399 | elsif t == Str 400 | append(value, rhs) 401 | else # List 402 | # print separator1 after every element for better interactive io 403 | separator1 = dim == 2 ? Newline : Null 404 | # but don't do this for separators like space, you would end up with trailing space in output, print them between elements 405 | separator2 = [Null,Space,Null][dim] || Newline 406 | 407 | concat_map(value,rhs){|v,r,first| 408 | svalue = Promise.new{ to_string_h(t-1, v, dim-1, Promise.new{append(separator1, r)}) } 409 | first ? svalue.value : append(separator2, svalue) 410 | } 411 | end 412 | end 413 | 414 | def print_string(value) 415 | while !value.empty 416 | v = value.value[0].value 417 | # raw print, multiple bytes will show up as one if utf is used. output will match source code so will look correct if editor encoding=terminal encoding 418 | raise DynamicError.new("char value, %s, outside of byte range" % v, nil) if v<0 || v>255 419 | putc v 420 | value = value.value[1] 421 | end 422 | end 423 | 424 | def is_digit(i) 425 | i>=48 && i<58 426 | end 427 | 428 | def read_num(s) 429 | multiplier=1 430 | until s.empty || is_digit(s.value[0].value) 431 | if s.value[0].value == ?-.ord 432 | multiplier *= -1 433 | else 434 | multiplier = 1 435 | end 436 | s = s.value[1] 437 | end 438 | v = 0 439 | found_int = false 440 | until s.empty || !is_digit(s.value[0].value) 441 | found_int = true 442 | v = v*10+s.value[0].value-48 443 | s = s.value[1] 444 | end 445 | 446 | # find decimal pointer and numbers after 447 | if found_int && !s.empty && s.value[0].value == ?..ord && !s.value[1].empty && is_digit(s.value[1].value[0].value) 448 | decimals = "0." 449 | s = s.value[1] 450 | until s.empty || !is_digit(s.value[0].value) 451 | found_int = true 452 | decimals << s.value[0].value.chr 453 | s = s.value[1] 454 | end 455 | v += decimals.to_f 456 | end 457 | 458 | [multiplier * v, found_int, s] 459 | end 460 | 461 | # string value -> [string] value 462 | def lines(s) 463 | return [] if s.empty 464 | 465 | after = Promise.new{lines(s.value[1])} 466 | if s.value[0].value == 10 467 | [Null, after] 468 | else 469 | [Promise.new{ 470 | after.empty ? [s.value[0], Null] : [s.value[0], after.value[0]] 471 | }, 472 | Promise.new{ 473 | after.empty ? [] : after.value[1].value 474 | }] 475 | end 476 | end 477 | 478 | # string promise -> [int] value 479 | def split_non_digits(s) 480 | return [] if s.empty 481 | v,found,s2=read_num(s) 482 | return [] if !found 483 | [v.const,Promise.new{split_non_digits(s2)}] 484 | end 485 | 486 | ReadStdin = Promise.new{ read_stdin } 487 | def read_stdin 488 | c=STDIN.getc 489 | if c.nil? 490 | [] 491 | else 492 | [c.ord.const, Promise.new{ read_stdin }] 493 | end 494 | end 495 | 496 | Null = [].const 497 | Newline = [10.const,Null].const 498 | Space = [32.const,Null].const 499 | 500 | def str_to_lazy_list(s,rhs=Null) 501 | to_lazy_list(s.chars.map(&:ord), rhs) 502 | end 503 | 504 | def to_lazy_list(l, rhs=Null, ind=0) 505 | ind >= l.size ? rhs.value : [l[ind].const, Promise.new{to_lazy_list(l, rhs, ind+1)}] 506 | end 507 | 508 | def to_eager_list(v) 509 | x=[] 510 | until v.empty 511 | x<1,9=>1,10=>1,11=>1,12=>1,13=>1,32=>1} 522 | 523 | def truthy(type, value) 524 | if type == Num 525 | value.value > 0 526 | elsif type == Char 527 | !FalseChars.include?(value.value) 528 | else # List 529 | !value.empty 530 | end 531 | end 532 | -------------------------------------------------------------------------------- /lex.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | class Token < Struct.new(:str,:char_no,:line_no) 3 | def ensure_name 4 | raise ParseError.new("cannot set %p (not an id)" % self.str, self) unless IdRx =~ str 5 | self 6 | end 7 | end 8 | AllSymbols='@!?`~#%^&*-_=+[]|;<,>.()\'"{}$/\\:'.chars.map{|c|Regexp.escape c} 9 | 10 | NumRx = /([0-9]+([.][0-9]+)?(e-?[0-9]+)?)|([.][0-9]+(e-?[0-9]+)?)/ 11 | CharRx = /'(\\n|\\0|\\x[0-9a-fA-F][0-9a-fA-F]|.)/m 12 | StrRx = /"(\\.|[^"])*"?/ 13 | AtomRx = /#{CharRx}|#{NumRx}|#{StrRx}/ 14 | SymRx = /#{AllSymbols*'|'}/ 15 | CommentRx = /--.*/ 16 | IgnoreRx = /#{CommentRx}|[ \t]+/ 17 | NewlineRx = /\r\n|\r|\n/ 18 | IdRx = /[^#{AllSymbols.join} \t\n\r0-9][^#{AllSymbols.join} \t\n\r]*/ # anything else consecutively, also allow numbers in name if not first char 19 | 20 | def lex(code,line_no=1) # returns a list of lines which are a list of tokens 21 | tokens = [[]] 22 | char_no = 1 23 | code.scan(/#{AtomRx}|#{CommentRx}|#{SymRx}|#{NewlineRx}|#{IgnoreRx}|#{IdRx}/m) {|matches| 24 | token=Token.new($&,char_no,line_no) 25 | match=$& 26 | line_no += $&.scan(NewlineRx).size 27 | if match[NewlineRx] 28 | char_no = match.size-match.rindex(NewlineRx) 29 | else 30 | # FYI this counts tab as 1, and utf8 characters as len of their bytes, could be misleading 31 | char_no += match.size 32 | end 33 | if token.str =~ /^#{IgnoreRx}$/ 34 | # pass 35 | elsif token.str =~ /^#{NewlineRx}$/ 36 | tokens << [] 37 | else 38 | tokens[-1] << token 39 | end 40 | } 41 | [tokens,line_no+1] 42 | end 43 | 44 | -------------------------------------------------------------------------------- /ops.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | require_relative "./type.rb" 3 | require_relative "./spec.rb" 4 | 5 | MacroImpl = -> *args { raise "macro impl called" } 6 | 7 | class Op < Struct.new( 8 | :name, 9 | :sym, # optional 10 | :type, 11 | :type_summary, 12 | :examples, 13 | :desc, 14 | :ref_only, 15 | :no_promote, 16 | :impl, 17 | :tests) 18 | def narg 19 | return 0 if Proc === type # Proc only used for input 20 | type ? type[0].specs.size : 0 21 | end 22 | def help(out=STDOUT) 23 | out.puts "#{name} #{sym}" 24 | out.puts desc if desc 25 | if type_summary 26 | out.puts type_summary 27 | else 28 | type.each{|t| 29 | out.puts t.inspect.gsub('->',"\xE2\x86\x92").gsub('[Char]','Str') 30 | } 31 | end 32 | (examples+tests).each{|example| 33 | out.puts example.gsub('->',"\xE2\x86\x92") 34 | } 35 | misc = [] 36 | out.puts 37 | end 38 | def add_test(s) 39 | tests< arg_types,from { poly_impl[*arg_types] } 74 | elsif impl_with_loc 75 | built_impl = -> arg_types,from { impl_with_loc[from] } 76 | elsif final_impl 77 | built_impl = final_impl 78 | else 79 | built_impl = -> arg_types,from { Proc===impl ? impl : lambda { impl } } 80 | end 81 | examples = [] 82 | examples << example if example 83 | examples << example2 if example2 84 | 85 | if coerce 86 | f = built_impl 87 | built_impl = -> t,from { -> a,b { f[t,from][coerce2s(t[0],a,t[1]),coerce2s(t[1],b,t[0])] }} 88 | end 89 | 90 | Op.new(name,sym,type,type_summary,examples,desc,ref_only,no_promote,built_impl,[]) 91 | end 92 | 93 | ApplyModifier = "@" 94 | FlipModifier = "\\" 95 | OpsList = [ 96 | "math", 97 | create_op( 98 | name: "add", 99 | sym: "+", 100 | example: "1+2 -> 3", 101 | example2: "'a+1 -> 'b", 102 | type: { [Num,Num] => Num, 103 | [Num,Char] => Char, 104 | [Char,Num] => Char }, 105 | impl: -> a,b { a.value + b.value }) 106 | .add_test("'a+1.2 -> 'b"), 107 | create_op( 108 | name: "sum", 109 | sym: "+", 110 | example: "1,2,3,4+ -> 10", 111 | type: { [Num] => Num }, 112 | no_promote: true, 113 | impl: -> a { sum(a) }) 114 | .add_test("1;>+ -> 0"), 115 | create_op( 116 | name: "sub", 117 | sym: "-", 118 | example: '5-3 -> 2', 119 | example2: "'b-'a -> 1", 120 | type: { [Num,Num] => Num, 121 | [Char,Num] => Char, 122 | [Num,Char] => Char, 123 | [Char,Char] => Num }, 124 | poly_impl: ->at,bt { flipif bt.is_char && !at.is_char, -> a,b { a.value - b.value }}) 125 | .add_test("1-'b -> 'a"), 126 | create_op( 127 | name: "mult", 128 | example: '2*3 -> 6', 129 | sym: "*", 130 | type: { [Num,Num] => Num }, 131 | impl: -> a,b { a.value * b.value }), 132 | create_op( 133 | name: "prod", 134 | sym: "*", 135 | example: "1,2,3,4* -> 24", 136 | no_promote: true, 137 | type: { [Num] => Num }, 138 | impl: -> a { prod(a) }) 139 | .add_test("1;>* -> 1"), 140 | create_op( 141 | name: "div", 142 | desc: "0/0 is 0", 143 | example: '7/3 -> 2', 144 | sym: "/", 145 | type: { [Num,Num] => Num }, 146 | impl_with_loc: -> from { -> a,b { 147 | if b.value==0 148 | if a.value == 0 149 | 0 150 | else 151 | raise DynamicError.new("div 0", from) 152 | end 153 | else 154 | a.value/b.value 155 | end 156 | }}) 157 | .add_test("10/5 -> 2") 158 | .add_test("9/5 -> 1") 159 | .add_test("11/(5-) -> -3") 160 | .add_test("10/(5-) -> -2") 161 | .add_test("11-/5 -> -3") 162 | .add_test("10-/5 -> -2") 163 | .add_test("10-/(5-) -> 2") 164 | .add_test("9-/(5-) -> 1") 165 | .add_test("1/0 -> DynamicError") 166 | .add_test("0/0 -> 0"), 167 | create_op( 168 | name: "mod", 169 | desc: "anything mod 0 is 0", 170 | example: '7%3 -> 1', 171 | sym: "%", 172 | type: { [Num,Num] => Num }, 173 | impl_with_loc: -> from { -> a,b { 174 | if b.value==0 175 | 0 176 | else 177 | a.value % b.value 178 | end 179 | }}) 180 | .add_test("10%5 -> 0") 181 | .add_test("9%5 -> 4") 182 | .add_test("11%(5-) -> -4") 183 | .add_test("10%(5-) -> 0") 184 | .add_test("11-%5 -> 4") 185 | .add_test("10-%5 -> 0") 186 | .add_test("10-%(5-) -> 0") 187 | .add_test("9-%(5-) -> -4") 188 | .add_test("5%0 -> 0"), 189 | create_op( 190 | name: "pow", # consider allowing rationals or even imaginary numbers 191 | desc: "negative exponent will result in a rational, but this behavior is subject to change and not officially supported, similarly for imaginary numbers", 192 | example: '2^3 -> 8', 193 | sym: "^", 194 | type: { [Num,Num] => Num }, 195 | impl: -> a,b { a.value ** b.value }), 196 | create_op( 197 | name: "neg", 198 | sym: "-", 199 | type: { Num => Num }, 200 | example: '2- -> -2', 201 | impl: -> a { -a.value } 202 | ), create_op( 203 | name: "abs", 204 | sym: "|", 205 | type: { Num => Num }, 206 | example: '2-| -> 2', 207 | example2: '2| -> 2', 208 | impl: -> a { a.value.abs }), 209 | create_op( 210 | name: "floor", 211 | sym: "&", 212 | type: { Num => Num }, 213 | example: '1.3& -> 1', 214 | impl: -> a { a.value.floor }), 215 | create_op( 216 | name: "toBase", 217 | sym: ";", 218 | type: { [Num,Num] => [Num], 219 | [Num,Char] => Str }, 220 | example: '6;2 -> [0,1,1]', 221 | impl: -> a,b { to_base(a.value.abs,b.value,a.value<=>0) }) 222 | .add_test('3,3.;2 -> <[1,1],[1,1]>') 223 | .add_test('6;(2-) -> [0,-1,-1,-1]') 224 | .add_test('6-;2 -> [0,-1,-1]') 225 | .add_test('5.5;2 -> [1.5,0.0,1.0]') 226 | .add_test('5.5-;2 -> [-1.5,-0.0,-1.0]'), 227 | create_op( 228 | name: "fromBase", 229 | sym: ";", 230 | type: { [[Num],Num] => Num, 231 | [Str,Num] => Num, 232 | [Str,Char] => Num }, 233 | example: '0,1,1;2 -> 6', 234 | impl: -> a,b { from_base(a,b.value) }) 235 | .add_test('0,1,1,1-%;(2-) -> 6') 236 | .add_test('0,1,1-%;2 -> -6') 237 | .add_test('1.5,0.0,1.0;2 -> 5.5') 238 | .add_test('"abc";\'d -> 999897'), 239 | "vector", 240 | create_op( 241 | name: "unvec", 242 | sym: "%", 243 | example: '1,2+3% -> [4,5]', 244 | type: { v(A) => [A] }, 245 | impl: -> a { a.value }, 246 | ), create_op( 247 | name: "vectorize", 248 | sym: ".", 249 | example: '1,2,3. -> <1,2,3>', 250 | type: { [A] => v(A) }, 251 | impl: -> a { a.value }), 252 | create_op( 253 | name: "range", 254 | sym: ":", 255 | example: '3:7 -> <3,4,5,6>', 256 | type: { [Num,Num] => v(Num), 257 | [Char,Char] => v(Char) }, 258 | impl: -> a,b { range(a.value, b.value) }) 259 | .add_test("5:3 -> <>") 260 | .add_test("1.5:5 -> <1.5,2.5,3.5,4.5>"), 261 | create_op( 262 | name: "repeat", 263 | sym: ",", 264 | example: '2, -> <2,2,2,2,2...', 265 | type: { A => v(A) }, 266 | impl: -> a { repeat(a) }), 267 | create_op( 268 | name: "from", 269 | sym: ":", 270 | example: '3: -> <3,4,5,6,7,8...', 271 | type: { Num => v(Num), 272 | Char => v(Char) }, 273 | impl: -> a { range_from(a.value) }), 274 | create_op( 275 | name: "consDefault", 276 | sym: "^", 277 | example: '2,3.^ -> <0,2,3>', 278 | type: { v(A) => v(A) }, 279 | type_summary: " -> \n[a] -> [a]", 280 | poly_impl: -> at { d=(at-1).default_value.const; -> a { [d,a] }}) 281 | .add_test("1,2^ -> [0,1,2]") 282 | .add_test("1^ -> <0,1>"), 283 | "basic list", 284 | create_op( 285 | name: "head", 286 | sym: "[", 287 | example: '"abc"[ -> \'a', 288 | type: { [A] => A }, 289 | no_promote: true, 290 | impl_with_loc: -> from { -> a { 291 | raise DynamicError.new "head on empty list",from if a.empty 292 | a.value[0].value 293 | }}, 294 | ), create_op( 295 | name: "last", 296 | sym: "]", 297 | no_promote: true, 298 | example: '"abc"] -> \'c', 299 | type: { [A] => A }, 300 | impl_with_loc: -> from { -> a { 301 | raise DynamicError.new "last on empty list",from if a.empty 302 | last(a) 303 | }} 304 | ), create_op( 305 | name: "tail", 306 | example: '"abc"> -> "bc"', 307 | sym: ">", 308 | no_promote: true, 309 | type: { [A] => [A] }, 310 | impl_with_loc: -> from { -> a { 311 | raise DynamicError.new "tail on empty list",from if a.empty 312 | a.value[1].value}} 313 | ), create_op( 314 | name: "init", 315 | example: '"abc"< -> "ab"', 316 | sym: "<", 317 | no_promote: true, 318 | type: { [A] => [A] }, 319 | impl_with_loc: -> from { -> a { 320 | raise DynamicError.new "init on empty list",from if a.empty 321 | init(a) 322 | }} 323 | ), create_op( 324 | name: "len", 325 | example: '"asdf"#'+' -> 4', 326 | sym: "#", 327 | type: { [A] => Num }, 328 | no_promote: true, 329 | impl: -> a { len(a) }), 330 | create_op( 331 | name: "take", 332 | sym: "[", 333 | example: '"abcd"[3 -> "abc"', 334 | type: { [[A],Num] => [A], 335 | [Num,[Achar]] => [A] }, 336 | poly_impl: ->at,bt { flipif bt.is_char, -> a,b { take(b.value, a) }} 337 | ).add_test('"abc"[(2-) -> ""') 338 | .add_test('"abc"[1.2 -> "a"') 339 | .add_test('1["abc" -> "a"') 340 | .add_test('""[2 -> ""'), 341 | create_op( 342 | name: "drop", 343 | sym: "]", 344 | example: '"abcd"]3 -> "d"', 345 | type: { [[A],Num] => [A], 346 | [Num,[Achar]] => [A] }, 347 | poly_impl: ->at,bt { flipif bt.is_char, -> a,b { drop(b.value, a) }} 348 | ).add_test('"abc"](2-) -> "abc"') 349 | .add_test('"abc"]1.2 -> "bc"') 350 | .add_test('1]"abc" -> "bc"') 351 | .add_test('""]2 -> ""'), 352 | create_op( 353 | name: "single", 354 | sym: ";", 355 | example: '2; -> [2]', 356 | type: { A => [A] }, 357 | impl: -> a { [a,Null] }), 358 | create_op( 359 | name: "concat", 360 | sym: "_", 361 | no_promote: true, 362 | example: '"abc","123"_ -> "abc123"', 363 | type: { [[A]] => [A] }, 364 | impl: -> a { concat(a) }), 365 | create_op( 366 | name: "append", 367 | sym: "_", 368 | example: '"abc"_"123" -> "abc123"', 369 | type: { [[A],[A]] => [A], 370 | [Anum,[Achar]] => [Achar], 371 | [[Achar],Anum] => [Achar] }, 372 | type_summary: "[*a] [*a] -> [a]", 373 | impl: -> a,b { append(a,b) }, 374 | coerce: true) 375 | .add_test('1_"a" -> "1a"'), 376 | create_op( 377 | name: "cons", 378 | sym: "`", 379 | example: '1`2`3 -> [3,2,1]', 380 | type: { [[A],A] => [A], 381 | [Anum,Achar] => [Achar], 382 | [[[Achar]],Anum] => [[Achar]] }, 383 | type_summary: "[*a] *a -> [a]", 384 | poly_impl: -> ta,tb {-> a,b { [coerce2s(tb,b,ta-1),coerce2s(ta,a,tb+1)] }}) 385 | .add_test('\'a`5 -> ["5","a"]') 386 | .add_test('"a"`(5) -> ["5","a"]') 387 | .add_test('"a";;`(5;) -> [["5"],["a"]]') 388 | .add_test("5`\'a -> \"a5\"") 389 | .add_test('5;`"a" -> ["a","5"]') 390 | .add_test('\'b`\'a -> "ab"'), 391 | create_op( 392 | name: "build", 393 | sym: ",", 394 | example: '1,2,3 -> [1,2,3]', 395 | type: { [[A],[A]] => [A], 396 | [Anum,[Achar]] => [Achar], 397 | [[Achar],Anum] => [Achar] }, 398 | type_summary: "*a *a -> [a]\n[*a] *a -> [a]\n*a [*a] -> [a]", 399 | poly_impl: -> ta,tb {-> a,b { 400 | append(coerce2s(ta,a,tb),coerce2s(tb,b,ta)) 401 | }} 402 | ).add_test("2,1 -> [2,1]") 403 | .add_test('(2,3),1 -> [2,3,1]') 404 | .add_test('(2,3),(4,5),1 -> <[2,3,1],[4,5,1]>') 405 | .add_test('2,(1,0) -> [2,1,0]') 406 | .add_test('(2,3),(1,0) -> [[2,3],[1,0]]') 407 | .add_test('(2,3).,1 -> <[2,1],[3,1]>') 408 | .add_test('(2,3),(4,5).,1 -> <[2,3,1],[4,5,1]>') 409 | .add_test('2,(1,0.) -> <[2,1],[2,0]>') 410 | .add_test('(2,3),(1,0.) -> <[2,3,1],[2,3,0]>') 411 | .add_test('\'a,5 -> "a5"') 412 | .add_test('5,\'a -> "5a"') 413 | .add_test('5,"a" -> ["5","a"]') 414 | .add_test('\'b,\'a -> "ba"'), 415 | "more list", 416 | create_op( 417 | name: "count", 418 | desc: "count the number of times each element has occurred previously", 419 | sym: "=", 420 | example: '"abcaab" = -> [0,0,0,1,2,1]', 421 | type: { [A] => [Num] }, 422 | no_promote: true, 423 | impl: -> a { occurence_count(a) } 424 | ).add_test('"ab","a","ab" count -> [0,0,1]'), 425 | create_op( 426 | name: "filterFrom", 427 | sym: "~", 428 | example: '0,1,1,0 ~ "abcd" -> "bc"', 429 | type: { [v(A),[B]] => [B] }, 430 | poly_impl: -> at,bt { -> a,b { filter(b,a,at-1) }}), 431 | create_op( 432 | name: "sort", 433 | desc: "O(n log n) sort - not optimized for lazy O(n) min/max yet todo", 434 | sym: "!", 435 | example: '"atlas" ! -> "aalst"', 436 | type: { [A] => [A] }, 437 | no_promote: true, 438 | poly_impl: -> at { -> a { sort(a,at-1) }} 439 | ), create_op( 440 | name: "sortFrom", 441 | desc: "stable O(n log n) sort - not optimized for lazy O(n) min/max yet todo", 442 | sym: "!", 443 | example: '3,1,4 ! "abc" -> "bac"', 444 | type: { [v(A),[B]] => [B] }, 445 | poly_impl: -> at,bt { -> a,b { sortby(b,a,at-1) }}) 446 | .add_test('"hi","there" ! (1,2,3) -> [1,2]') 447 | .add_test('"aaaaaa" ! "abcdef" -> "abcdef"'), 448 | create_op( 449 | name: "chunk", 450 | desc: "chunk while truthy", 451 | sym: "?", 452 | example: '"12 3"? -> ["12","3"]', 453 | type: { [A] => [[A]] }, 454 | poly_impl: -> at { -> a { chunk_while(a,a,at-1) } }), 455 | create_op( 456 | name: "chunkFrom", 457 | desc: "chunk while first arg is truthy", 458 | sym: "?", 459 | example: '"11 1" ? "abcd" -> ["ab","d"]', 460 | type: { [v(A),[B]] => [[B]] }, 461 | poly_impl: -> at,bt { -> a,b { chunk_while(b,a,at-1) } }) 462 | .add_test('" 11 " ? "abcde" -> ["","bc","",""]') 463 | .add_test('()?"" -> [""]'), 464 | create_op( 465 | name: "transpose", 466 | sym: "\\", 467 | example: '"abc","1"\\ -> ["a1","b","c"]', 468 | type: { [[A]] => [[A]] }, 469 | impl: -> a { transpose(a) }, 470 | ).add_test('"abc","1234"\ -> ["a1","b2","c3","4"]'), 471 | create_op( 472 | name: "reverse", 473 | sym: "/", 474 | example: '"abc" / -> "cba"', 475 | type: { [A] => [A] }, 476 | no_promote: true, 477 | impl: -> a { reverse(a) }), 478 | create_op( 479 | name: "reshape", 480 | sym: "#", 481 | desc: "Take elements in groups of sizes. If second list runs out, last element is repeated", 482 | example: '"abcdef"#2 -> ["ab","cd","ef"]', 483 | type: { [[A],[Num]] => [[A]], 484 | [[Num],[Achar]] => [[A]] }, 485 | poly_impl: ->at,bt { flipif bt.is_char, -> a,b { reshape(a,b) }}) 486 | .add_test('"abc"#2 -> ["ab","c"]') 487 | .add_test('"abcd"#(2,1) -> ["ab","c","d"]') 488 | .add_test('"."^10#2.5*" " -> ".. ... .. ..."') 489 | .add_test('2#"abcd" -> ["ab","cd"]') 490 | .add_test('""#2 -> []'), 491 | "string", 492 | create_op( 493 | name: "join", 494 | example: '"hi","yo"*" " -> "hi yo"', 495 | sym: "*", 496 | type: { [[Str],Str] => Str, 497 | [[Num],Str] => Str,}, 498 | poly_impl: -> at,bt { -> a,b { join(coerce2s(at,a,Str+1),b) } }) 499 | .add_test('1,2,3*", " -> "1, 2, 3"'), 500 | create_op( 501 | name: "split", 502 | desc: "split, keeping empty results (include at beginning and end)", 503 | example: '"hi, yo"/", " -> ["hi","yo"]', 504 | sym: "/", 505 | type: { [Str,Str] => [Str] }, 506 | impl: -> a,b { split(a,b) }) 507 | .add_test('"abcbcde"/"bcd" -> ["abc","e"]') 508 | .add_test('"ab",*" "/"b "[2 -> ["a","a"]') # test laziness 509 | .add_test('",a,,b,"/"," -> ["","a","","b",""]'), 510 | create_op( 511 | name: "replicate", 512 | example: '"ab"^3 -> "ababab"', 513 | sym: "^", 514 | type: { [Str,Num] => Str, 515 | [Num,Str] => Str }, 516 | poly_impl: -> ta,tb { flipif !ta.is_char, -> a,b { 517 | ipart = concat(take(b.value,repeat(a).const).const) 518 | if b.value.class == Integer 519 | ipart 520 | else 521 | append(ipart.const, take(b.value%1*len(a), a).const) 522 | end 523 | }}) 524 | .add_test('2^"ab" -> "abab"') 525 | .add_test('"abcd"^2.5 -> "abcdabcdab"'), 526 | create_op( 527 | name: "ord", 528 | example: "'a& -> 97", 529 | sym: "&", 530 | type: { Char => Num }, 531 | impl: -> a { a.value }), 532 | "logic", 533 | create_op( 534 | name: "equalTo", 535 | example: '3=3 -> [3]', 536 | example2: '3=0 -> []', 537 | sym: "=", 538 | type: { [A,A] => [A] }, 539 | poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == 0 ? [a,Null] : [] } }) 540 | .add_test("3=2 -> []") 541 | .add_test("1=2 -> []") 542 | .add_test("1=1 -> [1]") 543 | .add_test('\'a=\'a -> "a"') 544 | .add_test("'d=100 -> AtlasTypeError") 545 | .add_test('"abc"="abc" -> ["abc"]') 546 | .add_test('"abc"="abd" -> []') 547 | .add_test('"abc"=\'a -> <"a","","">') 548 | .add_test('"abc"=(\'a.) -> <"a">') 549 | .add_test('"abc".="abd" -> <"a","b","">'), 550 | create_op( 551 | name: "lessThan", 552 | example: '4<5 -> [4]', 553 | example2: '5<4 -> []', 554 | sym: "<", 555 | type: { [A,A] => [A] }, 556 | poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == -1 ? [a,Null] : [] } } 557 | ).add_test("5<4 -> []"), 558 | create_op( 559 | name: "greaterThan", 560 | example: '5>4 -> [5]', 561 | example2: '4>5 -> []', 562 | sym: ">", 563 | type: { [A,A] => [A] }, 564 | poly_impl: -> ta,tb {-> a,b { spaceship(a,b,ta) == 1 ? [a,Null] : [] } } 565 | ).add_test("4>5 -> []"), 566 | create_op( 567 | name: "not", 568 | sym: "~", 569 | type: { A => Num }, 570 | example: '2~ -> 0', 571 | example2: '0~ -> 1', 572 | poly_impl: -> ta { -> a { truthy(ta,a) ? 0 : 1 } }), 573 | create_op( 574 | name: "and", 575 | sym: "&", 576 | example: '1&2 -> 2', 577 | example2: '1-&2 -> -1', 578 | type: { [A,B] => B }, 579 | poly_impl: ->ta,tb { -> a,b { truthy(ta,a) ? b.value : (ta==tb ? a.value : tb.default_value) }} 580 | ), 581 | create_op( 582 | name: "or", 583 | sym: "|", 584 | example: '2|0 -> 2', 585 | example2: '0|2 -> 2', 586 | type: { [A,A] => A, 587 | [Anum,[Achar]] => [Achar], 588 | [[Achar],Anum] => [Achar] }, 589 | type_summary: "*a *a -> a", 590 | poly_impl: ->ta,tb { -> a,b { truthy(ta,a) ? coerce2s(ta,a,tb).value : coerce2s(tb,b,ta).value }}, 591 | ).add_test("0|2 -> 2") 592 | .add_test('1|"b" -> "1"') 593 | .add_test('"b"|3 -> "b"') 594 | .add_test('0|"b" -> "b"') 595 | .add_test('""|2 -> "2"') 596 | .add_test(' 0|\'c -> "c"'), 597 | create_op( 598 | name: "catch", 599 | sym: "}", 600 | example: '1/0 catch -> []', 601 | example2: '1/1 catch -> [1]', 602 | type: { A => [A] }, 603 | impl: -> a { 604 | begin 605 | a.value 606 | [a, Null] 607 | rescue AtlasError # dynamic and inf loop 608 | [] 609 | end 610 | }), 611 | '"io"', 612 | create_op( 613 | name: "inputRaw", 614 | type: Str, 615 | impl: -> { ReadStdin.value }), 616 | create_op( 617 | name: "inputLines", 618 | type: v(Str), 619 | impl: -> { lines(ReadStdin) }), 620 | create_op( 621 | name: "inputVector", 622 | type: v(Num), 623 | impl: -> { split_non_digits(ReadStdin) }), 624 | create_op( 625 | name: "inputMatrix", 626 | type: v([Num]), 627 | impl: -> { map(lines(ReadStdin).const){|v|split_non_digits(v)} }), 628 | create_op( 629 | name: "ans", 630 | type: A, 631 | impl: MacroImpl), 632 | create_op( 633 | name: "read", 634 | sym: "`", 635 | type: { Str => [Num] }, 636 | example: '"1 2 -3"` -> [1,2,-3]', 637 | impl: -> a { split_non_digits(a) }) 638 | .add_test('"1 2.30 -3 4a5 - -6 --7 .8" ` -> [1,2.3,-3,4,5,-6,7,8]'), 639 | create_op( 640 | name: "str", 641 | sym: "`", 642 | example: '12` -> "12"', 643 | type: { Num => Str }, 644 | impl: -> a { inspect_value(Num,a,0) }), 645 | "syntactic sugar", 646 | # Macros, type only used to specify number of args 647 | create_op( 648 | name: "set", 649 | desc: "save to a variable without consuming it", 650 | example: '5@a+a -> 10', 651 | sym: ApplyModifier, 652 | type: { [A,:id] => A }, 653 | impl: MacroImpl, 654 | ), create_op( 655 | name: "save", 656 | desc: "save to next available var (a,b,c,...)", 657 | example: '5{,1,a,2 -> [5,1,5,2]', 658 | sym: "{", 659 | type: { A => A }, 660 | impl: MacroImpl), 661 | 662 | # These are here purely for quickref purposes 663 | create_op( 664 | name: "flip", 665 | sym: "\\", 666 | desc: "reverse order of previous op's args", 667 | example: '2-\\5 -> 3', 668 | ref_only: true, 669 | type: :unused, 670 | type_summary: "op\\", 671 | impl: MacroImpl, 672 | ), create_op( 673 | name: "apply", 674 | sym: "@", 675 | desc: "increase precedence, apply next op before previous op", 676 | example: '2*3@+4 -> 14', 677 | type: {:unused => :unused}, 678 | type_summary: "@op", 679 | impl_with_loc: ->from{raise ParseError.new("@ must be followed by an op or atom",from)}, # this can occur from something like 1@@ or 1@ 680 | ), 681 | ] 682 | ActualOpsList = OpsList.reject{|o|String===o} 683 | 684 | Ops0 = {} 685 | Ops1 = {} 686 | Ops2 = {} 687 | AllOps = {} 688 | 689 | def flipif(cond,impl) 690 | if cond 691 | -> a,b { impl[b,a] } 692 | else 693 | impl 694 | end 695 | end 696 | 697 | def addOp(table,op) 698 | if (existing=table[op.sym]) 699 | combined_type = {} 700 | op.type.each{|s|combined_type[s.orig_key]=s.orig_val} 701 | existing.type.each{|s|combined_type[s.orig_key]=s.orig_val} 702 | combined_impl = -> arg_types,from { 703 | best_match = match_type(existing.type + op.type, arg_types) 704 | if existing.type.include? best_match 705 | existing.impl[arg_types,from] 706 | else 707 | op.impl[arg_types,from] 708 | end 709 | } 710 | combined = create_op( 711 | sym: op.sym, 712 | type: combined_type, 713 | final_impl: combined_impl, 714 | ) 715 | table[op.sym] = combined 716 | else 717 | table[op.sym] = op 718 | end 719 | table[op.name] = op 720 | end 721 | 722 | ActualOpsList.each{|op| 723 | next if op.ref_only 724 | ops = case op.narg 725 | when 0 726 | addOp(Ops0, op) 727 | when 1 728 | addOp(Ops1, op) 729 | when 2 730 | addOp(Ops2, op) 731 | else; error; end 732 | raise "name conflict #{op.name}" if AllOps.include? op.name 733 | AllOps[op.name] = AllOps[op.sym] = op 734 | } 735 | ImplicitOp = Ops2["build"] 736 | AllOps[""]=Ops2[""]=ImplicitOp # allow @ to flip the implicit op (todo pointless for multiplication) 737 | EmptyOp = create_op( 738 | name: "empty", 739 | type: Empty, 740 | impl: []) 741 | UnknownOp = create_op( 742 | name: "unknown", 743 | type: Unknown, 744 | impl_with_loc: -> from { raise AtlasTypeError.new("cannot use value of the unknown type", from) } 745 | ) 746 | Var = Op.new("var") 747 | 748 | def create_num(t) 749 | create_op( 750 | name: "data", 751 | type: Num, 752 | impl: t.str[/[.e]/] ? t.str.to_f : t.str.to_i 753 | ) 754 | end 755 | 756 | def create_str(t) 757 | raise LexError.new("unterminated string",t) if t.str[-1] != '"' || t.str.size==1 758 | create_op( 759 | name: "data", 760 | type: Str, 761 | impl: str_to_lazy_list(parse_str(t.str[1...-1])) 762 | ) 763 | 764 | end 765 | def create_char(t) 766 | raise LexError.new("empty char",t) if t.str.size < 2 767 | create_op( 768 | name: "data", 769 | type: Char, 770 | impl: parse_char(t.str[1..-1]).ord 771 | ) 772 | end 773 | 774 | IO_Vars = {"r" => 'inputRaw', 775 | "l" => 'inputLines', 776 | "v" => 'inputVector', 777 | "m" => 'inputMatrix'} 778 | 779 | Commands = { 780 | "help" => ["see op's info", "op", -> tokens, ir_cb { 781 | raise ParseError.new("usage: help , see #$site for tutorial",tokens[0]) if tokens.size != 1 782 | relevant = ActualOpsList.filter{|o|[o.name, o.sym].include?(tokens[0].str)} 783 | if !relevant.empty? 784 | relevant.each(&:help) 785 | else 786 | puts "no such op: #{tokens[0].str}" 787 | end 788 | }], 789 | "version" => ["see atlas version", nil, -> tokens, ir_cb { 790 | raise ParseError.new("usage: version",tokens[0]) if !tokens.empty? 791 | puts $version 792 | }], 793 | "type" => ["see expression type", "a", -> tokens, ir_cb { 794 | p ir_cb.call.type_with_vec_level 795 | }], 796 | "p" => ["pretty print value", "a", -> tokens, ir_cb { 797 | ir = ir_cb.call 798 | run(ir) {|v,n,s| inspect_value(ir.type+ir.vec_level,v,ir.vec_level) } 799 | puts 800 | }], 801 | "print" => ["print value (implicit)", "a", -> tokens, ir_cb { 802 | ir = ir_cb.call 803 | run(ir) {|v,n,s| to_string(ir.type+ir.vec_level,v,true) } 804 | }], 805 | 806 | } 807 | -------------------------------------------------------------------------------- /parse.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | AST = Struct.new(:op,:args,:token,:is_flipped) 3 | 4 | def parse_line(tokens) 5 | get_expr(balance_parens(tokens),false,nil) 6 | end 7 | 8 | def get_expr(tokens,apply,implicit_value) 9 | top = tokens.pop 10 | if top == nil || top.str == "(" 11 | tokens << top if top != nil # not our job to consume ( 12 | rhs = implicit_value 13 | elsif op=Ops1[top.str] 14 | arg = get_expr(tokens,apply|apply_check(tokens),implicit_value) 15 | rhs = AST.new(op,[arg],top) 16 | elsif top.str == ")" 17 | if tokens.empty? || tokens[-1].str == "(" 18 | rhs = AST.new(EmptyOp,[],top) 19 | else 20 | v = new_paren_var 21 | rhs = AST.new(Ops2["set"], [get_expr(tokens,false,v),v], nil) 22 | end 23 | tokens.pop 24 | else 25 | rhs = make_op0(top) 26 | end 27 | return rhs if apply 28 | 29 | until tokens.empty? || tokens[-1].str == "(" 30 | flipped = flip_check(tokens) 31 | from = tokens[-1] 32 | op=Ops2[from.str] 33 | if op && op.name == "set" 34 | if rhs.op.name == "var" 35 | rhs.token.ensure_name 36 | else 37 | if tokens[-1].str == "@" 38 | op=nil # allow it to mean apply implicit 39 | else 40 | raise ParseError.new("must set id's", tokens[-1]) 41 | end 42 | end 43 | end 44 | tokens.pop if op 45 | lhs = get_expr(tokens,apply_check(tokens),implicit_value) 46 | rhs = AST.new(op||ImplicitOp,[lhs,rhs],from,flipped) 47 | end 48 | rhs 49 | end 50 | 51 | def apply_check(tokens) 52 | !tokens.empty? && tokens[-1].str == ApplyModifier && (tokens.pop; true) 53 | end 54 | 55 | def flip_check(tokens) 56 | !tokens.empty? && tokens[-1].str == FlipModifier && (tokens.pop; true) 57 | end 58 | 59 | $paren_vars = 0 60 | def new_paren_var 61 | AST.new(Var,[],Token.new("paren_var#{$paren_vars+=1}")) 62 | end 63 | 64 | def make_op0(t) 65 | str = t.str 66 | if str =~ /^#{NumRx}$/ 67 | AST.new(create_num(t),[],t) 68 | elsif str[0] == '"' 69 | AST.new(create_str(t),[],t) 70 | elsif str[0] == "'" 71 | AST.new(create_char(t),[],t) 72 | elsif (op=Ops0[str]) 73 | AST.new(op,[],t) 74 | else 75 | AST.new(Var,[],t) 76 | end 77 | end 78 | 79 | def balance_parens(tokens) 80 | depth = left = 0 81 | tokens.each{|t| 82 | if t.str == '(' 83 | depth += 1 84 | elsif t.str == ')' 85 | if depth == 0 86 | left += 1 87 | else 88 | depth -= 1 89 | end 90 | end 91 | } 92 | # +1 to also enclose in parens, so that top level implicit value never used 93 | [Token.new("(")]*(left+1) + tokens + [Token.new(")")]*(depth+1) 94 | end 95 | -------------------------------------------------------------------------------- /repl.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | # Set ruby file coding (with top comment) and IO to operate on bytes. 3 | # This is done so that everything is simply bytes in Atlas, but will display 4 | # properly even if using fancy unicode chars in another encoding (lengths/etc will be off though). 5 | # It is important for all encodings to match otherwise will get runtime errors when using 6 | # fancy characters. 7 | Encoding.default_external="iso-8859-1" 8 | Encoding.default_internal="iso-8859-1" 9 | 10 | Dir[__dir__+"/*.rb"].each{|f| require_relative f } 11 | 12 | def repl(input=nil) 13 | context={} 14 | 15 | { "N" => "'\n", 16 | "S" => "' ", 17 | }.each{|name,val| 18 | context[name]=to_ir(AST.new(create_char(Token.new(val)),[]),{},nil) 19 | } 20 | 21 | IO_Vars.each{|k,v| context[k] = to_ir(AST.new(Ops0[v],[]),{},nil) } 22 | 23 | # hold off on these, should it be vec or list? 24 | # context["W"]=to_ir(AST.new(Ops0['wholeNumbers'],[]),{},nil) 25 | # context["Z"]=to_ir(AST.new(Ops0['positiveIntegers'],[]),{},nil) 26 | 27 | line_no = 1 28 | last = nil 29 | 30 | if input 31 | input_fn = lambda { input.gets(nil) } 32 | elsif !ARGV.empty? 33 | $line_mode = true if $line_mode.nil? 34 | input_fn = lambda { 35 | return nil if ARGV.empty? # no more files 36 | filename = ARGV.shift 37 | raise AtlasError.new("no such file %p" % filename, nil) unless File.exists? filename 38 | File.read(filename) 39 | } 40 | else 41 | require "readline" 42 | $repl_mode = true 43 | hist_file = Dir.home + "/.atlas_history" 44 | if File.exist? hist_file 45 | Readline::HISTORY.push *File.read(hist_file).split("\n") 46 | end 47 | input_fn = lambda { 48 | line = Readline.readline("\e[33m \xE1\x90\xB3 \e[0m".force_encoding("utf-8"), true) 49 | File.open(hist_file,'a'){|f|f.puts line} unless !line || line.empty? 50 | line 51 | } 52 | Readline.completion_append_character = " " 53 | Readline.basic_word_break_characters = " \n\t1234567890~`!@\#$%^&*()_-+={[]}\\|:;'\",<.>/?" 54 | Readline.completion_proc = lambda{|s| 55 | var_names = context.keys.reject{|k|k['_']} # hide internal vars 56 | all = var_names + ActualOpsList.filter(&:name).map(&:name) + Commands.keys 57 | all.filter{|name|name.index(s)==0}.reject{|name|name['_']} 58 | } 59 | end 60 | 61 | loop { 62 | begin 63 | line=input_fn.call 64 | break if line==nil # eof 65 | 66 | token_lines,line_no=lex(line, line_no) 67 | 68 | token_lines.each{|tokens| # each line 69 | next if tokens.empty? 70 | 71 | exe = lambda{ 72 | if tokens.empty? 73 | raise ParseError.new("expecting an expression for command on line %d" % (line_no-1),nil) 74 | else 75 | last = tokens.empty? ? last : infer(to_ir(parse_line(tokens),context,last)) 76 | end 77 | } 78 | 79 | if (command=Commands[tokens[0].str]) 80 | tokens.shift 81 | command[2][tokens, exe] 82 | elsif !command && (command=Commands[tokens[-1].str]) 83 | tokens.pop 84 | command[2][tokens, exe] 85 | elsif tokens[0].str=="let" 86 | raise ParseError.new("let syntax is: let var = value", tokens[0]) unless tokens.size > 3 && tokens[2].str=="=" 87 | set(tokens[1].ensure_name, parse_line(tokens[3..-1]), context,last) 88 | else 89 | ir = exe.call 90 | puts "\e[38;5;243m#{ir.type_with_vec_level.inspect}\e[0m" if $repl_mode 91 | run(ir) {|v| 92 | to_string(ir.type+ir.vec_level,v,$line_mode) 93 | } 94 | end 95 | } 96 | rescue AtlasError => e 97 | STDERR.puts e.message 98 | # TODO there are some errors that could come from floats like 99 | # 0.0^(1.0-)+'a RangeError 100 | # converting inf to int FloatDomainError 101 | # "asdf"[(1-^(0.5)) NoMethodError (< on complex) 102 | # it would be best to catch them higher up for use with truthy 103 | rescue SignalException => e 104 | exit if !ARGV.empty? || input # not repl mode 105 | STDERR.print "CTRL-D to exit" if !line 106 | puts 107 | rescue SystemStackError => e 108 | STDERR.puts DynamicError.new("Stack Overflow Error\nYou could increase depth by changing shell variable (or by compiling to haskell todo):\nexport RUBY_THREAD_VM_STACK_SIZE=", nil).message 109 | rescue Errno::EPIPE 110 | exit 111 | rescue => e 112 | STDERR.puts "!!!This is an internal Altas error, please report the bug (via github issue or email name of this lang at golfscript.com)!!!\n\n" 113 | raise e 114 | end 115 | } # loop 116 | end 117 | -------------------------------------------------------------------------------- /spec.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | # general type vars 3 | A = :a 4 | B = :b 5 | 6 | # type var of base elem char 7 | Achar = :a_char 8 | 9 | # type var of base elem num 10 | Anum = :a_num 11 | 12 | # type spec ================================ 13 | # { from => to } or just "to" if no args 14 | # fyi you can't use [type] unless you mean list of type (doesn't mean 1) 15 | # to can be a list for multiple rets or a type 16 | # from can be a list for multiple args or a conditional type 17 | # conditional type can be a type or a type qualifier of a type 18 | # type can be an actual type or a list of a type (recursively) or a type var e.g. :a 19 | class FnType < Struct.new(:specs,:ret,:orig_key,:orig_val) 20 | def inspect 21 | specs.map(&:inspect)*" "+" -> "+parse_raw_arg_spec(ret).inspect 22 | end 23 | end 24 | 25 | class VecOf < Struct.new(:of) 26 | end 27 | def v(a) 28 | VecOf.new(a) 29 | end 30 | 31 | def create_specs(raw_spec) 32 | case raw_spec 33 | when Hash 34 | raw_spec.map{|raw_arg,ret| 35 | specs = (x=case raw_arg 36 | when Array 37 | if raw_arg.size == 1 38 | [raw_arg] 39 | else 40 | raw_arg 41 | end 42 | else 43 | [raw_arg] 44 | end).map{|a_raw_arg| parse_raw_arg_spec(a_raw_arg) } 45 | FnType.new(specs,ret,raw_arg,ret) 46 | } 47 | when Type, Array, VecOf, Symbol 48 | [FnType.new([],raw_spec,[],raw_spec)] 49 | when Proc # lazy type 50 | lambda{ create_specs(raw_spec.call) } 51 | else 52 | raise "unknown fn type format" 53 | end 54 | end 55 | 56 | def parse_raw_arg_spec(raw,list_nest_depth=0) 57 | case raw 58 | when Symbol 59 | VarTypeSpec.new(raw,list_nest_depth) 60 | when Array 61 | raise if raw.size != 1 62 | parse_raw_arg_spec(raw[0],list_nest_depth+1) 63 | when VecOf 64 | r=parse_raw_arg_spec(raw.of) 65 | r.vec_of=true 66 | r 67 | when Type 68 | ExactTypeSpec.new(Type.new(raw.dim+list_nest_depth, raw.base_elem)) 69 | else 70 | p raw 71 | error 72 | end 73 | end 74 | 75 | class ExactTypeSpec 76 | attr_reader :type 77 | attr_accessor :vec_of 78 | def initialize(rtype) 79 | @type = rtype 80 | end 81 | def check_base_elem(uses,t) 82 | t.can_base_be(@type) 83 | end 84 | def inspect 85 | (vec_of ? "<" : "")+type.inspect+(vec_of ? ">" : "") 86 | end 87 | end 88 | 89 | class VarTypeSpec 90 | attr_reader :var_name 91 | attr_reader :extra_dims 92 | attr_accessor :vec_of 93 | def initialize(var_sym, extra_dims) # e.g. [[a]] is .new(:a, 2) 94 | @var_name,@var_constraint = name_and_constraint(var_sym) 95 | @extra_dims = extra_dims 96 | @vec_of = false 97 | end 98 | def check_base_elem(uses,type) 99 | if @var_constraint 100 | @var_constraint == type.base_elem.to_s 101 | else 102 | type.base_elem == Unknown.base_elem || (uses[var_name]||=type.base_elem) == type.base_elem 103 | end 104 | end 105 | def inspect 106 | constraint = @var_constraint ? " (#{@var_constraint})" : "" 107 | (vec_of ? "<" : "")+"["*extra_dims+var_name.to_s+constraint+"]"*extra_dims+(vec_of ? ">" : "") 108 | end 109 | end 110 | 111 | def name_and_constraint(var_sym) 112 | s = var_sym.to_s.split("_") 113 | [s[0].to_sym, s[1]] 114 | end 115 | 116 | def spec_to_type(spec, vars) 117 | case spec 118 | when Type 119 | TypeWithVecLevel.new(spec,0) 120 | when Array 121 | raise "cannot return multiple values for now" if spec.size != 1 122 | TypeWithVecLevel.new(spec_to_type(spec[0], vars).type + 1, 0) 123 | when Symbol 124 | name,constraint=name_and_constraint(spec) 125 | t=TypeWithVecLevel.new(vars[name],0) 126 | t.type.base_elem = constraint.to_sym if constraint 127 | t 128 | when VecOf 129 | t=spec_to_type(spec.of, vars) 130 | TypeWithVecLevel.new(t.type, t.vec_level+1) 131 | else 132 | unknown 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /test/all: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ruby test/test_parse.rb 3 | ruby test/test_vec.rb 4 | ruby test/test_behavior.rb 5 | ruby test/gen_crcl_example.rb 6 | ruby test/test_examples.rb 7 | ruby test/test_op_examples.rb 8 | ruby test/test_docs.rb 9 | ruby test/test_increasing.rb 10 | sh test/test_repl.sh 11 | ruby test/update_date.rb 12 | ruby web/quickref.rb > web/site/quickref.html 13 | ruby web/op_notes.rb 14 | ruby web/generate_site.rb 15 | -------------------------------------------------------------------------------- /test/behavior_tests.atl: -------------------------------------------------------------------------------- 1 | -- test ints 2 | 4 -> 4 3 | 45 -> 45 4 | 5 | -- test string and escapes 6 | "" -> "" 7 | "hi" -> "hi" 8 | "--" -> "--" 9 | "\n\\".; -> <"\n","\\"> 10 | "\x02\x20\xaa".; -> <"\x02"," ","\xaa"> 11 | 12 | -- test utf8 encoding chars as bytes 13 | "├───╯".; -> <"\xe2","\x94","\x9c","\xe2","\x94","\x80","\xe2","\x94","\x80","\xe2","\x94","\x80","\xe2","\x95","\xaf"> 14 | 15 | -- test char and escapes 16 | 'a -> 'a 17 | '\n -> '\n 18 | '\0 -> '\0 19 | '\ -> '\\ 20 | '" -> '" 21 | '' -> '' 22 | '-> LexError 23 | '\xff-'\0 -> 255 24 | '\xff -> '\xff 25 | '\0+5 -> '\x05 26 | -- test '- minus doesn't get commented out 27 | '--2 -> '+ 28 | 29 | -- Test implicit op 30 | 1+1 3 -> [2,3] 31 | (1+1)3 -> [2,3] 32 | 1,2"b" -> ["1","2","b"] 33 | "abc"+0"123" -> <"a123","b123","c123"> 34 | ("a","c")"d" -> ["a","c","d"] 35 | 36 | -- Test replication in auto vectorization 37 | "abc","def","xyz"[(1,2,3) -> <["abc"],["abc","def"],["abc","def","xyz"]> 38 | 39 | -- Test auto promotion 40 | 5\ -> [[5]] 41 | 5. -> <5> 42 | '5 read -> [5] 43 | 1'a -> "1a" 44 | 45 | --/ Test no promotion 46 | 5[ -> AtlasTypeError 47 | 48 | -- Test promotion prefered when last op was vec 49 | "123".` -> <[1],[2],[3]> 50 | 51 | -- not escaped since not needed 52 | '\_" --" -> "\\ --" 53 | '\_'f -> "\\f" 54 | 55 | 1+() -> AtlasTypeError 56 | ()%2 -> AtlasTypeError 57 | 0-12 -> -12 58 | 0/12 -> 0 59 | 012 -> 12 60 | () -> [] 61 | (),() -> [[],[]] 62 | (),(),() -> [[],[],[]] 63 | ();,()_() -> [[],[]] 64 | 65 | ()[ -> DynamicError 66 | ()] -> DynamicError 67 | 5;> -> [] 68 | 69 | ----------/ test vars 70 | 5@v1+v1 -> 10 71 | 72 | -- test nil 73 | ();,(),() -> [[],[],[]] 74 | 75 | -------- test infinite list 76 | v1`1@v1 -> [1,1,1,1,1,1,... 77 | v1`'-@v1 -> "-------------... 78 | 79 | -------------- test zips 80 | 3;,4+1 -> <4,5> 81 | 3;,4;,(5;,7)+1 -> <<4,5>,<6,8>> 82 | 1+"asdf"% -> "bteg" 83 | "asdf"+1 -> <'b,'t,'e,'g> 84 | (1;,2)+(4;,6,8) -> <5,8> 85 | (4;,6,8)+(1;,2) -> <5,8> 86 | 87 | "asdf"-1% -> "`rce" 88 | "abcd"-"aaa"% -> [0,1,2] 89 | 90 | "abcd","xzy" [ -> "abcd" 91 | "abcd","xzy".[% -> "ax" 92 | "abcd","xzy".]% -> "dy" 93 | "abcd","xzy"..[ -> AtlasTypeError 94 | 95 | "abcd";,"xzy" > -> ["xzy"] 96 | "abcd";,"xzy".tail -> <"bcd","zy"> 97 | 'c tail -> AtlasTypeError 98 | 99 | "def";,"xzy".`"abc" -> <"adef","bxzy"> 100 | "def","xzy"..;`"abc"@, -> <<"ad","be","cf">,<"ax","bz","cy">> 101 | 102 | -- coercion tests 103 | 'a | "asdf" -> <'a,'a,'a,'a> 104 | ' | "asdf" % -> "asdf" 105 | "asdf" | 'a% -> "asdf" 106 | "" | ('a;) -> "a" 107 | 0|"b" -> "b" 108 | ""|2 -> "2" 109 | 0|'c -> "c" 110 | 4,3,0|"f" -> <"4","3","f"> 111 | 0,1|("f","t") -> ["0","1"] 112 | ("f","t")|(0,1) -> ["f","t"] 113 | ()|1 -> <> 114 | ()|(1;) -> [1] 115 | ()|"a" -> "a" 116 | 117 | 0 & 2 | 3 -> 3 118 | 1 & 2 | 3 -> 2 119 | () & 2 | 3 -> 3 120 | 0; & 2 | 3 -> 2 121 | " "[ & 2 | 3 -> 3 122 | "a"[ & 2 | 3 -> 2 123 | 124 | 0 & 'a; -> " " 125 | () & 1 -> 0 126 | "" & "asdf" -> "" 127 | 128 | 1 & 'a | "b" -> <'a> 129 | 1 & 'a,@. | "bcd"% -> "aaa" 130 | "a " . & '1 | "23" % -> "13" 131 | 132 | "a b " . & ("fghi".) | ("jklm".) % -> "fkhm" 133 | "a b " . & 1 | 0 % -> [1,0,1,0] 134 | 135 | "asdf"[(1,2) -> <"a","as"> 136 | "abc","123".[2 -> <"ab","12"> 137 | 138 | ---------- more advanced circular programming 139 | 1+v1`1@v1 -> [1,2,3,4,5... 140 | v1+v2`1@v2`1@v1 -> [1,1,2,3,5,8,13,21... 141 | v1`0+(1+v2%`1@v2)%@v1 -> [1,3,6,10,15... 142 | 1+v1@v1 -> AtlasTypeError 143 | 144 | ---- test more ops and zips 145 | "hi".,[5 -> <"hhhhh","iiiii"> 146 | 147 | "hi".; -> <"h","i"> 148 | "asdfg"]2 -> "dfg" 149 | "abc","123".]2 -> <"c","3"> 150 | 151 | "hi","there",("asdf","123")._ -> <"hithere","asdf123"> 152 | 1;,_[5 -> [1,1,1,1,1] 153 | 154 | "abc"_("123",_) -> "abc123123... 155 | "abc",__"123" -> "abcabcabc... 156 | "123".;`"abc" -> <"a1","b2","c3"> 157 | "a","b"._("1","2") -> <"a1","b2"> 158 | 'a "b" -> "ab" 159 | 160 | "asdf"< -> "asd" 161 | "abc","123".< -> <"ab","12"> 162 | 163 | "abc","123"\ -> ["a1","b2","c3"] 164 | "abc","12","xyz"\ -> ["a1x","b2y","c"] 165 | "abc","123",("xyz","789").\ -> <["a1","b2","c3"],["x7","y8","z9"]> 166 | 167 | "abcd";\ -> ["a","b","c","d"] 168 | 4\ -> [[4]] 169 | "abc","123".;\ -> <["a","b","c"],["1","2","3"]> 170 | 171 | -- circular programming foldr 172 | 4,5,6+(v1>,0)@v1[ -> 15 173 | 174 | -- error and catching 175 | (v1<)`0@v1 -> InfiniteLoopError 176 | ""[ -> DynamicError 177 | --catch /9 :1:2:0:3;4 -> f 178 | 179 | "a b c".&(1,2,3,4,5,6.;)_ -> [1,3,5] 180 | 181 | ""` -> [] 182 | " "` -> [] 183 | "-a"` -> [] 184 | 185 | '5` -> [5] 186 | '5.` -> <[5]> 187 | 188 | "1 2","3 4"` -> <[1,2],[3,4]> 189 | 190 | -- complicated test (primes) 191 | (1+(v2*v1`1@v1))%(1+v2`2@v2)[20.& (();)|(v2.;;)__ -> [2,3,5,7,11... 192 | 193 | v1+(1,2,3,(4,5,6))`(0,)@v1] -> <6,15> 194 | 195 | (); -> [[]] 196 | ();[ -> [] 197 | 198 | -- check incorrect faith attempt 199 | -- this would attempt to access invalid elements if said check was not in place 200 | 0;;._(v1+(3;;)%%@v2.)%&(4;;[0;)|(5;;[0;)[@v1 `__ _(v2 `__) -> DynamicError 201 | 202 | -- tails' faith example that needed padding before 203 | a`0+1@a=10#&a|(b%>+1)@b[ -> 19 204 | 205 | -- Test auto replicating of nil 206 | "______MA_"='_ & ("CHISTMAS".;) | () _ -> "CHISTM" 207 | 208 | 209 | -- Test promotion 210 | "123".;` -> <[1],[2],[3]> 211 | "asdf"\ -> ["a","s","d","f"] 212 | 213 | 5. -> <5> 214 | 5% -> AtlasTypeError 215 | 216 | -- Test parse uses previous token correct 217 | 1 (2-) -> [1,-2] 218 | 219 | -- Test unary op not used as binary op 220 | 2 . 4 -> <[2,4]> 221 | 222 | -- Test @ can be applied to { (it is unmodifiable for flip) 223 | 1+2@{,a -> [3,2] 224 | 225 | -- This tests a pathological case in var lookups 226 | a@F _ F@a -> InfiniteLoopError 227 | 228 | -- Using the unknown type 229 | a@a -> AtlasTypeError 230 | a@a;# -> 1 231 | 232 | 233 | -- infinite type 234 | a;@a -> AtlasTypeError 235 | 236 | -- test unbalanced ) for use with circular programming/nil 237 | ) -> [] 238 | 239 | -- test apply 240 | 2*3@+4 -> 14 241 | 2*3-@+4 -> 2 242 | 243 | -- 1+2@3 -> <3,4> -- removed for now todo, @ will be a binary op again? 244 | 1+5@@a+a -> 11 245 | 246 | 1@ -> ParseError 247 | 1@@ -> ParseError 248 | 249 | -- test flip 250 | 1\2 -> [2,1] 251 | "a"\"b" -> ["b","a"] 252 | 1`\(2,3) -> [1,2,3] 253 | 1-\-2 -> <<-3>> 254 | 1,2\\ -> [[1,2]] 255 | 256 | 1+2@/\10 -> 6 257 | (\1+1%) -> [2,3,4,5,6,7... 258 | 1-\2@+3 -> 4 259 | 260 | -- this would not be useful 261 | -- (a@\2)+a -> 4 262 | 263 | -- this would be type A which should not be possible to construct 264 | ""&a@a -> DynamicError 265 | 266 | (1@"") -> ["1",""] 267 | 268 | -- test laziness of chunk while using collatz conjecture problem 269 | a%2&(3*a+1)|(a/2)`8@a=.~%?a[ -> [8,4,2,1] 270 | 271 | 5@λ*λ -> 25 272 | 273 | 1,2+0# -> 2 274 | -- test will not unvectorize after vec op 275 | 1,2.# -> AtlasTypeError 276 | 277 | -- test roman numerals 278 | M -> 1000 279 | D -> 500 280 | C -> 100 281 | L -> 50 282 | X -> 10 283 | V -> 5 284 | I -> 1 285 | XI -> 11 286 | IX -> 9 287 | 288 | -- test parse floats 289 | 1.01 -> 1.01 290 | 0.90 -> 0.9 291 | 4e2 -> 400.0 292 | 4e-2 -> 0.04 293 | 4.1e2 -> 410.0 294 | .3 -> 0.3 295 | .3e2 -> 30.0 296 | 297 | -- test catch in faith based circular program works 298 | a%,0>+(1,2,(1/0),3,4.catch,55[){ -> <65,64,62,7,4> 299 | (1,2,3,4),(5,(1/0),7,8)..catch,77[+b`0@b -> <[0,1,3,6,10],[0,5,82,89,97]> 300 | 301 | -- test auto unvec 302 | "1","2","". ~ "abc" -> "ab" 303 | 304 | 1^ -> <0,1> 305 | 306 | 307 | -- test build 308 | 1,2-> [1,2] 309 | 1,2,(3,4) -> [[1,2],[3,4]] 310 | 1,(2,3) -> [1,2,3] 311 | 1,2,(3,4),0 -> <[1,2,0],[3,4,0]> 312 | 0,(1,2,(3,4)) -> <[0,1,2],[0,3,4]> 313 | 314 | "asdf",1 -> ["asdf","1"] 315 | "asdf","z",1 -> ["asdf","z","1"] 316 | 'a,1 -> "a1" 317 | "a","b",("x","y"),1 -> <["a","b","1"],["x","y","1"]> 318 | "a","b",("x","y"),(1,2) -> [["a","b"],["x","y"],["1","2"]] 319 | 1,2,'a -> <"1a","2a"> 320 | 1,2,"ab" -> ["1","2","ab"] 321 | 1,2,(3,4),'a -> <<"1a","2a">,<"3a","4a">> 322 | 323 | a@b,b -> "aa" 324 | 325 | 'a-1000 -> '\{-903} -------------------------------------------------------------------------------- /test/check_example.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | require "./repl.rb" 3 | 4 | def check_example(test) 5 | i,o=test.split("->") 6 | o.strip! 7 | 8 | expected = o 9 | expected,limit = if expected =~ /\.\.\.$/ 10 | [$`,$`.size] 11 | else 12 | [expected,10000] 13 | end 14 | 15 | begin 16 | found,found_type = doit(i, limit) 17 | rescue Exception 18 | found = $! 19 | end 20 | 21 | if o=~/Error/ ? found.class.to_s!=o : found != expected 22 | STDERR.puts "FAIL: "+yield 23 | STDERR.puts i 24 | STDERR.puts "expected:" 25 | STDERR.puts expected 26 | raise found if Exception === found 27 | STDERR.puts "found:" 28 | STDERR.puts found 29 | exit(1) 30 | end 31 | end 32 | 33 | def doit(source,limit) 34 | $warn_on_unset_vars = false 35 | tokens,lines = lex(source) 36 | root = parse_line(tokens[0]) 37 | ir = to_ir(root,{},[]) 38 | infer(ir) 39 | v=make_promises(ir) 40 | v=inspect_value(ir.type+ir.vec_level,v,ir.vec_level) 41 | v=take(limit,v.const) 42 | 43 | [to_eager_str(v.const),ir.type.inspect] 44 | end -------------------------------------------------------------------------------- /test/examples/ans.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 1 3 | ans+ans 4 | ans+ans 5 | ans+ans 6 | ans+ans 7 | [stdout] 8 | 1 9 | 2 10 | 4 11 | 8 12 | 16 13 | -------------------------------------------------------------------------------- /test/examples/circular_start.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- this test that the unbalanced ) causes a circular program rather than the leading , affecting last result 3 | %,0>+"1,2,3,4"@`) 4 | [stdout] 5 | 10 6 | 9 7 | 7 8 | 4 -------------------------------------------------------------------------------- /test/examples/circular_vars2.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | let a=b+(5@b) 3 | b 4 | a 5 | let d=d 6 | let c=c 7 | c 8 | [stdout] 9 | 5 10 | 10 11 | [stderr] 12 | 5:7 (c) cannot use value of the unknown type (TypeError) 13 | -------------------------------------------------------------------------------- /test/examples/commands.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | ();,(),(). type 3 | 1,2.p 4 | [output] 5 | <[a]> 6 | <1,2> -------------------------------------------------------------------------------- /test/examples/comment.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- ignored 3 | 1 -- ignored 4 | 2--ignored 5 | 3 6 | --ignored 7 | [stdout] 8 | 1 9 | 2 10 | 3 -------------------------------------------------------------------------------- /test/examples/duplicate_assignment.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | let a=5 3 | let b=a+1 4 | b 5 | let a=6 6 | b 7 | 8 | [stdout] 9 | 6 10 | 7 11 | [stderr] 12 | -------------------------------------------------------------------------------- /test/examples/empty_char_error.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | r// 3 | ' 4 | [stderr] 5 | 2:3 (') empty char -------------------------------------------------------------------------------- /test/examples/errors_are_cached_too.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- this would take minutes if errors were recomputed unlike values 3 | 1/0_(1,%)[1000+a`0@a],%[1000,%[100..catch,5[++ 4 | [output] 5 | 500000 6 | -------------------------------------------------------------------------------- /test/examples/help.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | help mod 3 | help - 4 | [stdout] 5 | mod % 6 | anything mod 0 is 0 7 | Num Num → Num 8 | 7%3 → 1 9 | 10%5 → 0 10 | 9%5 → 4 11 | 11%(5-) → -4 12 | 10%(5-) → 0 13 | 11-%5 → 4 14 | 10-%5 → 0 15 | 10-%(5-) → 0 16 | 9-%(5-) → -4 17 | 5%0 → 0 18 | 19 | sub - 20 | Num Num → Num 21 | Char Num → Char 22 | Num Char → Char 23 | Char Char → Num 24 | 5-3 → 2 25 | 'b-'a → 1 26 | 1-'b → 'a 27 | 28 | neg - 29 | Num → Num 30 | 2- → -2 31 | -------------------------------------------------------------------------------- /test/examples/input_lines.test: -------------------------------------------------------------------------------- 1 | [input] 2 | 1 2 3 3 | 4 5 6 4 | 5 | [prog] 6 | l`\ 7 | [stdout] 8 | 1 4 9 | 2 5 10 | 3 6 11 | -------------------------------------------------------------------------------- /test/examples/int_columns.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | v+1 3 | [input] 4 | 1 10 5 | 4 20 6 | [stdout] 7 | 2 8 | 11 9 | 5 10 | 21 11 | -------------------------------------------------------------------------------- /test/examples/invalid_let.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | let 5=4 3 | [stderr] 4 | 1:5 (5) cannot set "5" (not an id) (ParseError) 5 | -------------------------------------------------------------------------------- /test/examples/invalid_set.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 3@4 3 | 7 set 6 4 | [stdout] 5 | 3 6 | 4 7 | [stderr] 8 | 2:3 (set) must set id's (ParseError) -------------------------------------------------------------------------------- /test/examples/large_prog.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 3 | 1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1 4 | [stdout] 5 | 1000 6 | 1000 -------------------------------------------------------------------------------- /test/examples/multiline_commands_and_strs.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | "ab 3 | cd"p 4 | 1/0 5 | 6 | 7 | 5 8 | [stdout] 9 | "ab\ncd" 10 | [stderr] 11 | 3:2 (/) div 0 (DynamicError) -------------------------------------------------------------------------------- /test/examples/multiple_type_errors.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | (1+()) + (2+()) 3 | [stderr] 4 | 1:3 (+) op is not defined for arg types: Num,[a] (TypeError) 5 | 1:12 (+) op is not defined for arg types: Num,[a] (TypeError) -------------------------------------------------------------------------------- /test/examples/nested_invalid_ref.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | let a=b 3 | a 4 | [output] 5 | b 6 | 7 | -------------------------------------------------------------------------------- /test/examples/newline_char.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | ' 3 | p 4 | [stdout] 5 | '\n -------------------------------------------------------------------------------- /test/examples/non_binary_error.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 1 add 3 | let p=5 4 | p 5 | [stdout] 6 | 1 7 | add 8 | 5 9 | [stderr] 10 | 1:3 (add) using "add" as identifier string even though it is an op (Warning) 11 | 2:5 (p) overwriting "p" even though it is an op (Warning) 12 | -------------------------------------------------------------------------------- /test/examples/read_binchar.test: -------------------------------------------------------------------------------- 1 | [input] 2 | λ 3 | [prog] 4 | l 5 | l& 6 | [output] 7 | λ 8 | 206 187 9 | -------------------------------------------------------------------------------- /test/examples/saves.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 5{+6{ 3 | a,b; 4 | 5{+7@c 5 | c 6 | 7 | [stdout] 8 | 11 9 | 5 11 10 | 12 11 | 12 12 | -------------------------------------------------------------------------------- /test/examples/shinh_313eq515.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?313+equals+151 3 | l/' {.=%=]~a@% 4 | [input] 5 | MLACTAOW4 T9F7ZFPNU 6 | NYXKOOEAM HQ0P77AFM 7 | 5V1BU1RXM 6LY7ANX57 8 | [stdout] 9 | MLACTAOW4 T9F7ZFPNU 10 | NYXKOOEAM HQ0P77AFM 11 | -------------------------------------------------------------------------------- /test/examples/shinh_6n.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?6N+plus+minus+one 3 | v,"= 6 *",(v+1{/6),(a%6-'-;),1 4 | [input] 5 | 179 6 | 271 7 | 263 8 | [stdout] 9 | 179 = 6 * 30 - 1 10 | 271 = 6 * 45 + 1 11 | 263 = 6 * 44 - 1 12 | -------------------------------------------------------------------------------- /test/examples/shinh_abbccc.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?abbccc 3 | l.^l@-'`; 4 | [input] 5 | thequickbrownfoxjumpsoverthelazydog 6 | [stdout] 7 | tttttttttttttttttttt 8 | hhhhhhhh 9 | eeeee 10 | qqqqqqqqqqqqqqqqq 11 | uuuuuuuuuuuuuuuuuuuuu 12 | iiiiiiiii 13 | ccc 14 | kkkkkkkkkkk 15 | bb 16 | rrrrrrrrrrrrrrrrrr 17 | ooooooooooooooo 18 | wwwwwwwwwwwwwwwwwwwwwww 19 | nnnnnnnnnnnnnn 20 | ffffff 21 | ooooooooooooooo 22 | xxxxxxxxxxxxxxxxxxxxxxxx 23 | jjjjjjjjjj 24 | uuuuuuuuuuuuuuuuuuuuu 25 | mmmmmmmmmmmmm 26 | pppppppppppppppp 27 | sssssssssssssssssss 28 | ooooooooooooooo 29 | vvvvvvvvvvvvvvvvvvvvvv 30 | eeeee 31 | rrrrrrrrrrrrrrrrrr 32 | tttttttttttttttttttt 33 | hhhhhhhh 34 | eeeee 35 | llllllllllll 36 | a 37 | zzzzzzzzzzzzzzzzzzzzzzzzzz 38 | yyyyyyyyyyyyyyyyyyyyyyyyy 39 | dddd 40 | ooooooooooooooo 41 | ggggggg 42 | -------------------------------------------------------------------------------- /test/examples/shinh_abbcccdddd.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?abbcccdddd 3 | ^](r.{^a@-'`{#)_\b 4 | [input] 5 | thequickbrownfoxjumpsoverthelazydog 6 | [output] 7 | tttttttttttttttttttt 8 | hhhhhhhhtttttttttttt 9 | eeeeehhhtttttttttttt 10 | qqqqqqqqqqqqqqqqqttt 11 | uuuuuuuuuuuuuuuuuuuuu 12 | iiiiiiiiiuuuuuuuuuuuu 13 | ccciiiiiiuuuuuuuuuuuu 14 | kkkkkkkkkkkuuuuuuuuuu 15 | bbkkkkkkkkkuuuuuuuuuu 16 | rrrrrrrrrrrrrrrrrruuu 17 | ooooooooooooooorrruuu 18 | wwwwwwwwwwwwwwwwwwwwwww 19 | nnnnnnnnnnnnnnwwwwwwwww 20 | ffffffnnnnnnnnwwwwwwwww 21 | ooooooooooooooowwwwwwww 22 | xxxxxxxxxxxxxxxxxxxxxxxx 23 | jjjjjjjjjjxxxxxxxxxxxxxx 24 | uuuuuuuuuuuuuuuuuuuuuxxx 25 | mmmmmmmmmmmmmuuuuuuuuxxx 26 | ppppppppppppppppuuuuuxxx 27 | sssssssssssssssssssuuxxx 28 | ooooooooooooooossssuuxxx 29 | vvvvvvvvvvvvvvvvvvvvvvxx 30 | eeeeevvvvvvvvvvvvvvvvvxx 31 | rrrrrrrrrrrrrrrrrrvvvvxx 32 | ttttttttttttttttttttvvxx 33 | hhhhhhhhttttttttttttvvxx 34 | eeeeehhhttttttttttttvvxx 35 | llllllllllllttttttttvvxx 36 | alllllllllllttttttttvvxx 37 | zzzzzzzzzzzzzzzzzzzzzzzzzz 38 | yyyyyyyyyyyyyyyyyyyyyyyyyz 39 | ddddyyyyyyyyyyyyyyyyyyyyyz 40 | oooooooooooooooyyyyyyyyyyz 41 | gggggggooooooooyyyyyyyyyyz 42 | 43 | 44 | -------------------------------------------------------------------------------- /test/examples/shinh_accumulate_values_in_reverse.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Accumulate+Value+in+Reverse 3 | >,0+v)/ 4 | [input] 5 | 1 6 | 2 7 | 3 8 | 4 9 | 5 10 | [output] 11 | 5 12 | 9 13 | 12 14 | 14 15 | 15 16 | -------------------------------------------------------------------------------- /test/examples/shinh_baby_shark.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Baby+Shark+Song 3 | "Babyz/Mommyz/Daddyz/Grandmaz/Grandpaz/Let's go hunt/Run away/Safe at last/It's the end"/z*" shark"/'/.,%[4*(',_" doo"@^6_' 4 | )'! 5 | [output] 6 | Baby shark, doo doo doo doo doo doo 7 | Baby shark, doo doo doo doo doo doo 8 | Baby shark, doo doo doo doo doo doo 9 | Baby shark! 10 | Mommy shark, doo doo doo doo doo doo 11 | Mommy shark, doo doo doo doo doo doo 12 | Mommy shark, doo doo doo doo doo doo 13 | Mommy shark! 14 | Daddy shark, doo doo doo doo doo doo 15 | Daddy shark, doo doo doo doo doo doo 16 | Daddy shark, doo doo doo doo doo doo 17 | Daddy shark! 18 | Grandma shark, doo doo doo doo doo doo 19 | Grandma shark, doo doo doo doo doo doo 20 | Grandma shark, doo doo doo doo doo doo 21 | Grandma shark! 22 | Grandpa shark, doo doo doo doo doo doo 23 | Grandpa shark, doo doo doo doo doo doo 24 | Grandpa shark, doo doo doo doo doo doo 25 | Grandpa shark! 26 | Let's go hunt, doo doo doo doo doo doo 27 | Let's go hunt, doo doo doo doo doo doo 28 | Let's go hunt, doo doo doo doo doo doo 29 | Let's go hunt! 30 | Run away, doo doo doo doo doo doo 31 | Run away, doo doo doo doo doo doo 32 | Run away, doo doo doo doo doo doo 33 | Run away! 34 | Safe at last, doo doo doo doo doo doo 35 | Safe at last, doo doo doo doo doo doo 36 | Safe at last, doo doo doo doo doo doo 37 | Safe at last! 38 | It's the end, doo doo doo doo doo doo 39 | It's the end, doo doo doo doo doo doo 40 | It's the end, doo doo doo doo doo doo 41 | It's the end! 42 | -------------------------------------------------------------------------------- /test/examples/shinh_base2to36.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | /(2:37,@x)`v@,)?[%x/{>9+W|a_; 3 | [input] 4 | 37 5 | [output] 6 | 100101 7 | 1101 8 | 211 9 | 122 10 | 101 11 | 52 12 | 45 13 | 41 14 | 37 15 | 34 16 | 31 17 | 2b 18 | 29 19 | 27 20 | 25 21 | 23 22 | 21 23 | 1i 24 | 1h 25 | 1g 26 | 1f 27 | 1e 28 | 1d 29 | 1c 30 | 1b 31 | 1a 32 | 19 33 | 18 34 | 17 35 | 16 36 | 15 37 | 14 38 | 13 39 | 12 40 | 11 -------------------------------------------------------------------------------- /test/examples/shinh_collatz.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Collatz+Problem 3 | a%2&(3*a+1)|a@/2`v@a-1?a[\ 4 | 1 5 | [stdin] 6 | 3 7 | [stdout] 8 | 3 9 | 10 10 | 5 11 | 16 12 | 8 13 | 4 14 | 2 15 | 1 16 | -------------------------------------------------------------------------------- /test/examples/shinh_colosseum_sequence.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Colosseum+Sequence 3 | bcdedcdefd-b{%@x+a@,]\x[]\x[_> 4 | 1 5 | [stdout] 6 | 1 7 | 2 8 | 3 9 | 2 10 | 1 11 | 2 12 | 3 13 | 2 14 | 2 15 | 1 16 | 2 17 | 3 18 | 2 19 | 3 20 | 2 21 | 3 22 | 2 23 | 1 24 | 3 25 | 2 26 | 3 27 | 2 28 | 1 29 | 2 30 | 3 31 | 2 32 | 1 33 | 2 34 | 2 35 | 3 36 | 2 37 | 1 38 | 2 39 | 1 40 | 2 41 | 1 42 | 2 43 | 3 44 | 1 45 | 2 46 | 3 47 | 2 48 | 1 49 | 2 50 | 3 51 | 2 52 | 1 53 | 2 54 | 2 55 | 1 56 | 2 57 | 3 58 | 2 59 | 3 60 | 2 61 | 3 62 | 2 63 | 1 64 | 3 65 | 2 66 | 3 67 | 2 68 | 1 69 | 2 70 | 3 71 | 2 72 | 1 73 | 2 74 | 2 75 | 3 76 | 2 77 | 1 78 | 2 79 | 1 80 | 2 81 | 1 82 | 2 83 | 3 84 | 1 85 | 2 86 | 1 87 | 2 88 | 3 89 | 2 90 | 1 91 | 2 92 | 3 93 | 2 94 | 2 95 | 2 96 | 3 97 | 2 98 | 1 99 | 2 100 | 3 101 | 2 102 | 1 103 | 2 104 | 2 105 | 1 106 | -------------------------------------------------------------------------------- /test/examples/shinh_count_in_base_2.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Count+in+Base+2 3 | /2`1@:101[9)%2~a@:/ 4 | [stdout] 5 | a 6 | b 7 | ba 8 | c 9 | ca 10 | cb 11 | cba 12 | d 13 | da 14 | db 15 | dba 16 | dc 17 | dca 18 | dcb 19 | dcba 20 | e 21 | ea 22 | eb 23 | eba 24 | ec 25 | eca 26 | ecb 27 | ecba 28 | ed 29 | eda 30 | edb 31 | edba 32 | edc 33 | edca 34 | edcb 35 | edcba 36 | f 37 | fa 38 | fb 39 | fba 40 | fc 41 | fca 42 | fcb 43 | fcba 44 | fd 45 | fda 46 | fdb 47 | fdba 48 | fdc 49 | fdca 50 | fdcb 51 | fdcba 52 | fe 53 | fea 54 | feb 55 | feba 56 | fec 57 | feca 58 | fecb 59 | fecba 60 | fed 61 | feda 62 | fedb 63 | fedba 64 | fedc 65 | fedca 66 | fedcb 67 | fedcba 68 | g 69 | ga 70 | gb 71 | gba 72 | gc 73 | gca 74 | gcb 75 | gcba 76 | gd 77 | gda 78 | gdb 79 | gdba 80 | gdc 81 | gdca 82 | gdcb 83 | gdcba 84 | ge 85 | gea 86 | geb 87 | geba 88 | gec 89 | geca 90 | gecb 91 | gecba 92 | ged 93 | geda 94 | gedb 95 | gedba 96 | gedc 97 | gedca 98 | gedcb 99 | gedcba 100 | gf 101 | gfa 102 | gfb 103 | gfba 104 | gfc 105 | -------------------------------------------------------------------------------- /test/examples/shinh_fizzbuzz.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?FizzBuzz 3 | Fizz,Buzz^(1:101{,%3@,5~)_|a 4 | [stdout] 5 | 1 6 | 2 7 | Fizz 8 | 4 9 | Buzz 10 | Fizz 11 | 7 12 | 8 13 | Fizz 14 | Buzz 15 | 11 16 | Fizz 17 | 13 18 | 14 19 | FizzBuzz 20 | 16 21 | 17 22 | Fizz 23 | 19 24 | Buzz 25 | Fizz 26 | 22 27 | 23 28 | Fizz 29 | Buzz 30 | 26 31 | Fizz 32 | 28 33 | 29 34 | FizzBuzz 35 | 31 36 | 32 37 | Fizz 38 | 34 39 | Buzz 40 | Fizz 41 | 37 42 | 38 43 | Fizz 44 | Buzz 45 | 41 46 | Fizz 47 | 43 48 | 44 49 | FizzBuzz 50 | 46 51 | 47 52 | Fizz 53 | 49 54 | Buzz 55 | Fizz 56 | 52 57 | 53 58 | Fizz 59 | Buzz 60 | 56 61 | Fizz 62 | 58 63 | 59 64 | FizzBuzz 65 | 61 66 | 62 67 | Fizz 68 | 64 69 | Buzz 70 | Fizz 71 | 67 72 | 68 73 | Fizz 74 | Buzz 75 | 71 76 | Fizz 77 | 73 78 | 74 79 | FizzBuzz 80 | 76 81 | 77 82 | Fizz 83 | 79 84 | Buzz 85 | Fizz 86 | 82 87 | 83 88 | Fizz 89 | Buzz 90 | 86 91 | Fizz 92 | 88 93 | 89 94 | FizzBuzz 95 | 91 96 | 92 97 | Fizz 98 | 94 99 | Buzz 100 | Fizz 101 | 97 102 | 98 103 | Fizz 104 | Buzz 105 | -------------------------------------------------------------------------------- /test/examples/shinh_hello_golfers.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?hello+golfers 3 | l%,world[_\"Hello, "'! 4 | -- [stdin] 5 | -- golfers 6 | [stdout] 7 | Hello, world! -------------------------------------------------------------------------------- /test/examples/shinh_length_of_fizz_buzz.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Length+of+FizzBuzz 3 | 1:101@i%3~+(i%5~)*4|i`@# 4 | [stdout] 5 | 1 6 | 1 7 | 4 8 | 1 9 | 4 10 | 4 11 | 1 12 | 1 13 | 4 14 | 4 15 | 2 16 | 4 17 | 2 18 | 2 19 | 8 20 | 2 21 | 2 22 | 4 23 | 2 24 | 4 25 | 4 26 | 2 27 | 2 28 | 4 29 | 4 30 | 2 31 | 4 32 | 2 33 | 2 34 | 8 35 | 2 36 | 2 37 | 4 38 | 2 39 | 4 40 | 4 41 | 2 42 | 2 43 | 4 44 | 4 45 | 2 46 | 4 47 | 2 48 | 2 49 | 8 50 | 2 51 | 2 52 | 4 53 | 2 54 | 4 55 | 4 56 | 2 57 | 2 58 | 4 59 | 4 60 | 2 61 | 4 62 | 2 63 | 2 64 | 8 65 | 2 66 | 2 67 | 4 68 | 2 69 | 4 70 | 4 71 | 2 72 | 2 73 | 4 74 | 4 75 | 2 76 | 4 77 | 2 78 | 2 79 | 8 80 | 2 81 | 2 82 | 4 83 | 2 84 | 4 85 | 4 86 | 2 87 | 2 88 | 4 89 | 4 90 | 2 91 | 4 92 | 2 93 | 2 94 | 8 95 | 2 96 | 2 97 | 4 98 | 2 99 | 4 100 | 4 101 | 2 102 | 2 103 | 4 104 | 4 105 | -------------------------------------------------------------------------------- /test/examples/shinh_letterbox.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Letterbox+Sequence 3 | behjilljkfeknmmopnnj-b%{> 4 | nnmpqook-b,[10+a_ 5 | 15 6 | [stdout] 7 | 3 8 | 6 9 | 8 10 | 7 11 | 10 12 | 10 13 | 8 14 | 9 15 | 4 16 | 3 17 | 9 18 | 12 19 | 11 20 | 11 21 | 13 22 | 14 23 | 12 24 | 12 25 | 8 26 | 12 27 | 15 28 | 18 29 | 20 30 | 19 31 | 22 32 | 22 33 | 20 34 | 21 35 | 16 36 | 12 37 | 15 38 | 18 39 | 20 40 | 19 41 | 22 42 | 22 43 | 20 44 | 21 45 | 16 46 | 11 47 | 14 48 | 17 49 | 19 50 | 18 51 | 21 52 | 21 53 | 19 54 | 20 55 | 15 56 | 14 57 | 17 58 | 20 59 | 22 60 | 21 61 | 24 62 | 24 63 | 22 64 | 23 65 | 18 66 | 15 67 | 18 68 | 21 69 | 23 70 | 22 71 | 25 72 | 25 73 | 23 74 | 24 75 | 19 76 | 13 77 | 16 78 | 19 79 | 21 80 | 20 81 | 23 82 | 23 83 | 21 84 | 22 85 | 17 86 | 13 87 | 16 88 | 19 89 | 21 90 | 20 91 | 23 92 | 23 93 | 21 94 | 22 95 | 17 96 | 9 97 | 12 98 | 15 99 | 17 100 | 16 101 | 19 102 | 19 103 | 17 104 | 18 105 | 13 106 | 15 -------------------------------------------------------------------------------- /test/examples/shinh_make_100.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?make+100+ver2 3 | l`@a+,-a-100|~a 4 | [input] 5 | 12 32 97 13 43 6 | 27 10 12 5 73 7 | [output] 8 | 12 32 13 43 9 | 10 12 5 73 10 | -------------------------------------------------------------------------------- /test/examples/shinh_polarized_political.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?polarised+political+climate 3 | -- myorig: $`' @l~l.~@%{[_(}>.>`(l!{/' [.)_)>; 4 | -- tails: .~~\${]1/.]1`$!/@.%/`}@[_ 5 | l.&(l?_!,]l.~@=[ 6 | [stdin] 7 | <>>><<><<>>><>< 8 | <>><<<<<<>>>><><>>>>>>>>> 9 | <><<><><<<<<>><><<<<><<<><><>><< 10 | <<<<><><<<<<<>><>>>><<<><>><<<<<<>>>><<> 11 | <>>>>><<<<>>>>><<<<><>><<<>><>>><>><>>>><>>><>> 12 | <><<>>><><<<<<<<>><>><>><<><<>>>>><><><>><>><<<<<>>< 13 | <><<><<><><><><<><>>><>><<<<><<<<<<<<>>>>>><>><<>>>><<<< 14 | >>><><><>><>><<>>><<<><><<<<<>>>><><<<><<>><>><<>><>>><>><< 15 | <>><>><<<<><<<<<>>>><><<<<<<<>>><><><><<<><>><><>><<<<>><<>>>> 16 | ><>><>>>>><><><>><><>>>>>><<><<><<>>><<<><<>>>>>>>>>><>><>>><<>> 17 | ><><<<><>>>><<><><<><><<>><<<<<>>>>><<<<<><<<>>>><><<><>>>><><>>> 18 | >>><><<<><><>><<<<><<<>><<><<<>>><<>><>><><><<>>>>>>><<>><<>>><><< 19 | <><<<<<>>>>><>><><<<><<><><>>><<><>>>>>>><>>><<<><<><<<><><<<<><<<< 20 | <><><<<<>><<><>>><<><>>><>><>>><>>><>>>><><<>>>>><><><>>>>>>>>><<>><> 21 | >>>>><>>>>><>>><<<<<<<><<><>><<<>>>><>>>><<<><>>><<>><<><<<<<<><>>><<>< 22 | ><>>>><<><<>>><<<><<><<<<<>>><>>><><<<<<><><>>>><><><<><<<>><><<>><<><<< 23 | ><><>><><><<>>><><<<<><<><>>>><>>><><>><<>>><<<>><<>>>><>><<<>><><<<<><>>> 24 | >><>><<<>><<><<<>>>>><><<<<<><><<>>><<><><<><<<<><<>><>>>><<><<>>>>><<<><> 25 | ><<<<><<>>>><>> >><<<><>>><<><<<><>><>>><>>><<<<<<><<<< <<>>>><><<<>> 26 | ><><><<><>>><<> >><><><><<<<>><><>><>><<<>>><>><< <>>< <>>>><>> 27 | <<><<><<<><<<> ><<>><<<<<<<<><>>< ><><>>><<> <<<>>> 28 | ><><<><<><<<<> >>><<>>><<<<>> ><>><>>>>< ><><> 29 | ><<><<<>><>><<><<>><><<<><> ><<><<><<< >><><> 30 | <>>>><>><<< >><<<>><<<><<>><> >><<>>>>>< <><<>> 31 | ><><<<><<> ><<<><><<><><><><< ><><>>>>< <><<< 32 | <>><>><>>< <<<<>>><<><>>><<<< <<<<>>>>>< >>>> 33 | <><>>><>>< <>><<<>><<>><><>><< >><<><<<< <>>> 34 | ><<><<<<<> ><>><>>>><<<<>><<< >><<<<<<< >><< 35 | >>>><><>><><<><<<>>><>><<><< >><><<<< <<>< 36 | >>><<<<<><>><<><<<>>><>< ><<><<<< ><> 37 | <<><<<>><<><><>><><<> <<<><>><< 38 | <<>><<>> >><<>><<><< <><>>><><>< 39 | >>><<<>> <>><<< >><><><<><> 40 | [stdout] 41 | <<<<<<<>>>>>>>> 42 | <<<<<<<<<>>>>>>>>>>>>>>>> 43 | <<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>> 44 | <<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>> 45 | <<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 46 | <<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>> 47 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>> 48 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 49 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>> 50 | <<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 51 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 52 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 53 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 54 | <<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 55 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 56 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 57 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 58 | <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 59 | <<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>> >>>>>>>>>>>>> 60 | <<<<<<<<<<<<<<< <<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>> >>>> >>>>>>>> 61 | <<<<<<<<<<<<<< <<<<<<<<<<<<<<<>>> >>>>>>>>>> >>>>>> 62 | <<<<<<<<<<<<<< <<<<<<>>>>>>>> >>>>>>>>>> >>>>> 63 | <<<<<<<<<<<<<<<<<<<<<<<<>>> >>>>>>>>>> >>>>>> 64 | <<<<<<<<<<< <<<<<<<<<>>>>>>>> >>>>>>>>>> >>>>>> 65 | <<<<<<<<<< <<<<<<<<<<<<<<>>>> >>>>>>>>> >>>>> 66 | <<<<<<<<<< <<<<<<<<<<>>>>>>>> >>>>>>>>>> >>>> 67 | <<<<<<<<<< <<<<<<<<<<<>>>>>>>> >>>>>>>>> >>>> 68 | <<<<<<<<<< <<<<<<<<<<<<<<<>>> >>>>>>>>> >>>> 69 | <<<<<<<<<<<<<<<<<<<<<>>>>>>> >>>>>>>> >>>> 70 | <<<<<<<<<<<<<<<<<<<<>>>> >>>>>>>> >>> 71 | <<<<<<<<<<<<<<<<<<>>> >>>>>>>>> 72 | <<<<<<<< <<<<<<<>>>> >>>>>>>>>>> 73 | <<<<<<<< <<<<>> >>>>>>>>>>> 74 | -------------------------------------------------------------------------------- /test/examples/shinh_positions_of_upper.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?1+11+14+20+f 3 | l>'@<'[_~(1: 4 | [input] 5 | ABCDEFGHIJ 6 | gexQ7pPowM 7 | 6LF8Vg0IPd 8 | [output] 9 | 1 2 3 4 5 6 7 8 9 10 10 | 4 7 10 11 | 2 3 5 8 9 12 | -------------------------------------------------------------------------------- /test/examples/shinh_range_analysis.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Range+Analysis 3 | l[6_(l/'*`%{[.,*a]@,_!{[)',_(b])') 4 | [input] 5 | range(2,4) * range(-5,-1) 6 | range(0,2) * range(-2,-1) 7 | range(3,4) * range(-3,4) 8 | [output] 9 | range(-20,-2) 10 | range(-4,0) 11 | range(-12,16) 12 | -------------------------------------------------------------------------------- /test/examples/shinh_stop_five_after.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | -- http://golf.shinh.org/p.rb?Stop+five+after+nonzero 3 | l%[(v~5@:[ 4 | [input] 5 | 0 6 | 0 7 | 0 8 | 0 9 | 0 10 | 0 11 | 0 12 | 11 13 | 0 14 | 0 15 | 0 16 | 3 17 | 0 18 | 0 19 | 142 20 | 0 21 | [output] 22 | 0 23 | 0 24 | 0 25 | 0 26 | 0 27 | 0 28 | 0 29 | 11 30 | 0 31 | 0 32 | 0 33 | 3 34 | -------------------------------------------------------------------------------- /test/examples/stack_overlfow_error.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | `1)!] 3 | [stderr] 4 | Stack Overflow Error 5 | -------------------------------------------------------------------------------- /test/examples/t1.test: -------------------------------------------------------------------------------- 1 | [input] 2 | hi 3 | 4 | [prog] 5 | 1+l 6 | "line2" 7 | 1/0 8 | "line3" 9 | 10 | [stdout] 11 | ij 12 | line2 13 | [stderr] 14 | 3:2 (/) div 0 (DynamicError) 15 | -------------------------------------------------------------------------------- /test/examples/test_output_different_types.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | "int" 3 | 3 4 | "(int)" 5 | 0,1,3 6 | "str" 7 | "asdf" 8 | "chr" 9 | 'a 10 | "(str)" 11 | "a1","b2","c" 12 | "((int))" 13 | 1,3,(1,4) 14 | "(((int))" 15 | 1,3,(1,4),(1,3,(1,4);) 16 | "((((int))))" 17 | 1,3,(1,4),(1,3,(1,4);),(1,3,(1,4),(1,3,(1,4);)) 18 | "" 19 | "((str))" 20 | "a1","b2","c"@v1,v1 21 | "(((str)))" 22 | v1,v1@v2,v2 23 | "end" 24 | 25 | [stdout] 26 | int 27 | 3 28 | (int) 29 | 0 30 | 1 31 | 3 32 | str 33 | asdf 34 | chr 35 | a 36 | (str) 37 | a1 38 | b2 39 | c 40 | ((int)) 41 | 1 3 42 | 1 4 43 | (((int)) 44 | 1 3 45 | 1 4 46 | 47 | 1 3 48 | 1 4 49 | ((((int)))) 50 | 1 3 51 | 1 4 52 | 53 | 1 3 54 | 1 4 55 | 56 | 1 3 57 | 1 4 58 | 59 | 1 3 60 | 1 4 61 | ((str)) 62 | a1 b2 c 63 | a1 b2 c 64 | (((str))) 65 | a1 b2 c 66 | a1 b2 c 67 | 68 | a1 b2 c 69 | a1 b2 c 70 | end 71 | -------------------------------------------------------------------------------- /test/examples/test_unset_var_warning.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | asdf -- doesn't warn since we haven't set a long id yet (assuming intentional) 3 | let ab=5 4 | qwerty 5 | [stdout] 6 | asdf 7 | qwerty 8 | [stderr] 9 | 3:1 (qwerty) using unset var (Warning) 10 | -------------------------------------------------------------------------------- /test/examples/triple_use.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | let a=a+1`0 3 | a take 10; 4 | a take 10; 5 | a take 10; 6 | [stdout] 7 | 0 1 2 3 4 5 6 7 8 9 8 | 0 1 2 3 4 5 6 7 8 9 9 | 0 1 2 3 4 5 6 7 8 9 -------------------------------------------------------------------------------- /test/examples/undefined_for_base.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | 'a + 'b 3 | 4 | [stderr] 5 | 1:4 (+) op is not defined for arg types: Char,Char (TypeError) -------------------------------------------------------------------------------- /test/examples/unset_identifier_error.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | asdf 3 | [output] 4 | asdf 5 | -------------------------------------------------------------------------------- /test/examples/unterminated_string_error.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | "hi 3 | [stderr] 4 | unterminated string (LexError) -------------------------------------------------------------------------------- /test/examples/utf8.test: -------------------------------------------------------------------------------- 1 | [prog] 2 | "├───╯" 3 | "├───╯"p 4 | 'a-1000 5 | -- not really a test of utf8 so much as it is a test that binary characters don't cause problems, since we input/output bytes 6 | [stdout] 7 | ├───╯ 8 | "\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x95\xaf" 9 | [stderr] 10 | char value, -903, outside of byte range (DynamicError) 11 | -------------------------------------------------------------------------------- /test/gen_crcl_example.rb: -------------------------------------------------------------------------------- 1 | # gen this rather than have it as a file, because my editor will change the \r if I open and save 2 | File.open("test/examples/crcl.test","w"){|f| 3 | f.puts "[prog] 4 | 1\r2\r\n3\n1/0 -- see line number 5 | [output] 6 | 1 7 | 2 8 | 3 9 | [stderr] 10 | 4:2 (/) div 0 (DynamicError) 11 | " 12 | } 13 | -------------------------------------------------------------------------------- /test/golf_test_expected.txt: -------------------------------------------------------------------------------- 1 |  ᐳ Fizz/ 2 |  ᐳ 1,2,3 3 |  ᐳ 1,2,(3,4) 4 |  ᐳ y/2`37@y[3 5 |  ᐳ zziF 6 | 1 7 | 2 8 | 3 9 | 1 2 10 | 3 4 11 | 37 12 | 18 13 | 9 14 | -------------------------------------------------------------------------------- /test/golf_test_input.txt: -------------------------------------------------------------------------------- 1 | Fizz/ 2 | 1,2,3 3 | 1,2,(3,4) 4 | y/2`37@y[3 -------------------------------------------------------------------------------- /test/repl_test_expected.txt: -------------------------------------------------------------------------------- 1 |  ᐳ 1+2*4 -- That is an up arrow (prev command 1+2)followed by *4 2 |  ᐳ 1,2,3 tail -- One tab to complete tail 3 |  ᐳ 4,5,6 print 4 |  ᐳ 7,8,9 5 |  ᐳ Num 6 | 12 7 | [Num] 8 | 2 3 9 | 4 10 | 5 11 | 6 12 | [Num] 13 | 7 8 9 14 | -------------------------------------------------------------------------------- /test/repl_test_input.txt: -------------------------------------------------------------------------------- 1 | *4 -- That is an up arrow (prev command 1+2)followed by *4 2 | 1,2,3 tai -- One tab to complete tail 3 | 4,5,6 print 4 | 7,8,9 5 | -------------------------------------------------------------------------------- /test/test_behavior.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | require "./test/check_example.rb" 3 | 4 | line = 1 5 | pass = 0 6 | behavior_tests = File.read("./test/behavior_tests.atl", :encoding => 'iso-8859-1').lines.to_a 7 | behavior_tests.map{|test| 8 | (line+=1; next) if test.strip == "" || test =~ /^--/ 9 | check_example(test){ 10 | "behavior test line %d" % [line] 11 | } 12 | pass += 1 13 | line += 1 14 | } 15 | 16 | puts "PASS %d behavior tests" % pass 17 | -------------------------------------------------------------------------------- /test/test_docs.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | runs = 0 3 | def failit(filename,line,prog,expected,output) 4 | puts "FAIL doc: " 5 | puts prog 6 | puts "Expecting:", expected 7 | puts "Found:", output 8 | puts "from: "+filename+" line: "+line.to_s 9 | exit(1) 10 | end 11 | 12 | (Dir['docs/*.md']<<"README.md").each{|doc| 13 | file = File.read(doc, :encoding => "utf-8") 14 | tests = 0 15 | file.scan(/((?: .*\n+)+) ───+\n((:? .*\n+)+)/){|a| 16 | tests += 1 17 | line=$`.count($/)+1 18 | prog,expected=*a 19 | prog.gsub!(/^ /,'') 20 | expected.gsub!(/^ /,'') 21 | 22 | File.write("test/prog.atl",prog) 23 | 24 | truncate = expected =~ /\.\.\.$/ 25 | expected.gsub!(/\.\.\.$/,'') 26 | 27 | cmd = "./atlas -L test/prog.atl" 28 | if truncate 29 | stdout, stderr, status = Open3.capture3("#{cmd} | head -c #{truncate}") 30 | else 31 | stdout, stderr, status = Open3.capture3(cmd) 32 | end 33 | stderr=stderr.split("\e[31m").join 34 | stderr=stderr.split("\e[0m").join 35 | output = stdout + stderr 36 | output.gsub!(/ *$/,'') 37 | output.strip! 38 | expected.strip! 39 | 40 | if output != expected 41 | failit(doc,line,prog,expected,output) 42 | else 43 | runs += 1 44 | end 45 | } 46 | raise "probably have misformatted example" if tests != file.split(/───+/).size-1 47 | } 48 | 49 | puts "PASS %d doc tests" % runs 50 | -------------------------------------------------------------------------------- /test/test_examples.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | require 'open3' 3 | runs = 0 4 | def failit(filename,test, reason) 5 | puts "FAIL example: " 6 | puts test 7 | puts reason 8 | puts "from: "+filename 9 | exit(1) 10 | end 11 | 12 | tests = Dir["test/examples/*.test"] 13 | section_regex = /^\[.*?\]\n/i 14 | tests.each{|test_filename| 15 | test = File.read(test_filename) 16 | sections = test.scan(section_regex) 17 | datum = test.split(section_regex)[1..-1] 18 | prog = nil 19 | args = "" 20 | expected_stderr = input = expected_stdout = "" 21 | sections.zip(datum){|section,data| 22 | case section.chomp[1...-1].downcase 23 | when "input","stdin" 24 | input = data.strip 25 | when "stdout","output" 26 | expected_stdout = data.strip 27 | when "stderr" 28 | expected_stderr = (data||"").strip 29 | when "prog" 30 | prog = data.strip 31 | when "args" 32 | args=data.strip 33 | else 34 | raise "unknown section %p" % section 35 | end 36 | } 37 | raise "invalid test #{test_filename}" if expected_stdout=="" && expected_stderr=="" 38 | 39 | File.write("test/input", input) 40 | File.write("test/prog.atl",prog) 41 | stdout, stderr, status = Open3.capture3("timeout 5s ./atlas test/prog.atl < test/input") 42 | 43 | stdout.strip! 44 | stderr.strip! 45 | stderr=stderr.split("\e[31m").join 46 | stderr=stderr.split("\e[0m").join 47 | 48 | if !expected_stderr.empty? && !stderr[expected_stderr] || expected_stderr.empty? && !stderr.empty? 49 | failit(test_filename,test,"stderr was\n"+stderr) 50 | elsif stdout != expected_stdout 51 | failit(test_filename,test,"stdout was\n"+stdout) 52 | els 53 | else 54 | runs += 1 55 | end 56 | } 57 | puts "PASS %d example runs" % runs 58 | -------------------------------------------------------------------------------- /test/test_fuzz.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | require 'timeout' 3 | require "./repl.rb" 4 | 5 | symbols = "~`!@#$%^&*()_-+={[}]|\\'\";:,<.>/?" 6 | 7 | 8 | #symbols = "~!!@@$()-=[]';?:" 9 | numbers = "0123" 10 | letters = "abCNS" 11 | spaces = " \n\n\n\n" # twice as likely 12 | 13 | # Just the interesting characters to focus on testing parse 14 | # all = "[! \n()'\"1\\:?ab".chars.to_a + [':=','a:=',"seeParse","seeInference","seeType"] 15 | 16 | all = (symbols+numbers+letters+spaces).chars+['"ab12"','p','print','help','ops','version','type','1.2','()',','] 17 | 18 | ReadStdin = Promise.new{ str_to_lazy_list("ab12") } 19 | 20 | # todo take all tests and make larger programs that are almost correct 21 | 22 | n = 1000000 23 | STEP_LIMIT = 5000 24 | 25 | class Promise 26 | alias old_value value 27 | def value 28 | $reductions += 1 29 | raise AtlasError.new("step limit exceeded", nil) if $reductions > STEP_LIMIT 30 | old_value 31 | end 32 | end 33 | 34 | 4.upto(8){|program_size| 35 | n.times{ 36 | program = program_size.times.map{all[(rand*all.size).to_i]}*"" 37 | program_io=StringIO.new(program) 38 | begin 39 | puts program 40 | $reductions = 0 41 | Timeout::timeout(0.2) { 42 | repl(program_io) 43 | } 44 | # puts "output: ", output_io.string 45 | rescue AtlasError,Timeout::Error => e 46 | 47 | rescue => e 48 | STDERR.puts "failed, program was" 49 | STDERR.puts program 50 | raise e 51 | end 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/test_increasing.rb: -------------------------------------------------------------------------------- 1 | require "./repl.rb" 2 | 3 | # todo consider errors 4 | # todo consider trying to hone in on list dim first, vec levels could be invalid? 5 | 6 | Types = 3.times.map{|vl| 7 | 3.times.map{|ll| 8 | [:num,:char,:a].map{|base_elem| 9 | TypeWithVecLevel.new(Type.new(ll,base_elem),vl) 10 | } 11 | } 12 | }.flatten 13 | 14 | Memo = {} 15 | def calc(op, arg_types) 16 | key = "%p %p" % [op.sym,arg_types] 17 | return Memo[key] if Memo.key? key 18 | args = arg_types.map{|type| 19 | arg=IR.new 20 | arg.type_with_vec_level = type 21 | arg.op = Op.new 22 | arg 23 | } 24 | node = IR.new(op,nil,nil,args) 25 | calc_type(node) 26 | return (Memo[key] = node.last_error ? nil : node.type_with_vec_level) 27 | end 28 | 29 | # Test list dims only increase list dims regardless of vec levels 30 | 31 | #1 arg 32 | Ops1.values.each{|op| 33 | Types.each{|t1| 34 | r = calc(op, [t1]) 35 | next unless r 36 | 3.times{|vec_level| 37 | r2 = calc(op, [TypeWithVecLevel.new(t1.type+1, vec_level)]) 38 | raise if r2 && r.type.dim>r2.type.dim 39 | } 40 | } 41 | } 42 | 43 | # 2 arg 44 | 45 | Ops2.values.each{|op| 46 | next if op.sym == ";" # to/fromBase is special, I am ok with this causing issues if used in circular programming as it is unlikely to be used that way, this exception allows it to be overloaded for to or from a base. 47 | Types.each{|t1| 48 | Types.each{|t2| 49 | r = calc(op, [t1,t2]) 50 | next unless r 51 | (1..3).each{|b| 52 | 3.times{|v1|3.times{|v2| 53 | args = [TypeWithVecLevel.new(t1.type+b[0], v1), 54 | TypeWithVecLevel.new(t2.type+b[1], v2)] 55 | r2 = calc(op, args) 56 | if r2 && r.type.dim>r2.type.dim 57 | p op.sym 58 | p '%p %p -> %p' % [t1,t2,r] 59 | p '%p %p -> %p' % [args[0],args[1],r2] 60 | raise 61 | end 62 | }} 63 | } 64 | } 65 | } 66 | } 67 | 68 | # Test vec dims only increase vec dims and do not change list dims 69 | 70 | #1 arg 71 | Ops1.values.each{|op| 72 | Types.each{|t1| 73 | r = calc(op, [t1]) 74 | next unless r 75 | r2 = calc(op, [TypeWithVecLevel.new(t1.type, t1.vec_level+1)]) 76 | 77 | if r2 && r.type.dim!=r2.type.dim 78 | p op.name 79 | puts "#{t1.inspect} -> #{r.inspect}" 80 | puts "#{TypeWithVecLevel.new(t1.type, t1.vec_level+1).inspect} -> #{r2.inspect}" 81 | raise 82 | end 83 | raise if r2 && r.vec_level>r2.vec_level 84 | } 85 | } 86 | 87 | Ops2.values.each{|op| 88 | Types.each{|t1| 89 | Types.each{|t2| 90 | r = calc(op, [t1,t2]) 91 | next unless r 92 | (1..3).each{|b| 93 | args = [TypeWithVecLevel.new(t1.type, t1.vec_level+b[0]), 94 | TypeWithVecLevel.new(t2.type, t2.vec_level+b[1])] 95 | r2 = calc(op, args) 96 | if r2 && r.type.dim!=r2.type.dim 97 | p op.sym 98 | p '%p %p -> %p' % [t1,t2,r] 99 | p '%p %p -> %p' % [args[0],args[1],r2] 100 | raise 101 | end 102 | raise if r2 && r.vec_level>r2.vec_level 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /test/test_op_examples.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | require './test/check_example.rb' 3 | 4 | pass=0 5 | ActualOpsList.each{|op| 6 | (op.examples+op.tests).each{|example| 7 | check_example(example){ 8 | "example test for op: #{op.name}" 9 | } 10 | pass+=1 11 | } 12 | } 13 | puts "PASS #{pass} op examples" 14 | -------------------------------------------------------------------------------- /test/test_parse.rb: -------------------------------------------------------------------------------- 1 | # Test that parse generates the correct AST 2 | tests = <<'EOF' 3 | 1+2 -> 1+2 4 | 1+2*3 -> 1+2*3 5 | 1+(2*3) -> 1+(2*3) 6 | 1~+2*3 -> 1~+2*3 7 | 1+2~*3 -> 1+2~*3 8 | 1+(2~)*3 -> 1+(2~)*3 9 | 1~~ -> 1~~ 10 | 11 | -- Test implicit 12 | 1 2 -> 1 2 13 | 1 (2*3) -> 1 (2*3) 14 | (1*2) 3 -> 1*2 3 15 | 16 | 1 2 3 -> 1 2 3 17 | (1 2) 3 -> 1 2 3 18 | 1 (2 3) -> 1 (2 3) 19 | (1)(2)(3) -> 1 2 3 20 | (1)(2) (3) -> 1 2 3 21 | (1) (2)(3) -> 1 2 3 22 | (1 2) (3 4) -> 1 2 (3 4) 23 | (1 2) 3 (4 5) -> 1 2 3 (4 5) 24 | 25 | -- Test space doesnt do anything 26 | 1 + 2~ -> 1+2~ 27 | 1 + 2*3 -> 1+2*3 28 | 1 ~ -> 1~ 29 | 1+2 * 3+4 -> 1+2*3+4 30 | 1+2 3*4 -> 1+2 3*4 31 | (1+2 ) -> 1+2 32 | ( 1+2) -> 1+2 33 | 34 | -- test unbalanced parens 35 | 1+2)+3 -> 1+2+3 36 | 1+(2*3 -> 1+(2*3) 37 | 38 | -- Identifiers 39 | AA -> AA 40 | aA -> aA 41 | a_a -> a_a 42 | A_ A -> A_ A 43 | 44 | -- Test binary character identifiers 45 | ->  -- chr two char 127 in a row 46 | λλ -> λλ -- two unicode chars in a row 47 | 48 | 1; head -> 1;[ 49 | 50 | -- Test apply 51 | 1+2@+3 -> 1+(2+3) 52 | 1+2@+3@+4 -> 1+(2+(3+4)) 53 | 1-2~@+3 -> 1-(2~+3) 54 | 1+2~\@+3 -> 1+(2~\+3) 55 | 1+2@~+3 -> 1+(2~)+3 56 | 1+2@~@+3 -> 1+(2~+3) 57 | -- 1+2@+3- -> 1+(2+3)- 58 | 1+2@+3-4 -> 1+(2+3)-4 59 | 1@+2 -> 1+2 60 | 1@~ -> 1~ 61 | 1+(1+5-)@- -> 1+((1+5-)-) 62 | 63 | -- 1+2@3 -> 1+(2 3) 64 | --1@ -> ParseError 65 | 1@1 -> 1@1 66 | -- 1@a -> 1@a 67 | EOF 68 | 69 | require "./repl.rb" 70 | 71 | class AST 72 | def ==(rhs) 73 | # ignore paren vars 74 | return args[0]==rhs if self.op.name == "set" && args[1].token.str[/_/] 75 | return rhs.args[0]==self if rhs.op.name == "set" && rhs.args[1].token.str[/_/] 76 | self.op == rhs.op && self.args.zip(rhs.args).all?{|s,r|s==r} 77 | end 78 | def inspect 79 | return self.token.str if self.op.name == "data" 80 | self.op.name + " " + self.args.map(&:inspect)*" " 81 | end 82 | end 83 | 84 | class Op 85 | def ==(rhs) 86 | self.name == rhs.name 87 | end 88 | end 89 | 90 | start_line=2 91 | pass = 0 92 | name = $0.sub('test/test_','').sub(".rb","") 93 | tests.lines.each{|test| 94 | start_line += 1 95 | next if test.strip == "" || test =~ /^--/ 96 | i,o=test.split("-"+">") 97 | STDERR.puts "INVALID test #{test}" if !o 98 | o.strip! 99 | begin 100 | tokens,lines = lex(i) 101 | found = parse_line(tokens[0]) 102 | 103 | tokens,lines = lex(o) 104 | expected = parse_line(tokens[0]) 105 | rescue Exception 106 | found = $! 107 | end 108 | 109 | if o=~/Error/ ? found.class.to_s!=o : found != expected 110 | STDERR.puts "FAIL: #{name} test line #{start_line}" 111 | STDERR.puts i 112 | STDERR.puts "expected:" 113 | STDERR.puts expected.inspect 114 | STDERR.puts "found:" 115 | raise found if Exception === found 116 | STDERR.puts found.inspect 117 | exit(1) 118 | end 119 | 120 | pass += 1 121 | } 122 | 123 | puts "PASS #{pass} #{name} tests" 124 | -------------------------------------------------------------------------------- /test/test_repl.sh: -------------------------------------------------------------------------------- 1 | echo 1+2 | ruby atlas > /dev/null 2 | cat test/repl_test_input.txt | ruby atlas > test/repl_test_output.txt 2>&1 3 | diff test/repl_test_output.txt test/repl_test_expected.txt 4 | -------------------------------------------------------------------------------- /test/test_vec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../repl.rb' 2 | 3 | def check(expected,found,name,test) 4 | if expected!=found 5 | STDERR.puts "expecting %p found %p for %s of %p" % [expected,found,name,test] 6 | exit 7 | end 8 | end 9 | 10 | fn_type = create_specs({[Num]=>Num})[0] 11 | 12 | T = TypeWithVecLevel 13 | U = Unknown 14 | 15 | # todo test VecOf 16 | # what other things can gen errors? are many errors impossible? 17 | 18 | # => zip_level, rep_level, promote_levels, return type 19 | tests = { 20 | # [int] 21 | [[T.new(Num+0,0)],{[Num]=>Num}] => [0,[0],[1],"Num"], 22 | [[T.new(Num+0,1)],{[Num]=>Num}] => [0,[0],[0],"Num"], 23 | [[T.new(Num+1,0)],{[Num]=>Num}] => [0,[0],[0],"Num"], 24 | [[T.new(Num+1,1)],{[Num]=>Num}] => [1,[0],[0],""], 25 | [[T.new(Num+2,0)],{[Num]=>Num}] => [1,[0],[0],""], 26 | [[T.new(Num+2,1)],{[Num]=>Num}] => [2,[0],[0],"<>"], 27 | 28 | # [a] 29 | [[T.new(Num+0,0)],{[A]=>A}] => [0,[0],[1],"Num"], 30 | [[T.new(Num+0,1)],{[A]=>A}] => [0,[0],[0],"Num"], 31 | [[T.new(Num+1,0)],{[A]=>A}] => [0,[0],[0],"Num"], 32 | [[T.new(Num+1,1)],{[A]=>A}] => [1,[0],[0],""], 33 | [[T.new(Num+2,0)],{[A]=>A}] => [0,[0],[0],"[Num]"], 34 | [[T.new(Num+2,1)],{[A]=>A}] => [1,[0],[0],"<[Num]>"], 35 | 36 | # [int] [int] 37 | [[T.new(Num+0,0),T.new(Num+0,0)],{[[Num],[Num]]=>Num}] => [0,[0,0],[1,1],"Num"], 38 | [[T.new(Num+2,2),T.new(Num+0,0)],{[[Num],[Num]]=>Num}] => [3,[0,3],[0,1],"<<>>"], 39 | [[T.new(Num+1,0),T.new(Num+1,0)],{[[Num],[Num]]=>Num}] => [0,[0,0],[0,0],"Num"], 40 | [[T.new(Num+2,0),T.new(Num+1,0)],{[[Num],[Num]]=>Num}] => [1,[0,1],[0,0],""], 41 | [[T.new(Num+1,1),T.new(Num+1,0)],{[[Num],[Num]]=>Num}] => [1,[0,1],[0,0],""], 42 | [[T.new(Num+2,1),T.new(Num+1,0)],{[[Num],[Num]]=>Num}] => [2,[0,2],[0,0],"<>"], 43 | [[T.new(Num+1,1),T.new(Num+1,1)],{[[Num],[Num]]=>Num}] => [1,[0,0],[0,0],""], 44 | [[T.new(Num+2,1),T.new(Num+1,1)],{[[Num],[Num]]=>Num}] => [2,[0,1],[0,0],"<>"], 45 | 46 | # [a] [int] 47 | [[T.new(Num+1,0),T.new(Num+0,0)],{[[A],[Num]]=>A}] => [0,[0,0],[0,1],"Num"], 48 | [[T.new(Num+1,0),T.new(Num+1,0)],{[[A],[Num]]=>A}] => [0,[0,0],[0,0],"Num"], 49 | [[T.new(Num+1,1),T.new(Num+1,0)],{[[A],[Num]]=>A}] => [1,[0,1],[0,0],""], 50 | [[T.new(Num+2,0),T.new(Num+1,0)],{[[A],[Num]]=>A}] => [0,[0,0],[0,0],"[Num]"], 51 | [[T.new(Num+1,0),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [1,[1,0],[0,0],""], 52 | [[T.new(Num+1,1),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [1,[0,0],[0,0],""], 53 | [[T.new(Num+2,0),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [1,[1,0],[0,0],"<[Num]>"], 54 | [[T.new(Num+2,1),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [1,[0,0],[0,0],"<[Num]>"], 55 | 56 | # [a] [a] 57 | [[T.new(Num+1,0),T.new(Num+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,1],"Num"], 58 | [[T.new(Num+1,0),T.new(Num+1,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"Num"], 59 | [[T.new(Num+0,1),T.new(Num+1,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"Num"], 60 | [[T.new(Num+2,0),T.new(Num+1,0)],{[[A],[A]]=>A}] => [1,[0,1],[0,0],""], 61 | [[T.new(Num+2,1),T.new(Num+1,0)],{[[A],[A]]=>A}] => [2,[0,2],[0,0],"<>"], 62 | [[T.new(Num+2,0),T.new(Num+2,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[Num]"], 63 | [[T.new(Num+1,1),T.new(Num+2,0)],{[[A],[A]]=>A}] => [1,[0,0],[0,0],""], 64 | [[T.new(Num+2,1),T.new(Num+2,0)],{[[A],[A]]=>A}] => [1,[0,1],[0,0],"<[Num]>"], 65 | 66 | # [a] [b] 67 | [[T.new(Num+1,0),T.new(Num+0,0)],{[[A],[B]]=>A}] => [0,[0,0],[0,1],"Num"], 68 | [[T.new(Num+1,0),T.new(Num+1,0)],{[[A],[B]]=>A}] => [0,[0,0],[0,0],"Num"], 69 | [[T.new(Num+2,0),T.new(Num+1,0)],{[[A],[B]]=>A}] => [0,[0,0],[0,0],"[Num]"], 70 | [[T.new(Num+2,1),T.new(Num+1,0)],{[[A],[B]]=>A}] => [1,[0,1],[0,0],"<[Num]>"], 71 | [[T.new(Num+2,1),T.new(Num+1,0)],{[[A],[B]]=>A}] => [1,[0,1],[0,0],"<[Num]>"], 72 | [[T.new(Num+2,1),T.new(Num+1,1)],{[[A],[B]]=>A}] => [1,[0,0],[0,0],"<[Num]>"], 73 | 74 | # Unknown tests 75 | # [int] would all be failures during lookup type fn 76 | 77 | # a 78 | [[T.new(U+0,0)],{[A]=>A}] => [0,[0],[0],"a"], 79 | [[T.new(U+1,0)],{[A]=>A}] => [0,[0],[0],"a"], 80 | [[T.new(U+2,0)],{[A]=>A}] => [0,[0],[0],"[a]"], 81 | [[T.new(U+0,0+1)],{[A]=>A}] => [1,[0],[0],""], 82 | [[T.new(U+0,0+2)],{[A]=>A}] => [2,[0],[0],"<>"], 83 | [[T.new(U+2,0+2)],{[A]=>A}] => [2,[0],[0],"<<[a]>>"], 84 | 85 | # [a] [int] 86 | [[T.new(U+0,0),T.new(Num+0,0)],{[[A],[Num]]=>A}] => [0,[0,0],[0,1],"a"], 87 | [[T.new(U+0,0),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [1,[1,0],[0,0],""], 88 | [[T.new(U+2,2),T.new(Num+2,0)],{[[A],[Num]]=>A}] => [2,[0,1],[0,0],"<<[a]>>"], 89 | 90 | # [a] [a] 91 | [[T.new(U+0,0),T.new(U+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"a"], 92 | [[T.new(U+1,0),T.new(U+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"a"], 93 | [[T.new(U+2,0),T.new(U+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[a]"], 94 | [[T.new(U+1,0),T.new(U+1,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"a"], 95 | [[T.new(U+2,0),T.new(U+1,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[a]"], 96 | [[T.new(U+2,0),T.new(U+2,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[a]"], 97 | [[T.new(U+2,0),T.new(U+0,1)],{[[A],[A]]=>A}] => [1,[1,0],[0,0],"<[a]>"], 98 | 99 | [[T.new(U+0,0),T.new(Num+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,1],"Num"], 100 | [[T.new(U+1,0),T.new(Num+0,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,1],"Num"], 101 | [[T.new(U+2,0),T.new(Num+0,0)],{[[A],[A]]=>A}] => [1,[0,1],[0,1],""], 102 | [[T.new(U+0,0),T.new(Num+1,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"Num"], 103 | [[T.new(U+0,0),T.new(Num+2,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[Num]"], 104 | [[T.new(U+2,0),T.new(Num+2,0)],{[[A],[A]]=>A}] => [0,[0,0],[0,0],"[Num]"], 105 | 106 | # [a] [b] todo but probably not needed 107 | 108 | } 109 | # tests = {tests.keys[-1] => tests[tests.keys[-1]]} 110 | 111 | tests.each{|k,v| 112 | arg_types, spec = *k 113 | node=IR.new 114 | node.args = arg_types.map{|a| 115 | r=IR.new 116 | r.type_with_vec_level = a 117 | r.op = Op.new 118 | r 119 | } 120 | node.op=Op.new 121 | fn_type = create_specs(spec)[0] 122 | # begin 123 | t = possible_types(node, fn_type) 124 | # rescue #AtlasError => e 125 | if node.last_error 126 | if v != nil 127 | STDERR.puts "not expecting error for %p"%[k] 128 | raise node.last_error 129 | end 130 | next 131 | end 132 | # end 133 | if v == nil 134 | STDERR.puts "expecting error for %p"%[k] 135 | STDERR.puts "found %p %p %p" % [t.inspect,node.zip_level,node.rep_levels] 136 | exit 137 | end 138 | ez,er,ep,et = *v 139 | check(et,t.inspect,"type",k) 140 | check(ez,node.zip_level,"zip_level",k) 141 | check(er,node.rep_levels,"rep level",k) 142 | check(ep,node.promote_levels,"promote level",k) 143 | } 144 | 145 | puts "PASS #{tests.size} vec tests" 146 | -------------------------------------------------------------------------------- /test/update_date.rb: -------------------------------------------------------------------------------- 1 | s=File.read('version.rb') 2 | s.gsub!(/Atlas \S+ \(.*?\)/) { "Atlas Beta (#{Time.now.strftime("%b %d, %Y")})"} 3 | File.open('version.rb','w'){|f|f<= 0 5 | TypeWithVecLevel = Struct.new(:type,:vec_level) 6 | 7 | class Type 8 | def inspect 9 | #return "(%d %s)"%[dim,base_elem] if dim < 0 # ret nicely since could have negative type errors in circular inference that later becomes valid 10 | "["*dim + (base_elem.to_s.size>1 ? base_elem.to_s.capitalize : base_elem.to_s) + "]"*dim 11 | end 12 | def -(rhs) 13 | self+-rhs 14 | end 15 | def +(zip_level) 16 | Type.new(dim+zip_level, base_elem) 17 | end 18 | def max_pos_dim 19 | is_unknown ? Inf : dim 20 | end 21 | def string_dim # dim but string = 0 22 | dim + (is_char ? -1 : 0) 23 | end 24 | def is_char 25 | base_elem == :char 26 | end 27 | def is_unknown 28 | base_elem == :a 29 | end 30 | def can_base_be(rhs) # return true if self can be rhs 31 | return self.base_elem == rhs.base_elem 32 | end 33 | def default_value 34 | return [] if dim > 0 35 | return 32 if is_char 36 | return 0 if base_elem == :num 37 | raise DynamicError.new("access of the unknown type",nil) 38 | end 39 | end 40 | 41 | Num = Type.new(0,:num) 42 | Char = Type.new(0,:char) 43 | Str = Type.new(1,:char) 44 | Unknown = Type.new(0,:a) 45 | UnknownV0 = TypeWithVecLevel.new(Unknown,0) 46 | Empty = Unknown+1 47 | 48 | class TypeWithVecLevel 49 | def inspect 50 | #return "(%d %s)"%[vec_level,type.inspect] if vec_level < 0 # ret nicely since could have negative type errors in circular inference that later becomes valid 51 | "<"*vec_level + type.inspect + ">"*vec_level 52 | end 53 | end 54 | 55 | -------------------------------------------------------------------------------- /version.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | $site="golfscript.com/atlas" 3 | $version="Atlas Beta (Mar 31, 2025)" 4 | -------------------------------------------------------------------------------- /web/generate_site.rb: -------------------------------------------------------------------------------- 1 | raise "generate site using utf-8, not %p" % Encoding.default_external if Encoding.default_external != Encoding::UTF_8 2 | 3 | allFiles = Dir['docs/*.md'] 4 | Basenames = allFiles.map{|f| f.sub(/\.md$/,'').sub('docs/','') } - ["try_it","examples","happenings"] 5 | 6 | 7 | def convertMd(filename) 8 | basefile = filename.sub(/\.md$/,'').sub('docs/','') 9 | basefile = "index" if basefile == "README" 10 | md = File.read(filename) 11 | 12 | # remove github readme warning 13 | md = md.lines.to_a[4..-1].join if basefile == "index" 14 | 15 | File.open('t.md','w'){|f|f<Atlas 23 | Docs 24 | Happenings 25 | Examples 26 | Quick Ref 27 | Try it! 28 | Source 29 | test 36 | 37 |
'+ 38 | Basenames.sort_by(&:downcase).map{|n| 39 | "#{n.split('_').map{|word|word[0].upcase+word[1..-1]}*" "}" 40 | }*"\n"+'
' 41 | 42 | # pre shows up too small on mobile... 43 | markdown.gsub!("
",'
') 44 | markdown.gsub!("
",'') 45 | 46 | markdown.gsub!(/────+\n/,'
') 47 | 48 | title = markdown[/

(.*?)<\/h1>/,1] 49 | title = "Atlas" if filename == "README.md" 50 | 51 | markdown.gsub!(/<(h[1-3])>(.*?)<\/\1>/){"<#$1 id=\"#{$2.downcase.tr'^a-z0-9',''}\">#$2"} 52 | 53 | if title.nil? 54 | puts 'skipping '+filename 55 | return 56 | end 57 | 58 | File.open('web/site/'+basefile+'.html','w'){|f|f<<' 59 | '+title+' 60 | '+"\n"+navbar+"
"+markdown+"
\n"+''} 61 | end 62 | 63 | (allFiles<<"README.md").each{|f| 64 | convertMd(f) 65 | } 66 | 67 | `ruby web/package.rb > web/site/atlas && chmod +x web/site/atlas` 68 | raise "problem with atlas package" if `echo 1+2 > test/temp.atl; web/site/atlas test/temp.atl` != "3\n" 69 | 70 | -------------------------------------------------------------------------------- /web/op_notes.rb: -------------------------------------------------------------------------------- 1 | require './repl.rb' 2 | require 'stringio' 3 | 4 | File.open("docs/op_notes.md","w"){|f| 5 | f<<' 6 | # Op Notes 7 | 8 | This page is auto generated and shows the `help ` for any op that was deemed complicated enough to require a description. 9 | 10 | --- 11 | ' 12 | 13 | ActualOpsList.each{|op| 14 | next if !op.desc 15 | s=StringIO.new 16 | op.help(s) 17 | f<'+self+'' 17 | end 18 | end 19 | 20 | class Op 21 | def ref 22 | puts "" 23 | puts name.td 24 | puts (sym||"").td 25 | if type_summary 26 | puts type_summary.gsub('->','→').gsub('[Char]','Str').td 27 | else 28 | if type.size==1 29 | type.each{|t| 30 | puts t.inspect.gsub('->','→').gsub('[Char]','Str').td 31 | } 32 | else 33 | print "" 34 | puts type[0].inspect.gsub('->','→').gsub('[Char]','Str').escape 35 | type[1..-1].each{|t| 36 | # puts "
" 37 | puts t.inspect.gsub('->','→').gsub('[Char]','Str').escape 38 | } 39 | puts "" 40 | end 41 | end 42 | print "" 43 | examples.each_with_index{|example,i| 44 | print "" + example.gsub('->','→') + "" 45 | } 46 | print desc if examples.empty? && desc 47 | puts "" 48 | # puts "no_zip=true" if no_zip 49 | puts "" 50 | end 51 | end 52 | 53 | puts ' 54 | Atlas Quick Ref' 55 | 56 | "literals".ref 57 | puts "","integers".td,"".td,"Num".td("code"),"42 → 42".td("code"),"" 58 | puts "","characters".td,"".td,"Char".td("code"),"'d → 'd".td("code"),"" 59 | puts "","strings".td,"".td,"Str".td("code"),'"hi" → "hi"'.td("code"),"" 60 | puts "","floats".td,"".td,"Num".td("code"), 61 | '',"" 62 | puts "","empty list".td,"".td,"[a]".td("code"),'() → []'.td("code"),"" 63 | 64 | OpsList.reject{|o| !(String===o) && o.name =~ /^implicit/ }.each{|o| 65 | o.ref 66 | } 67 | "debug".ref 68 | Commands.each{|str,data| 69 | desc,arg,impl = data 70 | puts "" 71 | puts str.td("code") 72 | puts "".td("code") 73 | puts (arg||"").td("code") 74 | puts desc.td 75 | puts "" 76 | } 77 | "misc".ref 78 | puts "" 79 | puts "S".td("code") 80 | puts "".td("code"),"Char".td("code"),"space".td 81 | puts "" 82 | puts "" 83 | puts "N".td("code") 84 | puts "".td("code"),"Char".td("code"),"newline".td 85 | puts "" 86 | puts "" 87 | puts "unset id".td 88 | puts "".td("code"),"Num".td("code") 89 | puts '' 90 | puts "" 91 | puts "" 92 | puts "unset id".td 93 | puts "".td("code"),"Char".td("code") 94 | puts 'z → \'z'.td("code") 95 | puts "" 96 | puts "" 97 | puts "unset id".td 98 | puts "".td("code"),"Str".td("code") 99 | puts 'Hello → "Hello"'.td("code") 100 | puts "" 101 | puts "
4.50 → 4.52e3 -> 2000.0
Roman NumeralsMIX → 1009
102 |
*a coerces 103 |
version: #$version 104 | " -------------------------------------------------------------------------------- /web/site/quickref.css: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: collapse; 3 | } 4 | 5 | table, th, td { 6 | border: 1px solid black; 7 | padding: 1px 8px; 8 | } 9 | 10 | .code { 11 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; 12 | font-size: 12px; 13 | color: var(--color-text-primary); 14 | word-wrap: normal; 15 | white-space: pre-wrap; 16 | } 17 | 18 | .center{ 19 | text-align: center; 20 | } 21 | 22 | table.subtable { 23 | width: 100%; 24 | } 25 | 26 | table.subtable, table.subtable th, table.subtable td { 27 | table-layout: auto; 28 | border: 0px; 29 | margin: 0px; 30 | padding: 0px 3px; 31 | text-align: center; 32 | } 33 | 34 | table tr th { 35 | cursor: pointer; 36 | } 37 | 38 | .left { 39 | float: left; 40 | } 41 | .right { 42 | float: right; 43 | } -------------------------------------------------------------------------------- /web/site/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | max-width: 900px; 3 | margin: 0 auto !important; 4 | color: #C8D1D9; 5 | background-color: #0E1016; 6 | line-height: 1.5; 7 | font-family: Verdana; 8 | } 9 | 10 | #content { 11 | padding-left: 10px; 12 | padding-right: 10px; 13 | } 14 | 15 | .navbar { 16 | font-weight: bold; 17 | width: 100%; 18 | /* font-size: 2.0vw; */ 19 | /* font-size: 100%px; */ 20 | overflow: hidden; 21 | background-color: #343940; 22 | } 23 | 24 | .navbar a { 25 | float: left; /* todo that fix for chrome only works if this is set to bottom but then it ignores the other properties here */ 26 | padding: 6px; 27 | margin: 2px; 28 | text-decoration: none; 29 | border-radius: 4px; 30 | } 31 | 32 | .navbar a.active { 33 | background-color: #4E93E2; 34 | color: #343940; 35 | } 36 | 37 | .navbar a:hover { 38 | background-color: #C8D1D9; 39 | color: #343940; 40 | text-decoration: none; 41 | } 42 | 43 | #atlas { 44 | /* font-size: 20px; */ 45 | } 46 | 47 | a { 48 | color: #4E93E2; 49 | text-decoration: none; 50 | } 51 | 52 | a:hover { 53 | text-decoration: underline; 54 | } 55 | 56 | 57 | hr { 58 | border: 1px solid #C8D1D9; 59 | } 60 | 61 | /* inline code */ 62 | code { 63 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; 64 | padding: 0.2em 0.4em; 65 | margin: 0; 66 | white-space: pre-wrap; 67 | border:none; 68 | background-color: #343940; 69 | border-radius: 4px; 70 | } 71 | /* program blocks */ 72 | div.prog code { 73 | display: block; 74 | background-color: #171A22; 75 | padding: 20px; 76 | line-height: 1.2; 77 | } 78 | --------------------------------------------------------------------------------