├── .gitmodules ├── GUIDE.md ├── LICENSE ├── OHJEET.md ├── README.md ├── build.gradle ├── examples ├── http_server.röd ├── json.röd ├── kuha.röd ├── kuvahaku.röd ├── simple_compiler.röd ├── telegram.röd ├── tietueet.röd └── walker.röd ├── settings.gradle ├── src └── org │ └── kaivos │ └── röda │ ├── Builtins.java │ ├── IOUtils.java │ ├── Interpreter.java │ ├── JSON.java │ ├── Parser.java │ ├── Röda.java │ ├── RödaStream.java │ ├── RödaValue.java │ ├── Timer.java │ ├── commands │ ├── AssignGlobalPopulator.java │ ├── BtosAndStobPopulator.java │ ├── CasePopulator.java │ ├── CdAndPwdPopulator.java │ ├── ChrAndOrdPopulator.java │ ├── CurrentTimePopulator.java │ ├── EnumPopulator.java │ ├── ErrorPopulator.java │ ├── ErrprintPopulator.java │ ├── ExecPopulator.java │ ├── FilePopulator.java │ ├── FilterPopulator.java │ ├── GetenvPopulator.java │ ├── HeadAndTailPopulator.java │ ├── IdentityPopulator.java │ ├── ImportPopulator.java │ ├── IndexOfPopulator.java │ ├── InterleavePopulator.java │ ├── JsonPopulator.java │ ├── KeysPopulator.java │ ├── MatchPopulator.java │ ├── MathPopulator.java │ ├── NamePopulator.java │ ├── ParseNumPopulator.java │ ├── PushAndPullPopulator.java │ ├── RandomPopulator.java │ ├── ReadAndWritePopulator.java │ ├── ReducePopulator.java │ ├── ReplacePopulator.java │ ├── SearchPopulator.java │ ├── SeqPopulator.java │ ├── ServerPopulator.java │ ├── ShiftPopulator.java │ ├── SortPopulator.java │ ├── SplitPopulator.java │ ├── StreamPopulator.java │ ├── StrsizePopulator.java │ ├── ThreadPopulator.java │ ├── TrueAndFalsePopulator.java │ ├── UndefinePopulator.java │ ├── UniqPopulator.java │ └── WcatPopulator.java │ ├── runtime │ ├── Datatype.java │ ├── Function.java │ └── Record.java │ └── type │ ├── RödaBoolean.java │ ├── RödaFloating.java │ ├── RödaFunction.java │ ├── RödaInteger.java │ ├── RödaList.java │ ├── RödaMap.java │ ├── RödaNamespace.java │ ├── RödaNativeFunction.java │ ├── RödaRecordInstance.java │ ├── RödaReference.java │ └── RödaString.java └── test └── org └── kaivos └── röda └── test ├── LexerTest.java └── RödaTest.java /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nept"] 2 | path = nept 3 | url = https://github.com/fergusq/nept.git 4 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | # A guide to Röda programming 2 | 3 | ## The basics 4 | 5 | ### Hello world -example 6 | 7 | ```sh 8 | main { 9 | print "Hello world!" 10 | } 11 | ``` 12 | 13 | Every Röda program consists of a list of function declarations. 14 | When the program is run, the interpreter executes the `main` function (defined by lines 1 and 3). 15 | At line 2 there is the body of the function that happens to be a single function call. 16 | 17 | For the rest of this guide I will omit the signature of the main program. 18 | 19 | ### Commands and variables 20 | 21 | In Röda statements are also called _commands_. We have already seen a function call, which is the most used command type, 22 | but there are also many others like `if`, `while` and `for`. 23 | 24 | The maybe second most used command is variable assignment. Below is a basic example of that. 25 | 26 | ```sh 27 | name := "Caroline" 28 | age := 31 29 | print name, ", ", age, " years" 30 | age = 32 31 | print name, ", ", age, " years" 32 | ``` 33 | 34 | Here we declare two variables, `name` and `age` and use them to print an introductive text of a person: `Caroline, 31 years`. 35 | Then we change the age variable and print to same text again. In Röda `:=` is used to create a new variable and `=` to edit an old. 36 | 37 | Using variables and a while loop we can print the first even numbers: 38 | 39 | ```sh 40 | i := 0 41 | while [ i < 10 ] do 42 | print i*2 43 | i++ 44 | done 45 | ``` 46 | 47 | Like in Bourne shell, condition is a command. It can be an arithmetic command `[]` or a normal function call like below. 48 | `fileExists` is used to check if the file exists. 49 | 50 | ```sh 51 | if fileExists "log.txt" do 52 | {} | exec "rm", "log.txt" | {} 53 | done 54 | ``` 55 | 56 | Unlike Bourne shell, `if` statements have `do` and `done`, not `then` and `fi`. 57 | 58 | ## Prime generator explained 59 | 60 | In this section we're going to review some syntactic features of Röda which may be confusing to beginners. 61 | In README.md, I used this prime generator as an example: 62 | 63 | ```sh 64 | primes := [2] 65 | seq 3, 10000 | { primes += i if [ i % p != 0 ] for p in primes } for i 66 | print p for p in primes 67 | ``` 68 | 69 | What does that program do? If you execute it, it will pause for a moment and then print the first prime numbers up to 9973. 70 | It makes a heavy use of a syntactic sugar called _suffix control structures_. They are a shorter way to write loops and conditionals. 71 | 72 | Let's look at the last line first. It takes the `primes` array and prints its content, each number to its own line. 73 | It consists of the statement, `print p`, and the suffix, `for p in primes`. The only difference to a normal loop is that the 74 | body is written _before_ the loop declaration. 75 | 76 | But how does the second line work? I have written the loops and conditionals without suffix syntax below: 77 | 78 | ```sh 79 | seq 3, 10000 | for i do 80 | if for p in primes do [ i % p != 0 ] done do 81 | primes += i 82 | done 83 | done 84 | ``` 85 | 86 | As you can see, it is a basic `seq for`-loop that iterates numbers from 3 to 10000. 87 | It then uses an if statement to determinate if the number is a prime, but how can there be a loop inside the condition? 88 | It happens that in Röda conditions are just statements and the value of the condition is just taken from the output stream of the statement. 89 | In this case the loop pushes more than one boolean to the stream and the body of the condition is executed when _all of them_ are true. 90 | The condition can in other words be read _a number is a prime if none of the previous primes divide it_. 91 | 92 | So are these suffix commands really required? It seems that one must read the code backwards to understand them! 93 | Still, they can be handy sometimes: they provide a more natural syntax to do many things like list comprehension and looped conditions (see below). 94 | It is advisable not to nest these more than two levels, though. 95 | 96 | ## Syntactic features 97 | 98 | ### Suffix commands 99 | 100 | The suffix syntax is a generalization of many other features like list comprehension, `map`- and `filter`-functions, looped conditionals, etc. 101 | 102 | #### List comprehension 103 | 104 | ```sh 105 | new_list := [statement for variable in list if statement] 106 | ``` 107 | 108 | Examples: 109 | ```sh 110 | names := [get_name(client) for client in clients] 111 | adults := [push(client) for client in clients if [ client.age >= 18 ]] 112 | pwdir := pwd() 113 | directories := [push(pwdir.."/"..f) for f in files if isDirectory(f)] 114 | ``` 115 | 116 | #### Mapping and filtering 117 | 118 | `for` can also used in pipes. `if` and `unless` are right-associative, so braces `{}` should be used around them. 119 | 120 | ```ruby 121 | ["Pictures/", "Music/"] | bufferedExec("ls", dir) for dir | { [ f ] for f unless isDirectory(f) } | for f do 122 | print f.." "..mimeType(f) 123 | done 124 | ``` 125 | 126 | #### Looped conditionals 127 | 128 | ```sh 129 | if [ condition ] for element in list do 130 | 131 | done 132 | ``` 133 | 134 | Ensures that the condition is true for all elements in list. 135 | 136 | ```sh 137 | if isDirectory(f) for f in [ls(".")] do 138 | print "All files are directories!" 139 | done 140 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Röda 2 | 3 | ## English 4 | 5 | Röda is a homemade scripting language inspired by Bourne shell, Ruby and others. While not being a real shell language, Röda 6 | still makes an extensive use of concurrency and streams (pipes). For more documentation, see GUIDE.md. 7 | The standard library reference and other information is also found at the authors [web page](http://iikka.kapsi.fi/roda/). 8 | 9 | ### Building 10 | 11 | Using Gradle: 12 | 13 | ```sh 14 | $ git clone --recursive https://github.com/fergusq/roda.git 15 | $ cd roda 16 | roda $ gradle fatJar 17 | ``` 18 | 19 | ### Example 20 | 21 | Real life examples: 22 | 23 | * Small scripts at my [gist page](https://gist.github.com/fergusq) - I use Röda very often to create small scripts that could have been written in sh, AWK or Perl. 24 | * [Static site generator](https://github.com/fergusq/plan) 25 | * [Mafia game master service](https://github.com/fergusq/mafia) 26 | * [Lyrics video generator](https://github.com/fergusq/videolyrics) 27 | 28 | Prime generator: 29 | 30 | ```sh 31 | #!/usr/bin/röda 32 | 33 | main { 34 | primes := [2] 35 | seq 3, 10000 | { primes += i if [ i % p != 0 ] for p in primes } for i 36 | print p for p in primes 37 | } 38 | ``` 39 | 40 | HTTP server: 41 | 42 | ```sh 43 | #!/usr/bin/röda 44 | 45 | { 46 | http := require("http_server") 47 | } 48 | 49 | main { 50 | server := new http.HttpServer(8080) 51 | server.controllers["/"] = http.controller({ |request| 52 | request.send "200 OK", "Hello world!Hello world!" 53 | }) 54 | while true; do 55 | server.update 56 | done 57 | } 58 | ``` 59 | 60 | ## Suomeksi 61 | 62 | Röda on uusi ohjelmointikieleni, joka on saanut vaikutteensa lähinnä Bourne shellistä. 63 | Dokumentaatio on tällä hetkellä saatavilla suomeksi tiedostossa OHJEET.md. 64 | 65 | ## LICENSE 66 | 67 | Röda Interpreter 68 | Copyright (C) 2017 Iikka Hauhio 69 | 70 | This program is free software: you can redistribute it and/or modify 71 | it under the terms of the GNU General Public License as published by 72 | the Free Software Foundation, either version 3 of the License, or 73 | (at your option) any later version. 74 | 75 | This program is distributed in the hope that it will be useful, 76 | but WITHOUT ANY WARRANTY; without even the implied warranty of 77 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 78 | GNU General Public License for more details. 79 | 80 | You should have received a copy of the GNU General Public License 81 | along with this program. If not, see . 82 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | targetCompatibility = 1.8 4 | sourceCompatibility = 1.8 5 | version = '0.14-alpha' 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | dependencies { 11 | compile project(':nept') 12 | compile 'org.jline:jline:3.7.1' 13 | testCompile 'junit:junit:4.12' 14 | } 15 | 16 | sourceSets.main.java.srcDirs = ['src'] 17 | sourceSets.test.java.srcDirs = ['test'] 18 | 19 | task fatJar(type: Jar) { 20 | manifest { 21 | attributes "Main-Class": "org.kaivos.röda.Röda" 22 | } 23 | baseName = project.name + "-all" 24 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 25 | with jar 26 | } 27 | -------------------------------------------------------------------------------- /examples/http_server.röd: -------------------------------------------------------------------------------- 1 | record HttpServer(port_value) { 2 | port : number = port_value 3 | socket : Server = server(port_value) 4 | controllers : map<> = new map<> 5 | 6 | /* Luku */ 7 | 8 | function read connection, &request { 9 | request := new Request 10 | request.server = self 11 | request.connection = connection 12 | connection.readLine(line) 13 | things := [split(line)] 14 | if [ #things >= 3 ]; do 15 | request.command = things[0] 16 | request.path = things[1] 17 | request.protocol = things[2] 18 | request.protocol ~= "\r?\n$", "" 19 | else 20 | /* kysely on väärän muotoinen */ 21 | /* TODO: virheenkäsittely */ 22 | request.valid = FALSE 23 | connection.close() 24 | return 25 | done 26 | request.path ~= "^([a-z0-9]+://)?[^/]+", "" 27 | length := 0 28 | request.headers = new map<> 29 | request.as_text = "" 30 | while [ not (line =~ "\r?\n") ]; do 31 | request.as_text .= line 32 | line = connection.readLine() 33 | if [ line =~ "[A-Za-z\\-]+:[ \t]*.*\r?\n" ]; do 34 | header_name := line 35 | header_name ~= ":[ \t]*.*\r?\n$", "" 36 | header_value := line 37 | header_value ~= "^[A-Za-z\\-]+:[ \t]*", "", "\r?\n$", "" 38 | request.headers[header_name] = header_value 39 | done 40 | if [ line =~ "Content-Length: .*\r?\n" ]; do 41 | cl := line 42 | cl ~= "^Content-Length: ", "", "\r?\n$", "" 43 | length = parseInteger(cl) 44 | done 45 | done 46 | request.content = "" 47 | if [ length > 0 ]; do 48 | connection.readString(length, content) 49 | request.content = content 50 | done 51 | request.form = new map<> 52 | if push(request.headers["Content-Type"]?); do 53 | if [ request.headers["Content-Type"] =~ "application/x-www-form-urlencoded(;.*)?" ]; do 54 | for param in [split(request.content, sep="&")] do 55 | kv := [split(param, sep="=")] 56 | if [ #kv = 2 ]; do 57 | key := kv[0] 58 | val := kv[1] 59 | val ~= "\\+", " " 60 | code := new list<> 61 | i := 0 62 | while [ i < #val ] do 63 | if [ val[i:i+1] = "%" and i < #val-2 ]; do 64 | code += parseInteger(val[i+1:i+3], radix=16) 65 | i += 3 66 | else 67 | code .= stringToBytes(val[i:i+1]) 68 | i += 1 69 | done 70 | done 71 | request.form[key] = bytesToString(code) 72 | done 73 | done 74 | done 75 | done 76 | } 77 | 78 | /* Lähetys */ 79 | 80 | function sendContinue connection { 81 | response := "HTTP/1.1 100 Continue\r\n\r\n" 82 | connection.write(response) 83 | } 84 | 85 | /* Ohjaus */ 86 | 87 | function handle connection { 88 | while true; do 89 | self.read(connection, request) 90 | if push(request.valid); do 91 | try do 92 | request.handle() 93 | catch e 94 | date := "["..[{}()|bufferedExec("date", "+%d.%m.%Y/%H:%M")][0].."] " 95 | errprint(date, (typeof e).name, ": ", e.message, "\n") 96 | try do 97 | request.send500(e) 98 | catch e2 99 | date = "["..[{}()|bufferedExec("date", "+%d.%m.%Y/%H:%M")][0].."] " 100 | errprint(date, (typeof e2).name, ": ", e2.message, "\n") 101 | done 102 | done 103 | else 104 | break 105 | done 106 | done 107 | } 108 | 109 | function update { 110 | connection := self.socket.accept() 111 | date := "["..[{}()|bufferedExec("date", "+%d.%m.%Y/%H:%M")][0].."]" 112 | print(date.." Connection from "..connection.hostname.." ("..connection.ip..":"..connection.port..").") 113 | handler := thread({ self.handle(connection) }) 114 | handler.start() 115 | } 116 | } 117 | 118 | record Request { 119 | valid : boolean = TRUE 120 | command : string 121 | path : string 122 | protocol : string 123 | headers : map<> 124 | as_text : string 125 | content : string 126 | form : map<> 127 | server : HttpServer 128 | connection : Socket 129 | 130 | function send status, data, mime="text/html" { 131 | new Response(self).send status, data, mime=mime 132 | } 133 | 134 | function sendFile mime, name { 135 | new Response(self).sendFile mime, name 136 | } 137 | 138 | function redirect target { 139 | new Response(self).redirect target 140 | } 141 | 142 | function send403 { 143 | new Response(self).send403 144 | } 145 | 146 | function send404 { 147 | new Response(self).send404 148 | } 149 | 150 | function send412 { 151 | new Response(self).send412 152 | } 153 | 154 | function send500 err { 155 | new Response(self).send500 err 156 | } 157 | 158 | function handle { 159 | if push(self.headers["If-Unmodified-Since"]?); do 160 | self.send412() 161 | return 162 | done 163 | path := match("(/[^/]*)(/[^/]*)*", self.path) 164 | if [ #path = 0 ]; do path = ["/", "/", ""]; done 165 | if push self.server.controllers[path[1]]?; do 166 | ctrl := self.server.controllers[path[1]] 167 | type := typeof ctrl 168 | for f in type.fields do 169 | for a in f.annotations do 170 | if [ #a = 2 and a[0] = "HANDLE" and self.path =~ a[1] ]; do 171 | handler := f.get(ctrl) 172 | handler(self) 173 | return 174 | done 175 | done 176 | done 177 | ctrl.handle(self) 178 | else 179 | self.send404() 180 | done 181 | } 182 | } 183 | 184 | record Response(request) { 185 | request : Request = request 186 | connection : Socket = request.connection 187 | protocol : string = request.protocol 188 | server : HttpServer = request.server 189 | cookies : list<> = new list<> 190 | cookie_values : map<> = new map<> 191 | 192 | function contentHeaders type, length { 193 | content_headers := "" 194 | if [ length != 0 ]; do 195 | content_headers = "Content-Type: "..type.."\r\n" 196 | content_headers .= "Content-Length: "..length.."\r\n" 197 | content_headers .= "Accept-Ranges: bytes\r\n" 198 | done 199 | return content_headers 200 | } 201 | 202 | function cookieHeaders { 203 | return [ 204 | push("Set-Cookie: " .. name .. "=" .. self.cookie_values[name] .. "\r\n") for name in self.cookies 205 | ] & "" 206 | } 207 | 208 | function createHeaders content_headers, status { 209 | headers := "" 210 | if [ self.protocol != "HTTP/1.0" ]; do 211 | headers = "HTTP/1.1 "..status.."\r\n" 212 | headers .= "Server: http_server.roed\r\n" 213 | headers .= "Date: " 214 | env := new map 215 | env["LC_TIME"] = "c.UTF-8" 216 | headers .= [{}()|exec("date", "-u", "+%a, %b %d %Y %H:%M:%S GMT", env=env)][:-1]&"".."\r\n" 217 | headers .= content_headers 218 | headers .= self.cookieHeaders() 219 | headers .= "Connection: keep-alive\r\n" 220 | headers .= "\r\n" 221 | else 222 | headers = "HTTP/1.0 "..status.."\r\n" 223 | headers .= "Server: httpd.roed\r\n" 224 | headers .= content_headers 225 | headers .= self.cookieHeaders() 226 | headers .= "\r\n" 227 | done 228 | return headers 229 | } 230 | 231 | function send status, data, mime="text/html" { 232 | response := self.createHeaders(self.contentHeaders(mime.."; charset=utf-8", strsize(data)), status) 233 | if [ self.request.command != "HEAD" ]; do 234 | response .= data 235 | done 236 | self.connection.writeStrings(response) 237 | } 238 | 239 | function sendFile mime, name { 240 | response := self.createHeaders(self.contentHeaders(mime, fileLength(name)), "200 OK") 241 | self.connection.writeStrings(response) 242 | if [ self.request.command != "HEAD" ]; do 243 | self.connection.writeFile(name) 244 | done 245 | } 246 | 247 | function redirect target { 248 | if [ self.protocol = "HTTP/1.0" ]; do 249 | push("302 Found") 250 | else 251 | push("303 See Other") 252 | done | pull(status_code) 253 | data := " 254 | "..status_code.." 255 |

"..status_code.."

256 |

The document is here.

257 | 258 | 259 | " 260 | headers := self.contentHeaders("text/html; charset=utf-8", strsize(data)).."Location: "..target.."\r\n" 261 | response := self.createHeaders(headers, status_code) 262 | if [ self.request.command != "HEAD" ]; do 263 | response .= data 264 | done 265 | self.connection.writeStrings(response) 266 | } 267 | 268 | function send403 { 269 | self.send("403 Forbidden", " 270 | 403 Forbidden 271 |

403 Forbidden

272 |
273 |

http_server.röd

274 | ") 275 | } 276 | 277 | function send404 { 278 | self.send("404 Not found", " 279 | 404 Not found 280 |

404 Not found

The resource you were looking for doesn't exist.

281 |
282 |

http_server.röd

283 | ") 284 | } 285 | 286 | function send412 { 287 | self.send("412 Precondition failed", "") 288 | } 289 | 290 | function send500 err { 291 | self.send("500 Internal server error", " 292 | 500 Internal server error 293 |

500 Internal server error

Diagnostics:

294 |
"..err.message.."
295 |
"..[err.stack() | replace("<", "<")]&"\n".."
296 |
297 |

http_server.röd

298 | ") 299 | } 300 | 301 | function setCookie name, v { 302 | cookies += name unless [ name in cookies ] 303 | cookie_values[name] = v 304 | } 305 | } 306 | 307 | function @handle path { 308 | return ["HANDLE", path] 309 | } 310 | 311 | record Controller { 312 | handle : function 313 | } 314 | 315 | controller handler { 316 | ctrl := new Controller 317 | ctrl.handle = handler 318 | push ctrl 319 | } 320 | -------------------------------------------------------------------------------- /examples/json.röd: -------------------------------------------------------------------------------- 1 | function toRödaObj(json_tree) { 2 | if [ json_tree[0] = "LIST" ] do 3 | return [toRödaObj(elem) for elem in json_tree[1]] 4 | done 5 | if [ json_tree[0] = "MAP" ] do 6 | objmap := new map 7 | for elem in json_tree[1] do 8 | objmap[elem[0]] = toRödaObj(elem[1]) 9 | done 10 | return objmap 11 | done 12 | if [ json_tree[0] = "STRING" or json_tree[0] = "NUMBER" ] do 13 | return json_tree[1] 14 | done 15 | if [ json_tree[0] = "BOOLEAN" ] do 16 | return TRUE if [ json_tree[1] = "true" ] 17 | return FALSE 18 | done 19 | if [ json_tree[0] = "NULL" ] do 20 | error("null is not supported") 21 | done 22 | } 23 | 24 | function toRecord(class, json_obj) { 25 | return [ toRecord(element, class) for element in json_obj ] if [ json_obj is list ] 26 | instance := class.newInstance() 27 | for field in class.fields do 28 | for annotation in field.annotations do 29 | if [ annotation is list and #annotation > 1 and annotation[0] = "JSON_FIELD" ] do 30 | if [ not json_obj[field.name]? ] do 31 | error("illegal json object, key " .. field.name .. " not found") 32 | done 33 | field.set(instance, annotation[1](json_obj[field.name])) 34 | done 35 | done 36 | done 37 | return instance 38 | } 39 | -------------------------------------------------------------------------------- /examples/kuha.röd: -------------------------------------------------------------------------------- 1 | hae_kuva_ja_tee_koodi sana, mones, montako { 2 | hae_kuva sana, mones 3 | x_koordinaatti := mones*500//montako 4 | leveys := 500//montako 5 | push "" 6 | } 7 | 8 | /* tekee svg-kuvan */ 9 | tee_pilakuva nettikuvat, ylä, ala, tiedosto { 10 | push " 11 | 15 | 16 | 17 | "..nettikuvat.." 18 | "..ylä.." 19 | "..ala.." 20 | " | writeStrings "kuha.svg" 21 | {} | exec "rsvg-convert", "-o", tiedosto, "kuha.svg" 22 | } 23 | 24 | /* ohjaa kuvan luomista: käsittelee viestin, lataa kuvat, tekee kuhakuvan ja lähettää sen */ 25 | käsittele_kuhaviesti botti, ketju, viesti { 26 | /* esikäsitellään viesti */ 27 | viesti ~= "kunhan", "kuha", "Kunhan", "Kuha", "&", "&", "<", "<" 28 | 29 | push "käsitellään viesti '", viesti, "'\n" 30 | botti.send_action ketju, "upload_photo" 31 | 32 | /* jaetaan viesti sanoihin */ 33 | sanat := [split(viesti)] 34 | montako := #sanat 35 | 36 | /* erillinen lista niistä sanoista, joista otetaan kuvia, oletuksena kaikki sanat */ 37 | kuvasanat := sanat 38 | kuvamontako := montako 39 | 40 | /* jos kuvia tulisi liikaa, valitaan satunnaisesti vain osa */ 41 | if [ kuvamontako > 10 ]; do 42 | kuvasanat = [] 43 | kuvamontako = 5 44 | 45 | /* hypitään satunnaisesti joidenkin kuvien yli */ 46 | i := 0 47 | while [ #kuvasanat < 5 ]; do 48 | kuvasanat += sanat[i] 49 | i ++ 50 | if random; do 51 | i ++ 52 | done 53 | done 54 | done 55 | 56 | /* haetaan netistä kivoja kuvia */ 57 | nettikuvat := "" 58 | laskuri := 0 59 | for sana in kuvasanat; do 60 | nettikuvat .= [hae_kuva_ja_tee_koodi(sana, laskuri, kuvamontako)]&" " 61 | laskuri ++ 62 | done 63 | 64 | /* jaetaan sanat kahtia ja tehdään kuva */ 65 | puoliväli := montako//2 66 | tee_pilakuva nettikuvat, sanat[:puoliväli]&" ", sanat[puoliväli:]&" ", "kuhaonmeemi.png" 67 | 68 | /* lähetetään kuva ketjuun */ 69 | botti.send_photo ketju, "kuhaonmeemi.png" 70 | } 71 | 72 | { 73 | import "kuvahaku.röd" 74 | } 75 | 76 | /* pääfunktio */ 77 | main { 78 | import "telegram.röd" 79 | token := [readLines("token.txt")][0] 80 | bot := tg_init(token) 81 | bot.on_message = { |ketju, teksti| 82 | if [ teksti =~ ".*\\b[Kk][Uu][Hh][Aa].*" ]; do 83 | käsittele_kuhaviesti bot, ketju, teksti 84 | done 85 | } 86 | print "Started." 87 | while true; do 88 | try do 89 | bot.update 90 | catch e 91 | print "VIRHE: "..e.message 92 | done 93 | done 94 | } 95 | 96 | -------------------------------------------------------------------------------- /examples/kuvahaku.röd: -------------------------------------------------------------------------------- 1 | /* hakee kuvan netistä */ 2 | hae_kuva sana, tiedosto { 3 | sana ~= " ", "+" 4 | 5 | /* asetukset */ 6 | user_agent := "Links (2.7; Linux 3.5.0-17-generic x86_64; GNU C 4.7.1; text)" 7 | hakukone := "http://images.google.com/images?q="..sana.."&lr=lang_fi&cr=countryFI" 8 | etsitty_url := "http://t[0-9]\\.gstatic.com/images\\?q=tbn:[a-zA-Z0-9_-]*" 9 | 10 | /* haetaan lista kuvista */ 11 | kuvat := [loadResourceLines(hakukone, ua=user_agent) | search(etsitty_url)] 12 | 13 | /* jos kuvia löytyi... */ 14 | if [ #kuvat > 0 ]; do 15 | /* valitaan ensimmäinen kuva */ 16 | kuva := kuvat[0] 17 | 18 | /* ladataan kuva */ 19 | saveResource(kuva, tiedosto) 20 | done 21 | } 22 | -------------------------------------------------------------------------------- /examples/simple_compiler.röd: -------------------------------------------------------------------------------- 1 | record Datatype(n) { 2 | name : string = n 3 | } 4 | 5 | accept keyword { 6 | pull(token) 7 | error("expected `"..keyword.."', got `"..token.."'") if [ token != keyword ] 8 | } 9 | 10 | expect type1, type2 { 11 | if [ type1.name != type2.name ] do 12 | error("type mismatch: can't convert " .. type1.name .. " to " .. type2.name) 13 | done 14 | } 15 | 16 | compilePrim(&type) { 17 | if [ peek() = "(" ] do 18 | accept("(") 19 | compileExpression(type) 20 | accept(")") 21 | return 22 | done 23 | if [ peek() = "-" ] do 24 | accept("-") 25 | print("PUSH 0") 26 | compilePrim(type) 27 | expect(type, new Datatype("integer")) 28 | print("SUB") 29 | return 30 | done 31 | if [ peek() =~ "[0-9]+" ] do 32 | print("PUSH " .. pull()) 33 | type := new Datatype("integer") 34 | return 35 | done 36 | if [ peek() = "$" ] do 37 | accept("$") 38 | varname := pull() 39 | if [ peek() = "=" ] do 40 | accept("=") 41 | compileExpression(type) 42 | expect(type, new Datatype("integer")) 43 | print("SETVAR " .. varname) 44 | else 45 | print("PUSHVAR " .. varname) 46 | type := new Datatype("integer") 47 | done 48 | return 49 | done 50 | accept("void") 51 | print("PUSH VOID") 52 | type := new Datatype("void") 53 | } 54 | 55 | compileTerm(&type) { 56 | compilePrim(type) 57 | while [ peek() =~ "\\*|/" ] do 58 | operator := pull() 59 | compilePrim(type) 60 | expect(type, new Datatype("integer")) 61 | print("MUL") if [ operator = "*" ] 62 | print("DIV") if [ operator = "/" ] 63 | done if [ type.name = "integer" ] 64 | } 65 | 66 | compileExpression(&type) { 67 | compileTerm(type) 68 | while [ peek() =~ "\\+|\\-" ] do 69 | operator := pull() 70 | compileTerm(type) 71 | expect(type, new Datatype("integer")) 72 | print("ADD") if [ operator = "+" ] 73 | print("SUB") if [ operator = "-" ] 74 | done if [ type.name = "integer" ] 75 | } 76 | 77 | { createGlobal "counter", 0 } 78 | newLabelNum { 79 | counter ++ 80 | return counter 81 | } 82 | 83 | compileIf { 84 | labelNum := newLabelNum() 85 | accept("if") 86 | accept("(") 87 | compileExpression(type) 88 | expect(type, new Datatype("integer")) 89 | accept(")") 90 | print("JNZ if"..labelNum.."_end") 91 | accept("{") 92 | compileBlock() 93 | accept("}") 94 | print("if"..labelNum.."_end:") 95 | } 96 | 97 | compileWhile { 98 | labelNum := newLabelNum() 99 | print("while"..labelNum.."_start:") 100 | accept("while") 101 | accept("(") 102 | compileExpression(type) 103 | expect(type, new Datatype("integer")) 104 | accept(")") 105 | print("JNZ while"..labelNum.."_end") 106 | accept("{") 107 | compileBlock() 108 | accept("}") 109 | print("JMP while"..labelNum.."_start") 110 | print("while"..labelNum.."_end:") 111 | } 112 | 113 | compilePrint { 114 | accept("print") 115 | compileExpression(type) 116 | accept(";") 117 | print("PRINT") 118 | } 119 | 120 | compileStatement { 121 | type := peek() 122 | if [ type = "if" ] do 123 | compileIf() 124 | return 125 | done 126 | if [ type = "while" ] do 127 | compileWhile() 128 | return 129 | done 130 | if [ type = "print" ] do 131 | compilePrint() 132 | return 133 | done 134 | compileExpression(type) 135 | accept(";") 136 | print("POP") 137 | } 138 | 139 | compileBlock { 140 | compileStatement() 141 | while [ peek() != "" and peek() != "}" ] do 142 | compileStatement() 143 | done 144 | } 145 | 146 | filter condition { 147 | push(item) for item if condition(item) 148 | } 149 | 150 | lex { 151 | split(sep="(?<=[^a-zA-Z0-9_])|(?=[^a-zA-Z0-9_])") | filter({ |token|; [ token != " " ] }) 152 | push("") 153 | } 154 | 155 | main code... { 156 | push(code&" ") | lex() | compileBlock() 157 | } 158 | -------------------------------------------------------------------------------- /examples/telegram.röd: -------------------------------------------------------------------------------- 1 | record TelegramBot { 2 | token : string 3 | offset : number = 0 4 | thread : Thread 5 | running : boolean = true() 6 | on_message : function = {|chat, message|;} 7 | 8 | function start { 9 | self.thread = thread({ 10 | while push self.running; do 11 | try self.update 12 | done 13 | }) 14 | self.thread.start 15 | } 16 | 17 | function stop { 18 | self.running = false() 19 | } 20 | 21 | function update { 22 | base_url := "https://api.telegram.org/bot"..self.token 23 | update_url = base_url.."/getUpdates?offset=" 24 | 25 | code := [loadResourceLines(update_url..self.offset)]&" " 26 | tree := json(code) 27 | json_search tree, ["result", { |updates| 28 | for update in updates[1]; do 29 | chat_id := 0 30 | msg_text := "" 31 | json_search update, ["update_id", { |id| 32 | if [ self.offset <= id[1] ]; do 33 | self.offset = id[1]+1 34 | done 35 | }], ["message", { |message| 36 | json_search message, ["chat", { |chat| 37 | json_search chat, ["id", { |id| 38 | chat_id = id[1] 39 | }] 40 | }], ["text", { |text| 41 | msg_text = text[1] 42 | }], ["caption", { |caption| 43 | msg_text = caption[1] 44 | }] 45 | }] 46 | self.on_message chat_id, msg_text 47 | done 48 | }] 49 | } 50 | 51 | function send_message chat, message, args... { 52 | base_url := "https://api.telegram.org/bot"..self.token 53 | message_url = base_url.."/sendMessage" 54 | {} | exec "curl", "--silent", message_url, "-F", "chat_id="..chat, "-F", "text="..message, *args | {} 55 | } 56 | 57 | function send_photo chat, file, args... { 58 | base_url := "https://api.telegram.org/bot"..self.token 59 | photo_url = base_url.."/sendPhoto" 60 | {} | exec "curl", "--silent", photo_url, "-F", "chat_id="..chat, "-F", "photo=@"..file, *args | {} 61 | } 62 | 63 | function send_action chat, action { 64 | base_url := "https://api.telegram.org/bot"..self.token 65 | action_url = base_url.."/sendChatAction" 66 | {} | exec "curl", "--silent", action_url, "-F", "chat_id="..chat, "-F", "action="..action | {} 67 | } 68 | } 69 | 70 | json_search tree, queries... { 71 | for node in tree[1]; do 72 | for query in queries; do 73 | if [ node[0] = query[0] ]; do 74 | query[1] node[1] 75 | done 76 | done 77 | done 78 | } 79 | 80 | tg_init token { 81 | bot := new TelegramBot 82 | bot.token = token 83 | push bot 84 | } 85 | -------------------------------------------------------------------------------- /examples/tietueet.röd: -------------------------------------------------------------------------------- 1 | record Taulu<> { 2 | rivit : list<> = new list<> 3 | laskuri : number = 0 4 | function lisää_rivi rivi { 5 | rivi.id = self.laskuri; 6 | self.laskuri ++; 7 | self.rivit += rivi; 8 | } 9 | } 10 | 11 | record Rivi { 12 | id : number 13 | } 14 | 15 | record Asiakas : Rivi { 16 | nimi : string 17 | osoite : string 18 | } 19 | 20 | function asiakas nimi osoite { 21 | a := new Asiakas; 22 | a.nimi = nimi; 23 | a.osoite = osoite; 24 | push a; 25 | } 26 | 27 | main { 28 | asiakkaat := new Taulu<>; 29 | asiakkaat.lisää_rivi asiakas("Teppo", "Mäenpääntie 2"); 30 | for asiakas in asiakkaat.rivit; do 31 | print asiakas.nimi, " ", asiakas.osoite; 32 | done; 33 | } 34 | -------------------------------------------------------------------------------- /examples/walker.röd: -------------------------------------------------------------------------------- 1 | record Optional<> { 2 | present : boolean 3 | val : T 4 | 5 | function ifPresent(f) { 6 | f(self.val) if [ self.present ] 7 | } 8 | } 9 | 10 | function optionalOf<>(t : T) { 11 | optional := new Optional<> 12 | optional.val = t 13 | optional.present = TRUE 14 | return optional 15 | } 16 | 17 | function emptyOptional<>() { 18 | optional := new Optional<> 19 | optional.present = FALSE 20 | return optional 21 | } 22 | 23 | record Tree<>(left, right, val) { 24 | left : Optional<< Tree<> >> = left 25 | right : Optional<< Tree<> >> = right 26 | val : T = val 27 | } 28 | 29 | function createTree<>(left, right, val) { 30 | return new Tree<>(optionalOf<< Tree<> >>(left), optionalOf<< Tree<> >>(right), val) 31 | } 32 | 33 | function createLeaf<>(val) { 34 | return new Tree<>(emptyOptional<< Tree<> >>(), emptyOptional<< Tree<> >>(), val) 35 | } 36 | 37 | function walk(t) { 38 | t.left.ifPresent(walk) 39 | push t.val 40 | t.right.ifPresent(walk) 41 | } 42 | 43 | function same(t1, t2) { 44 | return [walk(t1)] = [walk(t2)] 45 | } 46 | 47 | main { 48 | nl := { |i|; createLeaf<>(i) } 49 | nt := { |l, i, r|; createTree<>(l, r, i) } 50 | t1 := nt(nt(nl(1), 2, nl(3)), 4, nl(5)) 51 | t2 := nt(nl(1), 2, nt(nl(3), 4, nl(5))) 52 | print same(t1, t2) 53 | } 54 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':nept' -------------------------------------------------------------------------------- /src/org/kaivos/röda/IOUtils.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda; 2 | 3 | import java.lang.Iterable; 4 | import java.util.Iterator; 5 | 6 | import java.io.File; 7 | import java.io.BufferedReader; 8 | import java.io.FileReader; 9 | import java.io.InputStreamReader; 10 | import java.io.InputStream; 11 | import java.io.IOException; 12 | import java.io.FileNotFoundException; 13 | 14 | public final class IOUtils { 15 | private IOUtils() {} 16 | 17 | public static File getMaybeRelativeFile(File pwd, String name) { 18 | if (name.startsWith("/")) { // tee tästä yhteensopiva outojen käyttöjärjestelmien kanssa 19 | return new File(name); 20 | } else if (name.startsWith("~")) { 21 | return new File(System.getenv("HOME"), name.replaceAll("~/?", "")); 22 | } else { 23 | return new File(pwd, name); 24 | } 25 | } 26 | 27 | public static final ClosableIterable fileIterator(File file) { 28 | try { 29 | BufferedReader in = new BufferedReader(new FileReader(file)); 30 | return lineIterator(in); 31 | } catch (FileNotFoundException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | public static final ClosableIterable fileIterator(String file) { 37 | try { 38 | BufferedReader in = new BufferedReader(new FileReader(file)); 39 | return lineIterator(in); 40 | } catch (FileNotFoundException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | public static final ClosableIterable streamLineIterator(InputStream stream) { 46 | BufferedReader in = new BufferedReader(new InputStreamReader(stream)); 47 | return lineIterator(in); 48 | } 49 | 50 | public static final ClosableIterable streamCharacterIterator(InputStream stream) { 51 | BufferedReader in = new BufferedReader(new InputStreamReader(stream)); 52 | return characterIterator(in); 53 | } 54 | 55 | public static final ClosableIterable lineIterator(BufferedReader r) { 56 | return new ClosableIterable() { 57 | @Override 58 | public Iterator iterator() { 59 | return new Iterator() { 60 | String buffer; 61 | { 62 | updateBuffer(); 63 | } 64 | 65 | private void updateBuffer() { 66 | try { 67 | buffer = r.readLine(); 68 | if (buffer == null) r.close(); 69 | } catch (IOException e) { 70 | throw new RuntimeException(e); 71 | } 72 | } 73 | 74 | @Override public boolean hasNext() { 75 | return buffer != null; 76 | } 77 | 78 | @Override public String next() { 79 | String tmp = buffer; 80 | updateBuffer(); 81 | return tmp; 82 | } 83 | }; 84 | } 85 | @Override 86 | public void close() throws IOException { 87 | r.close(); 88 | } 89 | }; 90 | } 91 | 92 | public static final ClosableIterable characterIterator(BufferedReader r) { 93 | return new ClosableIterable() { 94 | @Override 95 | public Iterator iterator() { 96 | return new Iterator() { 97 | int buffer; 98 | { 99 | updateBuffer(); 100 | } 101 | 102 | private void updateBuffer() { 103 | try { 104 | buffer = r.read(); 105 | if (buffer == -1) r.close(); 106 | } catch (IOException e) { 107 | throw new RuntimeException(e); 108 | } 109 | } 110 | 111 | @Override public boolean hasNext() { 112 | return buffer != -1; 113 | } 114 | 115 | @Override public Character next() { 116 | char tmp = (char) buffer; 117 | updateBuffer(); 118 | return tmp; 119 | } 120 | }; 121 | } 122 | @Override 123 | public void close() throws IOException { 124 | r.close(); 125 | } 126 | }; 127 | } 128 | 129 | public interface ClosableIterable extends Iterable, AutoCloseable { 130 | @Override 131 | public void close() throws IOException; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/JSON.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.Stack; 8 | import java.util.Iterator; 9 | import java.util.Collections; 10 | import java.util.NoSuchElementException; 11 | 12 | import java.util.regex.Pattern; 13 | 14 | import org.kaivos.nept.parser.TokenScanner; 15 | import org.kaivos.nept.parser.TokenList; 16 | import org.kaivos.nept.parser.ParsingException; 17 | 18 | public class JSON { 19 | private JSON() {} 20 | 21 | private static final String NUMBER_REGEX = "-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE](\\+|-)?[0-9]+)?"; 22 | 23 | public static final TokenScanner t = new TokenScanner() 24 | .addOperators("[]{},:") 25 | .addOperatorRule("true") 26 | .addOperatorRule("false") 27 | .addOperatorRule("null") 28 | .addPatternRule(Pattern.compile("^"+NUMBER_REGEX), '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9') 29 | .separateIdentifiersAndPunctuation(false) 30 | .addStringRule('"','"','\\') 31 | .addEscapeCode('\\', "\\") 32 | .addEscapeCode('/', "/") 33 | .addEscapeCode('n', "\n") 34 | .addEscapeCode('r', "\r") 35 | .addEscapeCode('t', "\t") 36 | .addCharacterEscapeCode('u', 4, 16) 37 | .appendOnEOF(""); 38 | 39 | public static String escape(String string) { 40 | string = string.replaceAll("\\\\" , "\\\\\\\\").replaceAll("\"" , "\\\\\""); 41 | return "\"" + string + "\""; 42 | } 43 | 44 | 45 | /*** Luokat ***/ 46 | 47 | public static interface JSONKey {} 48 | public static class JSONKeyString implements JSONKey { 49 | private final String key; 50 | private JSONKeyString(String key) { 51 | this.key = key; 52 | } 53 | 54 | public String getKey() { 55 | return key; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return escape(key); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return key.hashCode(); 66 | } 67 | } 68 | public static class JSONKeyInteger implements JSONKey { 69 | private final int key; 70 | private JSONKeyInteger(int key) { 71 | this.key = key; 72 | } 73 | 74 | public int getKey() { 75 | return key; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return String.valueOf(key); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return key; 86 | } 87 | } 88 | 89 | public static abstract class JSONElement implements Iterable { 90 | private final List path; 91 | private JSONElement(List path) { 92 | this.path = path; 93 | } 94 | 95 | public List getPath() { 96 | return path; 97 | } 98 | 99 | public abstract String getElementName(); 100 | } 101 | 102 | public static class JSONList extends JSONElement { 103 | private final List elements; 104 | private JSONList(List path, List elements) { 105 | super(path); 106 | this.elements = Collections.unmodifiableList(elements); 107 | } 108 | 109 | public List getElements() { 110 | return elements; 111 | } 112 | 113 | @Override 114 | public String toString() { 115 | String ans = "["; 116 | int i = 0; 117 | for (JSONElement e : elements) { 118 | if (i != 0) ans += ","; 119 | ans += e.toString(); 120 | i++; 121 | } 122 | ans += "]"; 123 | return ans; 124 | } 125 | 126 | @Override 127 | public String getElementName() { 128 | return "LIST"; 129 | } 130 | 131 | @Override 132 | public Iterator iterator() { 133 | return new Iterator() { 134 | int i = -1; 135 | Iterator eIterator; 136 | 137 | @Override 138 | public boolean hasNext() { 139 | return i < 0 || eIterator != null; 140 | } 141 | 142 | @Override 143 | public JSONElement next() { 144 | if (!hasNext()) throw new NoSuchElementException(); 145 | if (i == -1) { 146 | updateIterator(); 147 | return JSONList.this; 148 | } 149 | JSONElement a = eIterator.next(); 150 | updateIterator(); 151 | return a; 152 | } 153 | private void updateIterator() { 154 | while (eIterator == null || !eIterator.hasNext()) { 155 | if (++i < elements.size()) { 156 | eIterator = elements.get(i).iterator(); 157 | } 158 | else { 159 | eIterator = null; 160 | break; 161 | } 162 | } 163 | } 164 | }; 165 | } 166 | } 167 | 168 | public static class JSONMap extends JSONElement { 169 | private final Map elements; 170 | private JSONMap(List path, Map elements) { 171 | super(path); 172 | this.elements = Collections.unmodifiableMap(elements); 173 | } 174 | 175 | public Map getElements() { 176 | return elements; 177 | } 178 | 179 | @Override 180 | public String toString() { 181 | String ans = "{"; 182 | int i = 0; 183 | for (Map.Entry entry : elements.entrySet()) { 184 | if (i != 0) ans += ","; 185 | ans += entry.getKey().toString(); 186 | ans += ":"; 187 | ans += entry.getValue().toString(); 188 | i++; 189 | } 190 | ans += "}"; 191 | return ans; 192 | } 193 | 194 | @Override 195 | public String getElementName() { 196 | return "MAP"; 197 | } 198 | 199 | @Override 200 | public Iterator iterator() { 201 | return new Iterator() { 202 | int i = -1; 203 | Iterator eIterator; 204 | List elements = new ArrayList<>(JSONMap.this.elements.values()); 205 | 206 | @Override 207 | public boolean hasNext() { 208 | return i < 0 || eIterator != null; 209 | } 210 | 211 | @Override 212 | public JSONElement next() { 213 | if (!hasNext()) throw new NoSuchElementException(); 214 | if (i == -1) { 215 | updateIterator(); 216 | return JSONMap.this; 217 | } 218 | JSONElement a = eIterator.next(); 219 | updateIterator(); 220 | return a; 221 | } 222 | private void updateIterator() { 223 | while (eIterator == null || !eIterator.hasNext()) { 224 | if (++i < elements.size()) { 225 | eIterator = elements.get(i).iterator(); 226 | } 227 | else { 228 | eIterator = null; 229 | break; 230 | } 231 | } 232 | } 233 | }; 234 | } 235 | } 236 | 237 | private static abstract class JSONAtomic extends JSONElement { 238 | protected final T value; 239 | private final String elementName; 240 | private JSONAtomic(String elementName, List path, T value) { 241 | super(path); 242 | this.elementName = elementName; 243 | this.value = value; 244 | } 245 | 246 | public T getValue() { 247 | return value; 248 | } 249 | 250 | @Override 251 | public String toString() { 252 | return value.toString(); 253 | } 254 | 255 | @Override 256 | public String getElementName() { 257 | return elementName; 258 | } 259 | 260 | @Override 261 | public Iterator iterator() { 262 | return new Iterator() { 263 | boolean first = true; 264 | 265 | @Override 266 | public boolean hasNext() { 267 | return first; 268 | } 269 | 270 | @Override 271 | public JSONElement next() { 272 | if (first) { 273 | first = false; 274 | return JSONAtomic.this; 275 | } 276 | throw new NoSuchElementException(); 277 | } 278 | }; 279 | } 280 | } 281 | 282 | public static class JSONString extends JSONAtomic { 283 | private JSONString(List path, String value) { 284 | super("STRING", path, value); 285 | } 286 | 287 | @Override 288 | public String toString() { 289 | return escape(value); 290 | } 291 | } 292 | public static class JSONInteger extends JSONAtomic { 293 | private JSONInteger(List path, long value) { 294 | super("NUMBER", path, value); 295 | } 296 | } 297 | public static class JSONDouble extends JSONAtomic { 298 | private JSONDouble(List path, double value) { 299 | super("NUMBER", path, value); 300 | } 301 | } 302 | 303 | public static enum JSONConstants { 304 | TRUE("true", "BOOLEAN"), 305 | FALSE("false", "BOOLEAN"), 306 | NULL("null", "NULL"); 307 | 308 | private String name, elementName; 309 | 310 | JSONConstants(String name, String elementName) { 311 | this.name = name; 312 | this.elementName = elementName; 313 | } 314 | 315 | public String getName() { 316 | return name; 317 | } 318 | 319 | public String getElementName() { 320 | return elementName; 321 | } 322 | } 323 | 324 | public static class JSONConstant extends JSONAtomic { 325 | private JSONConstant(List path, JSONConstants value) { 326 | super(value.getElementName(), path, value); 327 | } 328 | 329 | @Override 330 | public String toString() { 331 | return value.getName(); 332 | } 333 | } 334 | 335 | /*** Parseri ***/ 336 | 337 | public static JSONElement parseJSON(String text) { 338 | return parse(t.tokenize(text, ""), new Stack<>()); 339 | } 340 | 341 | private static JSONElement parse(TokenList tl, Stack path) { 342 | if (tl.isNext("[")) { 343 | return parseList(tl, path); 344 | } 345 | if (tl.isNext("{")) { 346 | return parseMap(tl, path); 347 | } 348 | if (tl.isNext("\"")) { 349 | tl.accept("\""); 350 | String text = tl.nextString(); 351 | tl.accept("\""); 352 | return new JSONString(new ArrayList<>(path), text); 353 | } 354 | if (tl.seekString().matches("-?[0-9]+")) { 355 | long integer = Long.parseLong(tl.nextString()); 356 | return new JSONInteger(new ArrayList<>(path), integer); 357 | } 358 | if (tl.seekString().matches(NUMBER_REGEX)) { 359 | double doubling = Double.parseDouble(tl.nextString()); 360 | return new JSONDouble(new ArrayList<>(path), doubling); 361 | } 362 | for (JSONConstants constant : JSONConstants.values()) { 363 | if (tl.seekString().equals(constant.getName())) { 364 | tl.next(); 365 | return new JSONConstant(new ArrayList<>(path), constant); 366 | } 367 | } 368 | throw new ParsingException(TokenList.expected("[", "{", "true", "false", "null", "", ""), tl.next()); 369 | } 370 | 371 | private static JSONList parseList(TokenList tl, Stack path) { 372 | List list = new ArrayList<>(); 373 | tl.accept("["); 374 | int i = 0; 375 | while (!tl.isNext("]")) { 376 | if (i != 0) tl.accept(","); 377 | path.push(new JSONKeyInteger(i)); 378 | list.add(parse(tl, path)); 379 | path.pop(); 380 | i++; 381 | } 382 | tl.accept("]"); 383 | return new JSONList(new ArrayList<>(path), list); 384 | } 385 | 386 | private static JSONMap parseMap(TokenList tl, Stack path) { 387 | Map map = new HashMap<>(); 388 | tl.accept("{"); 389 | int i = 0; 390 | while (!tl.isNext("}")) { 391 | if (i != 0) tl.accept(","); 392 | tl.accept("\""); 393 | JSONKeyString key = new JSONKeyString(tl.nextString()); 394 | tl.accept("\""); 395 | tl.accept(":"); 396 | path.push(key); 397 | map.put(key, parse(tl, path)); 398 | path.pop(); 399 | i++; 400 | } 401 | tl.accept("}"); 402 | return new JSONMap(new ArrayList<>(path), map); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/RödaStream.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | import java.util.ArrayDeque; 8 | import java.util.ArrayList; 9 | import java.util.Deque; 10 | import java.util.Iterator; 11 | 12 | import java.util.function.Consumer; 13 | import java.util.function.Supplier; 14 | 15 | import java.io.BufferedReader; 16 | import java.io.PrintWriter; 17 | import java.io.IOException; 18 | 19 | import org.kaivos.röda.RödaValue; 20 | import org.kaivos.röda.type.RödaList; 21 | import org.kaivos.röda.type.RödaString; 22 | 23 | import static org.kaivos.röda.Interpreter.error; 24 | 25 | /** 26 | * RödaStream represents a pipe and can be used to transfer values from one 27 | * thread to another. 28 | * 29 | * Futhermore, RödaStream is a collection that holds all the current and future 30 | * values in a pipe. It can be used to iterate over all these values. 31 | */ 32 | public abstract class RödaStream implements Iterable { 33 | private Deque stack = new ArrayDeque<>(); 34 | 35 | protected abstract RödaValue get(); 36 | protected abstract void put(RödaValue value); 37 | 38 | /** 39 | * Closes the stream permanently. 40 | */ 41 | public abstract void finish(); 42 | 43 | /** 44 | * Returns false if it is not possible to pull values from the stream. 45 | * This is a non-blocking operation, and a truthy return value does not mean 46 | * that it is possible to pull values from the stream. 47 | */ 48 | public boolean closed() { 49 | return finished() && stack.isEmpty(); 50 | } 51 | 52 | /** 53 | * Returns true if it is possible to pull values from the stream. 54 | * This is a blocking operation. 55 | */ 56 | public boolean open() { 57 | return peek() != null; 58 | } 59 | 60 | /** 61 | * Returns true if the stream is permanently finished. 62 | */ 63 | public abstract boolean finished(); 64 | 65 | /** 66 | * Pushes a new value to the stream. 67 | */ 68 | public final void push(RödaValue value) { 69 | put(value); 70 | } 71 | 72 | /** 73 | * Adds a new value to the stack. 74 | */ 75 | public final void unpull(RödaValue value) { 76 | stack.addFirst(value); 77 | } 78 | 79 | /** 80 | * Pulls a value from the stream, or, if the stack is not empty, from the stack. 81 | * 82 | * @return the value, or null if the stream is closed. 83 | */ 84 | public final RödaValue pull() { 85 | if (!stack.isEmpty()) return stack.removeFirst(); 86 | return get(); 87 | } 88 | 89 | /** 90 | * Pulls a value from the stream and places it to the stack. 91 | * Next time a value is pulled, it will be taken from the stack. 92 | * 93 | * @return the value, or null if the stream is closed. 94 | */ 95 | public final RödaValue peek() { 96 | RödaValue value = pull(); 97 | if (value == null) return null; 98 | stack.addFirst(value); 99 | return value; 100 | } 101 | 102 | /** 103 | * Returns a value that represents all current and future values in the 104 | * stream. 105 | */ 106 | public final RödaValue readAll() { 107 | List list = new ArrayList<>(); 108 | while (true) { 109 | RödaValue val = pull(); 110 | if (val == null) 111 | break; 112 | list.add(val); 113 | } 114 | return RödaList.of(list); 115 | } 116 | 117 | /** 118 | * Calls the given consumer for all current and future values in the stream. 119 | * 120 | * @param consumer 121 | * the callback function used to consume the values 122 | */ 123 | public final void forAll(Consumer consumer) { 124 | while (true) { 125 | RödaValue val = pull(); 126 | if (val == null) 127 | break; 128 | consumer.accept(val); 129 | } 130 | } 131 | 132 | /** 133 | * Returns a iterator that iterates over all the current and future values 134 | * in the stream. 135 | */ 136 | @Override 137 | public Iterator iterator() { 138 | return new Iterator() { 139 | RödaValue buffer; 140 | { 141 | buffer = pull(); 142 | } 143 | 144 | @Override 145 | public boolean hasNext() { 146 | return buffer != null; 147 | } 148 | 149 | @Override 150 | public RödaValue next() { 151 | RödaValue tmp = buffer; 152 | buffer = pull(); 153 | return tmp; 154 | } 155 | }; 156 | } 157 | 158 | public static RödaStream makeStream() { 159 | RödaStream stream = new RödaStreamImpl(); 160 | return stream; 161 | } 162 | 163 | public static RödaStream makeEmptyStream() { 164 | RödaStream stream = new RödaStreamImpl(); 165 | stream.finish(); 166 | return stream; 167 | } 168 | 169 | public static RödaStream makeStream(Consumer put, Supplier get, Runnable finish, 170 | Supplier finished) { 171 | RödaStream stream = new RödaStream() { 172 | @Override 173 | public void put(RödaValue value) { 174 | put.accept(value); 175 | } 176 | 177 | @Override 178 | public RödaValue get() { 179 | return get.get(); 180 | } 181 | 182 | boolean hasFinished = false; 183 | 184 | @Override 185 | public void finish() { 186 | hasFinished = true; 187 | finish.run(); 188 | } 189 | 190 | @Override 191 | public boolean finished() { 192 | return hasFinished || finished.get(); 193 | } 194 | }; 195 | return stream; 196 | } 197 | 198 | static class RödaStreamImpl extends RödaStream { 199 | BlockingQueue> queue = new LinkedBlockingQueue<>(); 200 | boolean finished = false; 201 | 202 | @Override 203 | public RödaValue get() { 204 | if (finished) return null; 205 | Optional value; 206 | try { 207 | value = queue.take(); 208 | } catch (InterruptedException e) { 209 | error(e); 210 | return null; 211 | } 212 | finished = !value.isPresent(); 213 | return value.orElse(null); 214 | } 215 | 216 | @Override 217 | public void put(RödaValue value) { 218 | try { 219 | queue.put(Optional.of(value)); 220 | } catch (InterruptedException e) { 221 | error(e); 222 | } 223 | } 224 | 225 | @Override 226 | public boolean finished() { 227 | return finished; 228 | } 229 | 230 | @Override 231 | public void finish() { 232 | queue.add(Optional.empty()); 233 | } 234 | 235 | @Override 236 | public String toString() { 237 | return "" + (char) ('A' + id); 238 | } 239 | 240 | /* pitää kirjaa virroista debug-viestejä varten */ 241 | private static int streamCounter = 0; 242 | 243 | int id; 244 | { 245 | id = streamCounter++; 246 | } 247 | } 248 | 249 | public static enum ISStreamMode { 250 | LINE, 251 | CHARACTER 252 | } 253 | 254 | public static class ISStream extends RödaStream { 255 | private BufferedReader in; 256 | private boolean finished = false; 257 | private ISStreamMode mode = ISStreamMode.LINE; 258 | 259 | public ISStream(BufferedReader in) { 260 | this.in = in; 261 | } 262 | 263 | public void setMode(ISStreamMode mode) { 264 | this.mode = mode; 265 | } 266 | 267 | public RödaValue get() { 268 | if (finished) 269 | return null; 270 | try { 271 | switch (mode) { 272 | case LINE: 273 | String line = in.readLine(); 274 | if (line == null) 275 | return null; 276 | else 277 | return RödaString.of(line); 278 | case CHARACTER: 279 | int chr = in.read(); 280 | if (chr == -1) 281 | return null; 282 | else 283 | return RödaString.of(Character.toString((char) chr)); 284 | default: 285 | error("invalid input mode"); 286 | return null; 287 | } 288 | } catch (IOException e) { 289 | error(e); 290 | return null; 291 | } 292 | } 293 | 294 | public void put(RödaValue val) { 295 | error("no output to input"); 296 | } 297 | 298 | public boolean finished() { 299 | try { 300 | return finished || !in.ready(); 301 | } catch (IOException e) { 302 | return false; // Pitäisikö olla virheidenkäsittely? 303 | } 304 | } 305 | 306 | public void finish() { 307 | finished = true; 308 | try { 309 | in.close(); 310 | } catch (IOException e) { 311 | error(e); 312 | } 313 | } 314 | } 315 | 316 | public static class OSStream extends RödaStream { 317 | private PrintWriter out; 318 | 319 | public OSStream(PrintWriter out) { 320 | this.out = out; 321 | } 322 | 323 | public RödaValue get() { 324 | error("no input from output"); 325 | return null; 326 | } 327 | 328 | public void put(RödaValue val) { 329 | if (!closed()) { 330 | String str = val.str(); 331 | out.print(str); 332 | out.flush(); 333 | } else 334 | error("stream is closed"); 335 | } 336 | 337 | public boolean finished() { 338 | return finished; 339 | } 340 | 341 | boolean finished = false; 342 | 343 | public void finish() { 344 | finished = true; 345 | out.flush(); 346 | out.close(); 347 | } 348 | }; 349 | } 350 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/RödaValue.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda; 2 | 3 | import java.util.List; 4 | import java.util.ArrayList; 5 | import java.util.Map; 6 | import java.util.regex.Pattern; 7 | 8 | import org.kaivos.röda.runtime.Datatype; 9 | import org.kaivos.röda.runtime.Function; 10 | import org.kaivos.röda.type.RödaBoolean; 11 | 12 | import static org.kaivos.röda.type.RödaNativeFunction.NativeFunction; 13 | import static org.kaivos.röda.Interpreter.RödaScope; 14 | import static org.kaivos.röda.Interpreter.typeMismatch; 15 | 16 | public abstract class RödaValue { 17 | 18 | public static final Datatype STRING = new Datatype("string"); 19 | public static final Datatype NUMBER = new Datatype("number"); 20 | public static final Datatype INTEGER = new Datatype("integer"); 21 | public static final Datatype FLOATING = new Datatype("floating"); 22 | public static final Datatype BOOLEAN = new Datatype("boolean"); 23 | public static final Datatype LIST = new Datatype("list"); 24 | public static final Datatype MAP = new Datatype("map"); 25 | public static final Datatype FUNCTION = new Datatype("function"); 26 | public static final Datatype NFUNCTION = new Datatype("nfunction"); 27 | public static final Datatype NAMESPACE = new Datatype("namespace"); 28 | public static final Datatype REFERENCE = new Datatype("reference"); 29 | 30 | protected RödaValue() {} // käytä apufunktioita 31 | 32 | public abstract RödaValue copy(); 33 | 34 | public abstract String str(); 35 | 36 | public Pattern pattern() { 37 | return Pattern.compile(str()); 38 | } 39 | 40 | public String target() { 41 | typeMismatch("can't cast " + typeString() + " to reference"); 42 | return null; 43 | } 44 | 45 | public RödaScope localScope() { 46 | typeMismatch("can't cast " + typeString() + " to function"); 47 | return null; 48 | } 49 | 50 | public boolean bool() { 51 | return true; 52 | } 53 | 54 | public long integer() { 55 | typeMismatch("can't cast " + typeString() + " to integer"); 56 | return -1; 57 | } 58 | 59 | public double floating() { 60 | typeMismatch("can't cast " + typeString() + " to floating"); 61 | return -1; 62 | } 63 | 64 | public List list() { 65 | typeMismatch("can't cast " + typeString() + " to list"); 66 | return null; 67 | } 68 | 69 | public List modifiableList() { 70 | typeMismatch("can't cast " + typeString() + " to list"); 71 | return null; 72 | } 73 | 74 | public Map map() { 75 | typeMismatch("can't cast " + typeString() + " to list"); 76 | return null; 77 | } 78 | 79 | public RödaScope scope() { 80 | typeMismatch("can't cast " + typeString() + " to namespace"); 81 | return null; 82 | } 83 | 84 | public Function function() { 85 | typeMismatch("can't cast " + typeString() + " to function"); 86 | return null; 87 | } 88 | 89 | public NativeFunction nfunction() { 90 | typeMismatch("can't cast " + typeString() + " to function"); 91 | return null; 92 | } 93 | 94 | public RödaValue get(RödaValue index) { 95 | typeMismatch(typeString() + " doesn't have elements"); 96 | return null; 97 | } 98 | 99 | public void set(RödaValue index, RödaValue value) { 100 | typeMismatch(typeString() + " doesn't have elements"); 101 | } 102 | 103 | public void setSlice(RödaValue start, RödaValue end, RödaValue step, RödaValue value) { 104 | typeMismatch(typeString() + " doesn't have elements"); 105 | } 106 | 107 | public void del(RödaValue index) { 108 | typeMismatch(typeString() + " doesn't have elements"); 109 | } 110 | 111 | public void delSlice(RödaValue start, RödaValue end, RödaValue step) { 112 | typeMismatch(typeString() + " doesn't have elements"); 113 | } 114 | 115 | public RödaValue contains(RödaValue index) { 116 | typeMismatch(typeString() + " doesn't have elements"); 117 | return null; 118 | } 119 | 120 | public RödaValue containsValue(RödaValue value) { 121 | typeMismatch(typeString() + " doesn't have elements"); 122 | return null; 123 | } 124 | 125 | public RödaValue length() { 126 | typeMismatch(typeString() + " doesn't have length"); 127 | return null; 128 | } 129 | 130 | public RödaValue slice(RödaValue start, RödaValue end, RödaValue step) { 131 | typeMismatch(typeString() + " doesn't have elements"); 132 | return null; 133 | } 134 | 135 | public RödaValue join(RödaValue separator) { 136 | typeMismatch("can't join " + typeString()); 137 | return null; 138 | } 139 | 140 | public void add(RödaValue value) { 141 | typeMismatch("can't add values to " + typeString()); 142 | } 143 | 144 | public void addAll(List value) { 145 | typeMismatch("can't add values to " + typeString()); 146 | } 147 | 148 | public void remove(RödaValue value) { 149 | typeMismatch("can't remove values from " + typeString()); 150 | } 151 | 152 | public void setField(String field, RödaValue value) { 153 | typeMismatch(typeString() + " doesn't have fields"); 154 | } 155 | 156 | public RödaValue getField(String field) { 157 | typeMismatch(typeString() + " doesn't have fields"); 158 | return null; 159 | } 160 | 161 | public Map fields() { 162 | typeMismatch(typeString() + " doesn't have fields"); 163 | return null; 164 | } 165 | 166 | public RödaValue resolve(boolean implicite) { 167 | typeMismatch("can't cast " + typeString() + " to reference"); 168 | return null; 169 | } 170 | 171 | public RödaValue unsafeResolve() { 172 | typeMismatch("can't cast " + typeString() + " to reference"); 173 | return null; 174 | } 175 | 176 | public RödaValue impliciteResolve() { 177 | return this; 178 | } 179 | 180 | public void assign(RödaValue value) { 181 | typeMismatch("can't cast " + typeString() + " to reference"); 182 | } 183 | 184 | public void assignLocal(RödaValue value) { 185 | typeMismatch("can't cast " + typeString() + " to reference"); 186 | } 187 | 188 | public RödaValue callOperator(Parser.ExpressionTree.CType operator, RödaValue value) { 189 | switch (operator) { 190 | case EQ: 191 | return RödaBoolean.of(this.halfEq(value)); 192 | case NEQ: 193 | return RödaBoolean.of(!this.halfEq(value)); 194 | default: 195 | if (value == null) 196 | typeMismatch("can't " + operator.name() + " " + basicIdentity()); 197 | else 198 | typeMismatch("can't " + operator.name() + " " + basicIdentity() + " and " + value.basicIdentity()); 199 | return null; 200 | } 201 | } 202 | 203 | private List identities = new ArrayList<>(); 204 | 205 | protected void assumeIdentity(String name) { 206 | identities.add(new Datatype(name)); 207 | } 208 | 209 | protected void assumeIdentity(Datatype identity) { 210 | identities.add(identity); 211 | } 212 | 213 | protected void assumeIdentities(List identities) { 214 | this.identities.addAll(identities); 215 | } 216 | 217 | public List identities() { 218 | return identities; 219 | } 220 | 221 | public Datatype basicIdentity() { 222 | return identities.get(0); 223 | } 224 | 225 | public boolean is(String type) { 226 | return is(new Datatype(type)); 227 | } 228 | 229 | public boolean is(Datatype type) { 230 | return identities.contains(type); 231 | } 232 | 233 | boolean weakEq(RödaValue value) { 234 | return str().equals(value.str()); 235 | } 236 | 237 | /** Viittauksien vertaileminen kielletty **/ 238 | boolean halfEq(RödaValue value) { 239 | if (is(STRING) && value.is(INTEGER) 240 | || is(INTEGER) && value.is(STRING)) { 241 | return weakEq(value); 242 | } 243 | else return strongEq(value); 244 | } 245 | 246 | /** Viittauksien vertaileminen kielletty **/ 247 | public boolean strongEq(RödaValue value) { 248 | return false; 249 | } 250 | 251 | public final String typeString() { 252 | return basicIdentity().toString(); 253 | } 254 | 255 | @Override 256 | public String toString() { 257 | return "RödaValue{str=" + str() + "}"; 258 | } 259 | 260 | @Override 261 | public boolean equals(Object obj) { 262 | if (obj instanceof RödaValue) { 263 | if (!((RödaValue) obj).is(REFERENCE)) { 264 | return strongEq((RödaValue) obj); 265 | } else if (is(REFERENCE)) { 266 | RödaValue target = unsafeResolve(); 267 | return target.equals(((RödaValue) obj).unsafeResolve()); 268 | } 269 | else return false; 270 | } 271 | return super.equals(obj); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/Timer.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.ThreadMXBean; 5 | 6 | /** 7 | * A simple timer that counts time in nanoseconds. 8 | * 9 | * @author Iikka Hauhio 10 | * 11 | */ 12 | public class Timer { 13 | 14 | private long sum = 0, start = 0; 15 | 16 | private static final ThreadMXBean TMXB = ManagementFactory.getThreadMXBean(); 17 | 18 | /** 19 | * Starts the timer. 20 | */ 21 | public void start() { 22 | if (start != 0) throw new RuntimeException("Can't start a running timer."); 23 | 24 | start = TMXB.getCurrentThreadCpuTime(); 25 | } 26 | 27 | /** 28 | * Stops the timer and increments the value of the time with the number of 29 | * nanoseconds since it was started. 30 | */ 31 | public void stop() { 32 | if (start == 0) throw new RuntimeException("Can't stop a stopped timer."); 33 | long diff = TMXB.getCurrentThreadCpuTime() - start; 34 | sum += diff; 35 | start = 0; 36 | } 37 | 38 | /** 39 | * Resets the value of the timer. 40 | */ 41 | public void reset() { 42 | if (start != 0) throw new RuntimeException("Can't reset a running timer."); 43 | sum = 0; 44 | } 45 | 46 | /** 47 | * Adds the value of the given timer to the value of this timer. 48 | */ 49 | public void add(Timer timer) { 50 | sum += timer.sum; 51 | } 52 | 53 | /** 54 | * Returns the value of the timer, in milliseconds. The value is the total 55 | * time the timer has been running. 56 | * @return value of timer in milliseconds 57 | */ 58 | public long timeMillis() { 59 | return sum / 1_000_000; 60 | } 61 | 62 | /** 63 | * Returns the value of the timer, in nanoseconds. The value is the total 64 | * time the timer has been running. 65 | * @return value of timer in nanoseconds 66 | */ 67 | public long timeNanos() { 68 | return sum; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "Timer@" + hashCode() + "{ sum = " + sum + " }"; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/AssignGlobalPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentOverflow; 4 | import static org.kaivos.röda.Interpreter.unknownName; 5 | import static org.kaivos.röda.RödaValue.NAMESPACE; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.util.Arrays; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaNativeFunction; 13 | 14 | public final class AssignGlobalPopulator { 15 | 16 | private AssignGlobalPopulator() {} 17 | 18 | public static void populateAssignGlobal(RödaScope S) { 19 | S.setLocal("assignGlobal", RödaNativeFunction.of("assignGlobal", (typeargs, args, kwargs, scope, in, out) -> { 20 | String variableName = args.get(0).str(); 21 | S.setLocal(variableName, args.get(1)); 22 | }, Arrays.asList(new Parameter("variable", false, STRING), new Parameter("value", false)), false)); 23 | 24 | S.setLocal("createGlobal", RödaNativeFunction.of("createGlobal", (typeargs, args, kwargs, scope, in, out) -> { 25 | String variableName = args.get(0).str(); 26 | if (S.resolve(variableName) == null) 27 | S.setLocal(variableName, args.get(1)); 28 | }, Arrays.asList(new Parameter("variable", false, STRING), new Parameter("value", false)), false)); 29 | 30 | S.setLocal("assignGlobalType", RödaNativeFunction.of("assignGlobalType", (typeargs, args, kwargs, scope, in, out) -> { 31 | String typename = args.get(0).str(); 32 | if (args.size() > 2) argumentOverflow("assignGlobalType", 2, args.size()); 33 | RödaScope typeScope = args.size() == 1 ? scope : args.get(1).scope(); 34 | if (!typeScope.getRecords().containsKey(typename)) 35 | unknownName("record class '" + typename + "' not found"); 36 | S.registerRecord(typeScope.getRecordDeclarations().get(typename)); 37 | }, Arrays.asList( 38 | new Parameter("typename", false, STRING), 39 | new Parameter("source_namespace", false, NAMESPACE)), true)); 40 | 41 | S.setLocal("createGlobalType", RödaNativeFunction.of("createGlobalType", (typeargs, args, kwargs, scope, in, out) -> { 42 | String typename = args.get(0).str(); 43 | if (args.size() > 2) argumentOverflow("createGlobalType", 2, args.size()); 44 | RödaScope typeScope = args.size() == 1 ? scope : args.get(1).scope(); 45 | if (!typeScope.getRecords().containsKey(typename)) 46 | unknownName("record class '" + typename + "' not found"); 47 | if (!S.getRecords().containsKey(typename)) 48 | S.registerRecord(typeScope.getRecordDeclarations().get(typename)); 49 | }, Arrays.asList( 50 | new Parameter("typename", false, STRING), 51 | new Parameter("source_namespace", false, NAMESPACE)), true)); 52 | 53 | S.setLocal("assignType", RödaNativeFunction.of("assignType", (typeargs, args, kwargs, scope, in, out) -> { 54 | RödaScope target = args.get(0).scope(); 55 | String typename = args.get(1).str(); 56 | if (args.size() > 3) argumentOverflow("assignType", 2, args.size()); 57 | RödaScope typeScope = args.size() == 1 ? scope : args.get(2).scope(); 58 | if (!typeScope.getRecords().containsKey(typename)) 59 | unknownName("record class '" + typename + "' not found"); 60 | target.registerRecord(typeScope.getRecordDeclarations().get(typename)); 61 | }, Arrays.asList( 62 | new Parameter("target_namespace", false, NAMESPACE), 63 | new Parameter("typename", false, STRING), 64 | new Parameter("source_namespace", false, NAMESPACE)), true)); 65 | 66 | S.setLocal("createType", RödaNativeFunction.of("createType", (typeargs, args, kwargs, scope, in, out) -> { 67 | RödaScope target = args.get(0).scope(); 68 | String typename = args.get(1).str(); 69 | if (args.size() > 3) argumentOverflow("createType", 2, args.size()); 70 | RödaScope typeScope = args.size() == 1 ? scope : args.get(2).scope(); 71 | if (!typeScope.getRecords().containsKey(typename)) 72 | unknownName("record class '" + typename + "' not found"); 73 | if (!target.getRecords().containsKey(typename)) 74 | target.registerRecord(typeScope.getRecordDeclarations().get(typename)); 75 | }, Arrays.asList( 76 | new Parameter("target_namespace", false, NAMESPACE), 77 | new Parameter("typename", false, STRING), 78 | new Parameter("source_namespace", false, NAMESPACE)), true)); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/BtosAndStobPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkList; 4 | import static org.kaivos.röda.Interpreter.checkInteger; 5 | import static org.kaivos.röda.Interpreter.checkString; 6 | import static org.kaivos.röda.Interpreter.outOfBounds; 7 | import static org.kaivos.röda.RödaValue.LIST; 8 | import static org.kaivos.röda.RödaValue.STRING; 9 | 10 | import java.nio.charset.Charset; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.function.Consumer; 16 | 17 | import org.kaivos.röda.Interpreter.RödaScope; 18 | import org.kaivos.röda.RödaValue; 19 | import org.kaivos.röda.runtime.Function.Parameter; 20 | import org.kaivos.röda.type.RödaInteger; 21 | import org.kaivos.röda.type.RödaList; 22 | import org.kaivos.röda.type.RödaNativeFunction; 23 | import org.kaivos.röda.type.RödaString; 24 | 25 | public final class BtosAndStobPopulator { 26 | 27 | private BtosAndStobPopulator() {} 28 | 29 | public static void populateBtosAndStob(RödaScope S) { 30 | S.setLocal("bytesToString", RödaNativeFunction.of("bytesToString", (typeargs, args, kwargs, scope, in, out) -> { 31 | Charset chrset = StandardCharsets.UTF_8; 32 | Consumer convert = v -> { 33 | checkList("bytesToString", v); 34 | byte[] arr = new byte[(int) v.list().size()]; 35 | int c = 0; 36 | for (RödaValue i : v.list()) { 37 | checkInteger("bytesToString", i); 38 | long l = i.integer(); 39 | if (l > Byte.MAX_VALUE * 2) 40 | outOfBounds("byteToString: too large byte: " + l); 41 | arr[c++] = (byte) l; 42 | } 43 | out.push(RödaString.of(new String(arr, chrset))); 44 | }; 45 | if (args.size() > 0) { 46 | args.forEach(convert); 47 | } else { 48 | in.forAll(convert); 49 | } 50 | }, Arrays.asList(new Parameter("lists", false, LIST)), true)); 51 | 52 | S.setLocal("stringToBytes", RödaNativeFunction.of("stringToBytes", (typeargs, args, kwargs, scope, in, out) -> { 53 | Charset chrset = StandardCharsets.UTF_8; 54 | Consumer convert = v -> { 55 | checkString("stringToBytes", v); 56 | byte[] arr = v.str().getBytes(chrset); 57 | List bytes = new ArrayList<>(); 58 | for (byte b : arr) 59 | bytes.add(RödaInteger.of(b)); 60 | out.push(RödaList.of(bytes)); 61 | }; 62 | if (args.size() > 0) { 63 | args.forEach(convert); 64 | } else { 65 | in.forAll(convert); 66 | } 67 | }, Arrays.asList(new Parameter("strings", false, STRING)), true)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/CasePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.RödaValue.STRING; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.kaivos.röda.Interpreter.RödaScope; 8 | import org.kaivos.röda.runtime.Function.Parameter; 9 | import org.kaivos.röda.type.RödaNativeFunction; 10 | import org.kaivos.röda.type.RödaString; 11 | 12 | public class CasePopulator { 13 | 14 | private CasePopulator() {} 15 | 16 | public static void populateUpperAndLowerCase(RödaScope S) { 17 | S.setLocal("upperCase", RödaNativeFunction.of("upperCase", (typeargs, args, kwargs, scope, in, out) -> { 18 | out.push(RödaString.of(args.get(0).str().toUpperCase())); 19 | }, Arrays.asList(new Parameter("str", false, STRING)), false)); 20 | S.setLocal("lowerCase", RödaNativeFunction.of("lowerCase", (typeargs, args, kwargs, scope, in, out) -> { 21 | out.push(RödaString.of(args.get(0).str().toLowerCase())); 22 | }, Arrays.asList(new Parameter("str", false, STRING)), false)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/CdAndPwdPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.error; 4 | import static org.kaivos.röda.RödaValue.STRING; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.Arrays; 9 | 10 | import org.kaivos.röda.IOUtils; 11 | import org.kaivos.röda.Interpreter; 12 | import org.kaivos.röda.Interpreter.RödaScope; 13 | import org.kaivos.röda.runtime.Function.Parameter; 14 | import org.kaivos.röda.type.RödaNativeFunction; 15 | import org.kaivos.röda.type.RödaString; 16 | 17 | public final class CdAndPwdPopulator { 18 | 19 | private CdAndPwdPopulator() {} 20 | 21 | public static void populateCdAndPwd(Interpreter I, RödaScope S) { 22 | S.setLocal("cd", RödaNativeFunction.of("cd", (typeargs, args, kwargs, scope, in, out) -> { 23 | String dirname = args.get(0).str(); 24 | File dir = IOUtils.getMaybeRelativeFile(I.currentDir, dirname); 25 | if (!dir.isDirectory()) { 26 | error("cd: not a directory"); 27 | } 28 | I.currentDir = dir; 29 | }, Arrays.asList(new Parameter("path", false, STRING)), false)); 30 | 31 | S.setLocal("pwd", RödaNativeFunction.of("pwd", (typeargs, args, kwargs, scope, in, out) -> { 32 | try { 33 | out.push(RödaString.of(I.currentDir.getCanonicalPath())); 34 | } catch (IOException e) { 35 | error(e); 36 | } 37 | }, Arrays.asList(), false)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ChrAndOrdPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.illegalArguments; 4 | import static org.kaivos.röda.Interpreter.outOfBounds; 5 | import static org.kaivos.röda.RödaValue.INTEGER; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.util.Arrays; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaInteger; 13 | import org.kaivos.röda.type.RödaNativeFunction; 14 | import org.kaivos.röda.type.RödaString; 15 | 16 | public class ChrAndOrdPopulator { 17 | 18 | private ChrAndOrdPopulator() {} 19 | 20 | public static void populateChrAndOrd(RödaScope S) { 21 | S.setLocal("chr", RödaNativeFunction.of("chr", (typeargs, args, kwargs, scope, in, out) -> { 22 | long arg = args.get(0).integer(); 23 | if (arg < 0 || arg > Integer.MAX_VALUE) { 24 | outOfBounds("chr: code point out of range: " + arg); 25 | } 26 | out.push(RödaString.of(new String(Character.toChars((int) arg)))); 27 | }, Arrays.asList(new Parameter("n", false, INTEGER)), false)); 28 | 29 | S.setLocal("ord", RödaNativeFunction.of("ord", (typeargs, args, kwargs, scope, in, out) -> { 30 | String arg = args.get(0).str(); 31 | int length = arg.codePointCount(0, arg.length()); 32 | if (length > 1) { 33 | illegalArguments("ord: expected only one character, got " + length); 34 | } 35 | out.push(RödaInteger.of(arg.codePointAt(0))); 36 | }, Arrays.asList(new Parameter("c", false, STRING)), false)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/CurrentTimePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.kaivos.röda.Interpreter.RödaScope; 6 | import org.kaivos.röda.type.RödaInteger; 7 | import org.kaivos.röda.type.RödaNativeFunction; 8 | 9 | public final class CurrentTimePopulator { 10 | 11 | private CurrentTimePopulator() {} 12 | 13 | public static void populateTime(RödaScope S) { 14 | S.setLocal("currentTime", RödaNativeFunction.of("currentTime", (typeargs, args, kwargs, scope, in, out) -> { 15 | out.push(RödaInteger.of((int) System.currentTimeMillis())); 16 | }, Arrays.asList(), false)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/EnumPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Parser.expressionInt; 4 | import static org.kaivos.röda.RödaValue.INTEGER; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.kaivos.röda.Interpreter.RödaScope; 9 | import org.kaivos.röda.Interpreter; 10 | import org.kaivos.röda.RödaValue; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaInteger; 13 | import org.kaivos.röda.type.RödaNativeFunction; 14 | 15 | public final class EnumPopulator { 16 | 17 | private EnumPopulator() {} 18 | 19 | public static void populateEnum(RödaScope S) { 20 | S.setLocal("enum", RödaNativeFunction.of("enum", (typeargs, args, kwargs, scope, in, out) -> { 21 | if (args.size() > 1) Interpreter.argumentOverflow("enum", 1, args.size()); 22 | long i = args.size() == 0 ? 0 : args.get(0).integer(); 23 | long step = kwargs.get("step").integer(); 24 | while (true) { 25 | RödaValue val = in.pull(); 26 | if (val == null) break; 27 | out.push(val); 28 | out.push(RödaInteger.of(i)); 29 | i += step; 30 | } 31 | }, Arrays.asList(new Parameter("fst", false, INTEGER)), true, 32 | Arrays.asList(new Parameter("step", false, expressionInt("", 0, 1))))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ErrorPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkArgs; 4 | import static org.kaivos.röda.Interpreter.error; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.util.Arrays; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaNativeFunction; 13 | 14 | public class ErrorPopulator { 15 | 16 | private ErrorPopulator() {} 17 | 18 | public static void populateError(RödaScope S) { 19 | S.setLocal("error", RödaNativeFunction.of("error", (typeargs, args, kwargs, scope, in, out) -> { 20 | checkArgs("error", 1, args.size()); 21 | if (args.get(0).is(STRING)) { 22 | error(args.get(0).str()); 23 | } else if (!args.get(0).is("Error")) { 24 | typeMismatch("error: can't cast " + args.get(0).typeString() + " to an error"); 25 | } else 26 | error(args.get(0)); 27 | }, Arrays.asList(new Parameter("errorObject", false)), false)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ErrprintPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.kaivos.röda.Interpreter.RödaScope; 6 | import org.kaivos.röda.RödaValue; 7 | import org.kaivos.röda.runtime.Function.Parameter; 8 | import org.kaivos.röda.type.RödaNativeFunction; 9 | 10 | public final class ErrprintPopulator { 11 | 12 | private ErrprintPopulator() {} 13 | 14 | public static void populateErrprint(RödaScope S) { 15 | S.setLocal("errprint", RödaNativeFunction.of("errprint", (typeargs, args, kwargs, scope, in, out) -> { 16 | if (args.isEmpty()) { 17 | while (true) { 18 | RödaValue input = in.pull(); 19 | if (input == null) break; 20 | System.err.print(input.str()); 21 | } 22 | } else 23 | for (RödaValue value : args) { 24 | System.err.print(value.str()); 25 | } 26 | }, Arrays.asList(new Parameter("values", false)), true)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ExecPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 5 | import static org.kaivos.röda.Interpreter.checkMap; 6 | import static org.kaivos.röda.Interpreter.checkString; 7 | import static org.kaivos.röda.Interpreter.error; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.io.PrintWriter; 14 | import java.lang.ProcessBuilder.Redirect; 15 | import java.util.Arrays; 16 | import java.util.Collections; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map.Entry; 20 | import java.util.concurrent.ExecutionException; 21 | import java.util.concurrent.Future; 22 | 23 | import org.kaivos.röda.Interpreter; 24 | import org.kaivos.röda.Interpreter.RödaException; 25 | import org.kaivos.röda.Interpreter.RödaScope; 26 | import org.kaivos.röda.Parser; 27 | import org.kaivos.röda.Parser.DatatypeTree; 28 | import org.kaivos.röda.RödaStream; 29 | import org.kaivos.röda.runtime.Function.Parameter; 30 | import org.kaivos.röda.RödaValue; 31 | import org.kaivos.röda.type.RödaNativeFunction; 32 | import org.kaivos.röda.type.RödaString; 33 | 34 | public final class ExecPopulator { 35 | 36 | private ExecPopulator() {} 37 | 38 | private static void outputThread(InputStream pout, RödaStream out, boolean lineMode) { 39 | InputStreamReader reader = new InputStreamReader(pout); 40 | try { 41 | if (lineMode) { 42 | BufferedReader br = new BufferedReader(reader); 43 | while (true) { 44 | String str = br.readLine(); 45 | if (str == null) 46 | break; 47 | out.push(RödaString.of(str)); 48 | } 49 | br.close(); 50 | } else { 51 | while (true) { 52 | int chr = reader.read(); 53 | if (chr == -1) 54 | break; 55 | out.push(RödaString.of(String.valueOf((char) chr))); 56 | } 57 | } 58 | reader.close(); 59 | } catch (IOException e) { 60 | error(e); 61 | } 62 | } 63 | 64 | public static void addExecFunction(Interpreter I, String name, boolean lineMode) { 65 | I.G.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 66 | if (args.size() < 1) 67 | argumentUnderflow(name, 1, args.size()); 68 | List params = args.stream().map(v -> v.str()).collect(toList()); 69 | 70 | HashMap envVars = new HashMap<>(); 71 | checkMap(name, kwargs.get("env")); 72 | for (Entry e : kwargs.get("env").map().entrySet()) { 73 | checkString(name, e.getValue()); 74 | envVars.put(e.getKey(), e.getValue().str()); 75 | } 76 | 77 | boolean inheritIn = false, inheritOut = false, inheritErr = true; 78 | 79 | if (kwargs.containsKey("redirect_in")) { 80 | RödaValue val = kwargs.get("redirect_in"); 81 | if (val.is(RödaValue.BOOLEAN)) inheritIn = val.bool(); 82 | } 83 | 84 | if (kwargs.containsKey("redirect_out")) { 85 | RödaValue val = kwargs.get("redirect_out"); 86 | if (val.is(RödaValue.BOOLEAN)) inheritOut = val.bool(); 87 | } 88 | 89 | if (kwargs.containsKey("redirect_err")) { 90 | RödaValue val = kwargs.get("redirect_err"); 91 | if (val.is(RödaValue.BOOLEAN)) inheritErr = val.bool(); 92 | } 93 | 94 | try { 95 | ProcessBuilder b = new ProcessBuilder(params); 96 | if (inheritIn) b.redirectInput(Redirect.INHERIT); 97 | if (inheritOut) b.redirectOutput(Redirect.INHERIT); 98 | if (inheritErr) b.redirectError(Redirect.INHERIT); 99 | b.directory(I.currentDir); 100 | b.environment().putAll(envVars); 101 | Process p = b.start(); 102 | InputStream pout = p.getInputStream(); 103 | InputStream perr = p.getErrorStream(); 104 | PrintWriter pin = new PrintWriter(p.getOutputStream()); 105 | Runnable input = () -> { 106 | if (true) { 107 | while (p.isAlive()) { 108 | RödaValue value = in.pull(); 109 | if (value == null) 110 | break; 111 | pin.print(value.str()); 112 | pin.flush(); 113 | } 114 | } 115 | pin.close(); 116 | }; 117 | Runnable output = () -> outputThread(pout, out, lineMode); 118 | Runnable errput = () -> outputThread(perr, out, lineMode); 119 | Future futureIn = null, futureOut = null, futureErr = null; 120 | if (!inheritIn) futureIn = Interpreter.executor.submit(input); 121 | if (!inheritOut) futureOut = Interpreter.executor.submit(output); 122 | if (!inheritErr) futureErr = Interpreter.executor.submit(errput); 123 | if (!inheritOut) futureOut.get(); 124 | if (!inheritErr) futureErr.get(); 125 | if (!inheritIn) futureIn.get(); 126 | p.waitFor(); 127 | } catch (IOException e) { 128 | error(e); 129 | } catch (InterruptedException e) { 130 | error(e); 131 | } catch (ExecutionException e) { 132 | if (e.getCause() instanceof RödaException) { 133 | throw (RödaException) e.getCause(); 134 | } 135 | error(e.getCause()); 136 | } 137 | }, Arrays.asList(new Parameter("command", false), new Parameter("args", false)), true, 138 | Arrays.asList(new Parameter("env", false, 139 | Parser.expressionNew("", -1, new DatatypeTree("map"), Collections.emptyList()))), 140 | true)); 141 | } 142 | 143 | public static void populateExec(Interpreter I, RödaScope S) { 144 | addExecFunction(I, "exec", false); 145 | addExecFunction(I, "bufferedExec", true); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/FilePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.checkString; 5 | import static org.kaivos.röda.Interpreter.error; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.attribute.PosixFilePermission; 11 | import java.util.Arrays; 12 | import java.util.Set; 13 | import java.util.function.BiConsumer; 14 | 15 | import org.kaivos.röda.IOUtils; 16 | import org.kaivos.röda.Interpreter; 17 | import org.kaivos.röda.Interpreter.RödaScope; 18 | import org.kaivos.röda.RödaStream; 19 | import org.kaivos.röda.RödaValue; 20 | import org.kaivos.röda.runtime.Function.Parameter; 21 | import org.kaivos.röda.type.RödaBoolean; 22 | import org.kaivos.röda.type.RödaInteger; 23 | import org.kaivos.röda.type.RödaNativeFunction; 24 | import org.kaivos.röda.type.RödaString; 25 | 26 | public final class FilePopulator { 27 | 28 | private FilePopulator() {} 29 | 30 | private static void processQuery(Interpreter I, String name, RödaStream out, BiConsumer consumer, RödaValue value) { 31 | checkString(name, value); 32 | String filename = value.str(); 33 | File file = IOUtils.getMaybeRelativeFile(I.currentDir, filename); 34 | consumer.accept(file, out); 35 | } 36 | 37 | private static void addQueryType(Interpreter I, String name, BiConsumer consumer) { 38 | I.G.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 39 | if (args.isEmpty()) { 40 | argumentUnderflow(name, 1, 0); 41 | } 42 | else { 43 | for (int i = 0; i < args.size(); i++) { 44 | RödaValue value = args.get(i); 45 | processQuery(I, name, out, consumer, value); 46 | } 47 | } 48 | }, Arrays.asList(new Parameter("files", false)), true)); 49 | } 50 | 51 | public static void populateFile(Interpreter I, RödaScope S) { 52 | addQueryType(I, "fileLength", (file, out) -> out.push(RödaInteger.of(file.length()))); 53 | addQueryType(I, "fileExists", (file, out) -> out.push(RödaBoolean.of(file.exists()))); 54 | addQueryType(I, "isFile", (file, out) -> out.push(RödaBoolean.of(file.isFile()))); 55 | addQueryType(I, "isDirectory", (file, out) -> out.push(RödaBoolean.of(file.isDirectory()))); 56 | addQueryType(I, "mimeType", (file, out) -> { 57 | try { 58 | out.push(RödaString.of(Files.probeContentType(file.toPath()))); 59 | } catch (IOException e) { 60 | error(e); 61 | } 62 | }); 63 | addQueryType(I, "filePermissions", (file, out) ->{ 64 | try { 65 | out.push(RödaInteger.of(permissionsToInt(Files.getPosixFilePermissions(file.toPath())))); 66 | } catch (IOException e) { 67 | error(e); 68 | } 69 | }); 70 | addQueryType(I, "ls", (file, out) -> { 71 | try { 72 | Files.list(file.toPath()).map(p -> RödaString.of(p.toAbsolutePath().toString())).forEach(out::push); 73 | } catch (IOException e) { 74 | error(e); 75 | } 76 | }); 77 | } 78 | 79 | private static int permissionsToInt(Set permissions) { 80 | return permissions.stream().mapToInt(FilePopulator::permissionToInt).sum(); 81 | } 82 | 83 | private static int permissionToInt(PosixFilePermission permission) { 84 | switch (permission) { 85 | case OWNER_EXECUTE: 86 | return 100; 87 | case GROUP_EXECUTE: 88 | return 10; 89 | case OTHERS_EXECUTE: 90 | return 1; 91 | case OWNER_WRITE: 92 | return 200; 93 | case GROUP_WRITE: 94 | return 20; 95 | case OTHERS_WRITE: 96 | return 2; 97 | case OWNER_READ: 98 | return 400; 99 | case GROUP_READ: 100 | return 40; 101 | case OTHERS_READ: 102 | return 4; 103 | default: 104 | return 0; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/FilterPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.typeMismatch; 4 | import static org.kaivos.röda.RödaValue.BOOLEAN; 5 | import static org.kaivos.röda.RödaValue.FUNCTION; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.regex.Pattern; 11 | 12 | import org.kaivos.röda.Interpreter; 13 | import org.kaivos.röda.RödaStream; 14 | import org.kaivos.röda.RödaValue; 15 | import org.kaivos.röda.Interpreter.RödaScope; 16 | import org.kaivos.röda.runtime.Function.Parameter; 17 | import org.kaivos.röda.type.RödaNativeFunction; 18 | 19 | public final class FilterPopulator { 20 | 21 | private FilterPopulator() {} 22 | 23 | private static boolean evalCond(Interpreter I, RödaValue cond, RödaValue val) { 24 | RödaStream in = RödaStream.makeEmptyStream(); 25 | RödaStream out = RödaStream.makeStream(); 26 | I.exec("", 0, 27 | cond, 28 | Collections.emptyList(), Arrays.asList(val), Collections.emptyMap(), 29 | new RödaScope(I.G), in, out); 30 | out.finish(); 31 | boolean retval = true; 32 | while (true) { 33 | RödaValue cval = out.pull(); 34 | if (cval == null) break; 35 | if (!cval.is(BOOLEAN)) 36 | typeMismatch("condition returned a value of type '" + cval.typeString() 37 | + "', expected boolean"); 38 | retval &= cval.bool(); 39 | } 40 | return retval; 41 | } 42 | 43 | public static void populateFilterAndGrep(Interpreter I, RödaScope S) { 44 | S.setLocal("filter", RödaNativeFunction.of("filter", (typeargs, args, kwargs, scope, in, out) -> { 45 | in.forAll(val -> { 46 | if (evalCond(I, args.get(0), val)) out.push(val); 47 | }); 48 | }, Arrays.asList(new Parameter("cond", false, FUNCTION)), false)); 49 | 50 | S.setLocal("grep", RödaNativeFunction.of("grep", (typeargs, args, kwargs, scope, in, out) -> { 51 | Pattern[] patterns = new Pattern[args.size()]; 52 | for (int i = 0; i < patterns.length; i++) patterns[i] = Pattern.compile(args.get(i).str()); 53 | in.forAll(val -> { 54 | for (Pattern p : patterns) { 55 | if (p.matcher(val.str()).matches()) { 56 | out.push(val); 57 | } 58 | } 59 | }); 60 | }, Arrays.asList(new Parameter("patterns", false, STRING)), true)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/GetenvPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.RödaValue.STRING; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.kaivos.röda.Interpreter.RödaScope; 8 | import org.kaivos.röda.runtime.Function.Parameter; 9 | import org.kaivos.röda.type.RödaNativeFunction; 10 | import org.kaivos.röda.type.RödaString; 11 | 12 | public final class GetenvPopulator { 13 | 14 | private GetenvPopulator() {} 15 | 16 | public static void populateGetenv(RödaScope S) { 17 | S.setLocal("getenv", RödaNativeFunction.of("getenv", (typeargs, args, kwargs, scope, in, out) -> { 18 | out.push(RödaString.of(System.getenv(args.get(0).str()))); 19 | }, Arrays.asList(new Parameter("name", false, STRING)), false)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/HeadAndTailPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentOverflow; 4 | import static org.kaivos.röda.Interpreter.emptyStream; 5 | import static org.kaivos.röda.Interpreter.outOfBounds; 6 | import static org.kaivos.röda.RödaValue.INTEGER; 7 | import static org.kaivos.röda.RödaValue.LIST; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | import org.kaivos.röda.Interpreter.RödaScope; 14 | import org.kaivos.röda.RödaValue; 15 | import org.kaivos.röda.runtime.Function.Parameter; 16 | import org.kaivos.röda.type.RödaNativeFunction; 17 | 18 | public final class HeadAndTailPopulator { 19 | 20 | private HeadAndTailPopulator() { 21 | } 22 | 23 | public static void populateHeadAndTail(RödaScope S) { 24 | S.setLocal("head", RödaNativeFunction.of("head", (typeargs, args, kwargs, scope, in, out) -> { 25 | if (args.size() > 1) 26 | argumentOverflow("head", 1, args.size()); 27 | if (args.size() == 0) { 28 | RödaValue input = in.pull(); 29 | if (input == null) 30 | emptyStream("head: input stream is closed"); 31 | out.push(input); 32 | } else { 33 | long num = args.get(0).integer(); 34 | for (int i = 0; i < num; i++) { 35 | RödaValue input = in.pull(); 36 | if (input == null) 37 | emptyStream("head: input stream is closed"); 38 | out.push(input); 39 | } 40 | } 41 | }, Arrays.asList(new Parameter("number", false, INTEGER)), true)); 42 | 43 | S.setLocal("tail", RödaNativeFunction.of("tail", (typeargs, args, kwargs, scope, in, out) -> { 44 | if (args.size() > 1) 45 | argumentOverflow("tail", 1, args.size()); 46 | 47 | long numl; 48 | 49 | if (args.size() == 0) 50 | numl = 1; 51 | else { 52 | numl = args.get(0).integer(); 53 | if (numl > Integer.MAX_VALUE) 54 | outOfBounds("tail: too large number: " + numl); 55 | } 56 | 57 | int num = (int) numl; 58 | 59 | List values = new ArrayList<>(); 60 | for (RödaValue value : in) { 61 | values.add(value); 62 | } 63 | if (values.size() < num) 64 | emptyStream("tail: input stream is closed"); 65 | 66 | for (int i = values.size() - num; i < values.size(); i++) { 67 | out.push(values.get(i)); 68 | } 69 | 70 | }, Arrays.asList(new Parameter("number", false, INTEGER)), true)); 71 | 72 | S.setLocal("reverse", RödaNativeFunction.of("reverse", (typeargs, args, kwargs, scope, in, out) -> { 73 | if (args.size() > 1) 74 | argumentOverflow("reverse", 1, args.size()); 75 | 76 | List values; 77 | 78 | if (args.size() == 0) { 79 | values = new ArrayList<>(); 80 | in.forAll(values::add); 81 | } else { 82 | values = args.get(0).list(); 83 | } 84 | 85 | for (int i = values.size() - 1; i >= 0; i--) { 86 | out.push(values.get(i)); 87 | } 88 | 89 | }, Arrays.asList(new Parameter("list", false, LIST)), true)); 90 | 91 | S.setLocal("addHead", RödaNativeFunction.of("addHead", (typeargs, args, kwargs, scope, in, out) -> { 92 | args.forEach(out::push); 93 | in.forAll(out::push); 94 | }, Arrays.asList(new Parameter("values", false)), true)); 95 | 96 | S.setLocal("addTail", RödaNativeFunction.of("addTail", (typeargs, args, kwargs, scope, in, out) -> { 97 | in.forAll(out::push); 98 | args.forEach(out::push); 99 | }, Arrays.asList(new Parameter("values", false)), true)); 100 | 101 | S.setLocal("join", RödaNativeFunction.of("join", (typeargs, args, kwargs, scope, in, out) -> { 102 | RödaValue sep = args.get(0); 103 | int i = 0; 104 | while (true) { 105 | RödaValue value = in.pull(); 106 | if (value == null) break; 107 | if (i != 0) out.push(sep); 108 | out.push(value); 109 | i++; 110 | } 111 | }, Arrays.asList(new Parameter("value", false)), false)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/IdentityPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Collections; 4 | 5 | import org.kaivos.röda.Interpreter.RödaScope; 6 | import org.kaivos.röda.type.RödaNativeFunction; 7 | 8 | public final class IdentityPopulator { 9 | 10 | private IdentityPopulator() {} 11 | 12 | public static void populateIdentity(RödaScope S) { 13 | S.setLocal("identity", RödaNativeFunction.of("identity", (typeargs, args, kwargs, scope, in, out) -> { 14 | in.forAll(out::push); 15 | }, Collections.emptyList(), false)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ImportPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.RödaValue.STRING; 4 | 5 | import java.io.File; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | 9 | import org.kaivos.röda.IOUtils; 10 | import org.kaivos.röda.Interpreter; 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.RödaValue; 13 | import org.kaivos.röda.runtime.Function.Parameter; 14 | import org.kaivos.röda.type.RödaNamespace; 15 | import org.kaivos.röda.type.RödaNativeFunction; 16 | import org.kaivos.röda.type.RödaString; 17 | 18 | public final class ImportPopulator { 19 | 20 | private ImportPopulator() {} 21 | 22 | public static void populateImport(Interpreter I, RödaScope S) { 23 | S.setLocal("import", RödaNativeFunction.of("import", (typeargs, args, kwargs, scope, in, out) -> { 24 | File dir = kwargs.containsKey("dir") ? new File(kwargs.get("dir").str()) : I.currentDir; 25 | for (RödaValue value : args) { 26 | String filename = value.str(); 27 | File file = IOUtils.getMaybeRelativeFile(dir, filename); 28 | I.loadFile(file, I.G); 29 | } 30 | }, Arrays.asList(new Parameter("files", false, STRING)), true, Collections.emptyList(), true)); 31 | S.setLocal("localImport", RödaNativeFunction.of("localImport", (typeargs, args, kwargs, scope, in, out) -> { 32 | File dir = kwargs.containsKey("dir") ? new File(kwargs.get("dir").str()) : I.currentDir; 33 | for (RödaValue value : args) { 34 | String filename = value.str(); 35 | File file = IOUtils.getMaybeRelativeFile(dir, filename); 36 | I.loadFile(file, scope, true); 37 | } 38 | }, Arrays.asList(new Parameter("files", false, STRING)), true, Collections.emptyList(), true)); 39 | S.setLocal("safeLocalImport", RödaNativeFunction.of("safeLocalImport", (typeargs, args, kwargs, scope, in, out) -> { 40 | File dir = kwargs.containsKey("dir") ? new File(kwargs.get("dir").str()) : I.currentDir; 41 | for (RödaValue value : args) { 42 | String filename = value.str(); 43 | File file = IOUtils.getMaybeRelativeFile(dir, filename); 44 | I.loadFile(file, scope, false); 45 | } 46 | }, Arrays.asList(new Parameter("files", false, STRING)), true, Collections.emptyList(), true)); 47 | 48 | S.setLocal("importNamespace", RödaNativeFunction.of("importNamespace", (typeargs, args, kwargs, scope, in, out) -> { 49 | File dir = kwargs.containsKey("dir") ? new File(kwargs.get("dir").str()) : I.currentDir; 50 | for (RödaValue value : args) { 51 | String filename = value.str(); 52 | File file = IOUtils.getMaybeRelativeFile(dir, filename); 53 | RödaScope newScope = new RödaScope(I.G); 54 | newScope.setLocal("SOURCE_FILE", RödaString.of(file.getAbsolutePath())); 55 | newScope.setLocal("SOURCE_DIR", RödaString.of(file.getAbsoluteFile().getParentFile().getAbsolutePath())); 56 | I.loadFile(file, newScope); 57 | out.push(RödaNamespace.of(newScope)); 58 | } 59 | }, Arrays.asList(new Parameter("files", false, STRING)), true, Collections.emptyList(), true)); 60 | S.setLocal("localImportNamespace", RödaNativeFunction.of("localImportNamespace", (typeargs, args, kwargs, scope, in, out) -> { 61 | File dir = kwargs.containsKey("dir") ? new File(kwargs.get("dir").str()) : I.currentDir; 62 | for (RödaValue value : args) { 63 | String filename = value.str(); 64 | File file = IOUtils.getMaybeRelativeFile(dir, filename); 65 | RödaScope newScope = new RödaScope(scope); 66 | newScope.setLocal("SOURCE_FILE", RödaString.of(file.getAbsolutePath())); 67 | newScope.setLocal("SOURCE_DIR", RödaString.of(file.getAbsoluteFile().getParentFile().getAbsolutePath())); 68 | I.loadFile(file, newScope); 69 | out.push(RödaNamespace.of(newScope)); 70 | } 71 | }, Arrays.asList(new Parameter("files", false, STRING)), true, Collections.emptyList(), true)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/IndexOfPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.RödaValue.STRING; 4 | import static org.kaivos.röda.Interpreter.checkList; 5 | import static org.kaivos.röda.Interpreter.checkString; 6 | 7 | import java.util.Arrays; 8 | 9 | import org.kaivos.röda.Interpreter.RödaScope; 10 | import org.kaivos.röda.runtime.Function.Parameter; 11 | import org.kaivos.röda.type.RödaInteger; 12 | import org.kaivos.röda.type.RödaNativeFunction; 13 | 14 | public final class IndexOfPopulator { 15 | 16 | private IndexOfPopulator() {} 17 | 18 | public static void populateIndexOf(RödaScope S) { 19 | S.setLocal("indexOf", RödaNativeFunction.of("indexOf", (typeargs, args, kwargs, scope, in, out) -> { 20 | if (args.get(1).is(STRING)) { 21 | checkString("indexOf", args.get(0)); 22 | out.push(RödaInteger.of(args.get(1).str().indexOf(args.get(0).str()))); 23 | } 24 | else { 25 | checkList("indexOf", args.get(1)); 26 | for (int i = 0; i < args.get(1).list().size(); i++) { 27 | if (args.get(1).list().get(i).strongEq(args.get(0))) { 28 | out.push(RödaInteger.of(i)); 29 | return; 30 | } 31 | } 32 | out.push(RödaInteger.of(-1)); 33 | } 34 | }, Arrays.asList(new Parameter("sequence", false), new Parameter("str", false)), true)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/InterleavePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.emptyStream; 4 | import static org.kaivos.röda.Interpreter.illegalArguments; 5 | import static org.kaivos.röda.Interpreter.outOfBounds; 6 | import static org.kaivos.röda.RödaValue.INTEGER; 7 | import static org.kaivos.röda.RödaValue.LIST; 8 | 9 | import java.util.Arrays; 10 | 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.RödaValue; 13 | import org.kaivos.röda.runtime.Function.Parameter; 14 | import org.kaivos.röda.type.RödaNativeFunction; 15 | 16 | public final class InterleavePopulator { 17 | 18 | private InterleavePopulator() {} 19 | 20 | public static void populateInterleave(RödaScope S) { 21 | S.setLocal("interleave", RödaNativeFunction.of("interleave", (typeargs, args, kwargs, scope, in, out) -> { 22 | int length = args.get(0).list().size(); 23 | 24 | for (RödaValue list : args) { 25 | if (list.list().size() != length) 26 | illegalArguments("illegal use of interleave: all lists must have the same size"); 27 | } 28 | 29 | for (int i = 0; i < length; i++) { 30 | for (RödaValue list : args) { 31 | out.push(list.list().get(i)); 32 | } 33 | } 34 | }, Arrays.asList(new Parameter("first_list", false, LIST), new Parameter("other_lists", false, LIST)), true)); 35 | S.setLocal("slide", RödaNativeFunction.of("slide", (typeargs, args, kwargs, scope, in, out) -> { 36 | long n = args.get(0).integer(); 37 | if (n > Integer.MAX_VALUE || n < 1) outOfBounds("invalid subsequence length: " + n 38 | + " (valid range: 1 <= n <= " + Integer.MAX_VALUE + ")"); 39 | RödaValue[] array = new RödaValue[(int) n]; 40 | for (int i = 0; i < n; i++) { 41 | RödaValue val = in.pull(); 42 | if (val == null) emptyStream("empty stream"); 43 | array[i] = val; 44 | out.push(val); 45 | } 46 | int i = 0; 47 | RödaValue val; 48 | while ((val = in.pull()) != null) { 49 | array[(int)(i++%n)] = val; 50 | for (int j = 0; j < n; j++) { 51 | out.push(array[(int)((i+j)%n)]); 52 | } 53 | } 54 | }, Arrays.asList(new Parameter("n", false, INTEGER)), false)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/JsonPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.kaivos.röda.Interpreter.argumentOverflow; 5 | import static org.kaivos.röda.Interpreter.checkString; 6 | 7 | import java.util.Arrays; 8 | 9 | import org.kaivos.röda.Interpreter.RödaScope; 10 | import org.kaivos.röda.JSON; 11 | import org.kaivos.röda.JSON.JSONDouble; 12 | import org.kaivos.röda.JSON.JSONElement; 13 | import org.kaivos.röda.JSON.JSONInteger; 14 | import org.kaivos.röda.JSON.JSONList; 15 | import org.kaivos.röda.JSON.JSONMap; 16 | import org.kaivos.röda.JSON.JSONString; 17 | import org.kaivos.röda.runtime.Function.Parameter; 18 | import org.kaivos.röda.RödaStream; 19 | import org.kaivos.röda.RödaValue; 20 | import org.kaivos.röda.type.RödaFloating; 21 | import org.kaivos.röda.type.RödaInteger; 22 | import org.kaivos.röda.type.RödaList; 23 | import org.kaivos.röda.type.RödaNativeFunction; 24 | import org.kaivos.röda.type.RödaString; 25 | 26 | public final class JsonPopulator { 27 | 28 | private JsonPopulator() {} 29 | 30 | private static void handle(RödaStream out, String code) { 31 | JSONElement root = JSON.parseJSON(code); 32 | // rekursiivinen ulostulo 33 | // apuluokka rekursion mahdollistamiseksi 34 | class R { I i; } 35 | R> 36 | makeRöda = new R<>(); 37 | makeRöda.i = json -> { 38 | RödaValue elementName = RödaString.of(json.getElementName()); 39 | RödaValue value; 40 | if (json instanceof JSONInteger) { 41 | value = RödaInteger.of(((JSONInteger) json).getValue()); 42 | } 43 | else if (json instanceof JSONDouble) { 44 | value = RödaFloating.of(((JSONDouble) json).getValue()); 45 | } 46 | else if (json instanceof JSONString) { 47 | value = RödaString.of(((JSONString) json).getValue()); 48 | } 49 | else if (json instanceof JSONList) { 50 | value = RödaList.of(((JSONList) json).getElements() 51 | .stream() 52 | .map(j -> makeRöda.i.apply(j)) 53 | .collect(toList())); 54 | } 55 | else if (json instanceof JSONMap) { 56 | value = RödaList.of(((JSONMap) json).getElements().entrySet() 57 | .stream() 58 | .map(e -> RödaList.of(RödaString.of(e.getKey().getKey()), 59 | makeRöda.i.apply(e.getValue()))) 60 | .collect(toList())); 61 | } 62 | else { 63 | value = RödaString.of(json.toString()); 64 | } 65 | return RödaList.of(elementName, value); 66 | }; 67 | out.push(makeRöda.i.apply(root)); 68 | } 69 | 70 | public static void populateJson(RödaScope S) { 71 | S.setLocal("json", RödaNativeFunction.of("json", (typeargs, args, kwargs, scope, in, out) -> { 72 | if (args.size() > 1) argumentOverflow("json", 1, args.size()); 73 | else if (args.size() == 1) { 74 | RödaValue arg = args.get(0); 75 | checkString("json", arg); 76 | String code = arg.str(); 77 | handle(out, code); 78 | } 79 | else { 80 | RödaValue value; 81 | while ((value = in.pull()) != null) { 82 | checkString("json", value); 83 | String code = value.str(); 84 | handle(out, code); 85 | } 86 | } 87 | }, Arrays.asList(new Parameter("flags_and_code", false)), true)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/KeysPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.illegalArguments; 4 | import static org.kaivos.röda.RödaValue.MAP; 5 | import static org.kaivos.röda.RödaValue.NAMESPACE; 6 | 7 | import java.util.Arrays; 8 | 9 | import org.kaivos.röda.Interpreter.RödaScope; 10 | import org.kaivos.röda.RödaValue; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaNativeFunction; 13 | import org.kaivos.röda.type.RödaString; 14 | 15 | public class KeysPopulator { 16 | 17 | private KeysPopulator() {} 18 | 19 | public static void populateKeys(RödaScope S) { 20 | S.setLocal("keys", RödaNativeFunction.of("keys", (typeargs, args, kwargs, scope, in, out) -> { 21 | RödaValue arg = args.get(0); 22 | if (!arg.is(MAP) && !arg.is(NAMESPACE)) 23 | illegalArguments("illegal argument 'table' for 'keys': " 24 | + "map or namespace expected (got " + arg.typeString() + ")"); 25 | (arg.is(MAP) ? arg.map().keySet() : arg.scope().getLocalVariableNames()) 26 | .stream().map(RödaString::of).forEach(out::push); 27 | }, Arrays.asList(new Parameter("table", false)), false)); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/MatchPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.checkString; 5 | import static org.kaivos.röda.Interpreter.error; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.util.Arrays; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import java.util.regex.PatternSyntaxException; 12 | 13 | import org.kaivos.röda.Interpreter.RödaScope; 14 | import org.kaivos.röda.RödaValue; 15 | import org.kaivos.röda.runtime.Function.Parameter; 16 | import org.kaivos.röda.type.RödaList; 17 | import org.kaivos.röda.type.RödaNativeFunction; 18 | import org.kaivos.röda.type.RödaString; 19 | 20 | public final class MatchPopulator { 21 | 22 | private MatchPopulator() {} 23 | 24 | public static void populateMatch(RödaScope S) { 25 | S.setLocal("match", RödaNativeFunction.of("match", (typeargs, args, kwargs, scope, in, out) -> { 26 | if (args.size() < 1) 27 | argumentUnderflow("match", 1, 0); 28 | String regex = args.get(0).str(); 29 | args.remove(0); 30 | Pattern pattern; 31 | try { 32 | pattern = Pattern.compile(regex); 33 | } catch (PatternSyntaxException e) { 34 | error("match: pattern syntax exception: " + e.getMessage()); 35 | return; 36 | } 37 | 38 | if (args.size() > 0) { 39 | for (RödaValue arg : args) { 40 | Matcher matcher = pattern.matcher(arg.str()); 41 | if (matcher.matches()) { 42 | RödaValue[] results = new RödaValue[matcher.groupCount() + 1]; 43 | for (int i = 0; i < results.length; i++) { 44 | String group = matcher.group(i); 45 | results[i] = RödaString.of(group != null ? group : ""); 46 | } 47 | out.push(RödaList.of(results)); 48 | } else 49 | out.push(RödaList.of()); 50 | } 51 | } else { 52 | while (true) { 53 | RödaValue input = in.pull(); 54 | if (input == null) 55 | break; 56 | checkString("match", input); 57 | Matcher matcher = pattern.matcher(input.str()); 58 | if (matcher.matches()) { 59 | RödaValue[] results = new RödaValue[matcher.groupCount() + 1]; 60 | for (int i = 0; i < results.length; i++) { 61 | String group = matcher.group(i); 62 | results[i] = RödaString.of(group != null ? group : ""); 63 | } 64 | out.push(RödaList.of(results)); 65 | } else 66 | out.push(RödaList.of()); 67 | } 68 | } 69 | }, Arrays.asList(new Parameter("pattern", false, STRING), new Parameter("strings", false, STRING)), true)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/MathPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.RödaValue.FLOATING; 4 | import static org.kaivos.röda.RödaValue.INTEGER; 5 | import static org.kaivos.röda.RödaValue.NUMBER; 6 | 7 | import java.util.Arrays; 8 | import java.util.function.Function; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.RödaValue; 12 | import org.kaivos.röda.runtime.Function.Parameter; 13 | import org.kaivos.röda.type.RödaFloating; 14 | import org.kaivos.röda.type.RödaInteger; 15 | import org.kaivos.röda.type.RödaNativeFunction; 16 | 17 | public final class MathPopulator { 18 | 19 | private MathPopulator() {} 20 | 21 | private static void addMathFunction(RödaScope S, String name, Function f) { 22 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 23 | out.push(RödaFloating.of(f.apply(args.get(0).floating()))); 24 | }, Arrays.asList(new Parameter("arg", false, NUMBER)), false)); 25 | } 26 | 27 | public static void populateMath(RödaScope S) { 28 | S.setLocal("E", RödaFloating.of(Math.E)); 29 | S.setLocal("PI", RödaFloating.of(Math.PI)); 30 | 31 | addMathFunction(S, "floor", Math::floor); 32 | addMathFunction(S, "ceil", Math::ceil); 33 | S.setLocal("round", RödaNativeFunction.of("round", (typeargs, args, kwargs, scope, in, out) -> { 34 | out.push(RödaInteger.of(Math.round(args.get(0).floating()))); 35 | }, Arrays.asList(new Parameter("arg", false, NUMBER)), false)); 36 | 37 | S.setLocal("abs", RödaNativeFunction.of("abs", (typeargs, args, kwargs, scope, in, out) -> { 38 | RödaValue arg = args.get(0); 39 | if (arg.is(INTEGER)) out.push(RödaInteger.of(Math.abs(arg.integer()))); 40 | else if (arg.is(FLOATING)) out.push(RödaFloating.of(Math.abs(arg.floating()))); 41 | }, Arrays.asList(new Parameter("variables", false, NUMBER)), false)); 42 | 43 | addMathFunction(S, "signum", Math::signum); 44 | 45 | addMathFunction(S, "sin", Math::sin); 46 | addMathFunction(S, "cos", Math::cos); 47 | addMathFunction(S, "tan", Math::tan); 48 | addMathFunction(S, "asin", Math::asin); 49 | addMathFunction(S, "acos", Math::acos); 50 | addMathFunction(S, "atan", Math::atan); 51 | S.setLocal("atan2", RödaNativeFunction.of("atan2", (typeargs, args, kwargs, scope, in, out) -> { 52 | out.push(RödaFloating.of(Math.atan2(args.get(0).floating(), args.get(1).floating()))); 53 | }, Arrays.asList(new Parameter("x", false, NUMBER), new Parameter("y", false, NUMBER)), false)); 54 | addMathFunction(S, "sinh", Math::sinh); 55 | addMathFunction(S, "cosh", Math::cosh); 56 | addMathFunction(S, "tanh", Math::tanh); 57 | 58 | addMathFunction(S, "sqrt", Math::sqrt); 59 | addMathFunction(S, "cbrt", Math::cbrt); 60 | 61 | addMathFunction(S, "exp", Math::exp); 62 | addMathFunction(S, "ln", Math::log); 63 | addMathFunction(S, "log10", Math::log10); 64 | S.setLocal("log", RödaNativeFunction.of("log", (typeargs, args, kwargs, scope, in, out) -> { 65 | out.push(RödaFloating.of(Math.log(args.get(1).floating()) / Math.log(args.get(0).floating()))); 66 | }, Arrays.asList(new Parameter("base", false, NUMBER), new Parameter("arg", false, NUMBER)), false)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/NamePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.typeMismatch; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.kaivos.röda.Interpreter.RödaScope; 8 | import org.kaivos.röda.runtime.Function.Parameter; 9 | import org.kaivos.röda.RödaValue; 10 | import org.kaivos.röda.type.RödaNativeFunction; 11 | import org.kaivos.röda.type.RödaString; 12 | 13 | public final class NamePopulator { 14 | 15 | private NamePopulator() {} 16 | 17 | public static void populateName(RödaScope S) { 18 | S.setLocal("name", RödaNativeFunction.of("name", (typeargs, args, kwargs, scope, in, out) -> { 19 | for (RödaValue value : args) { 20 | if (!value.is(RödaValue.REFERENCE)) 21 | typeMismatch("invalid argument for undefine: only references accepted"); 22 | 23 | out.push(RödaString.of(value.target())); 24 | } 25 | }, Arrays.asList(new Parameter("variables", true)), true)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ParseNumPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.checkInteger; 5 | import static org.kaivos.röda.Interpreter.error; 6 | import static org.kaivos.röda.Interpreter.outOfBounds; 7 | import static org.kaivos.röda.RödaValue.STRING; 8 | 9 | import java.util.Arrays; 10 | 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.Parser; 13 | import org.kaivos.röda.RödaValue; 14 | import org.kaivos.röda.runtime.Function.Parameter; 15 | import org.kaivos.röda.type.RödaFloating; 16 | import org.kaivos.röda.type.RödaInteger; 17 | import org.kaivos.röda.type.RödaNativeFunction; 18 | 19 | public final class ParseNumPopulator { 20 | 21 | private ParseNumPopulator() {} 22 | 23 | public static void populateParseNum(RödaScope S) { 24 | S.setLocal("parseInteger", RödaNativeFunction.of("parseInteger", (typeargs, args, kwargs, scope, in, out) -> { 25 | checkInteger("parseInteger", kwargs.get("radix")); 26 | long radixl = kwargs.get("radix").integer(); 27 | if (radixl < Character.MIN_RADIX || radixl > Character.MAX_RADIX) 28 | outOfBounds("parseInteger: radix out of bounds: " + radixl); 29 | int radix = (int) radixl; 30 | if (args.isEmpty()) { 31 | argumentUnderflow("parseInteger", 1, 0); 32 | } 33 | try { 34 | for (RödaValue v : args) { 35 | out.push(RödaInteger.of(Long.parseLong(v.str(), radix))); 36 | } 37 | } catch (NumberFormatException e) { 38 | error("number format error: " + e.getMessage()); 39 | } 40 | }, Arrays.asList(new Parameter("strings", false, STRING)), true, 41 | Arrays.asList(new Parameter("radix", false, Parser.expressionInt("", 0, 10))))); 42 | 43 | S.setLocal("parseFloating", RödaNativeFunction.of("parseFloating", (typeargs, args, kwargs, scope, in, out) -> { 44 | if (args.isEmpty()) { 45 | argumentUnderflow("parseFloating", 1, 0); 46 | } 47 | try { 48 | for (RödaValue v : args) { 49 | out.push(RödaFloating.of(Double.parseDouble(v.str()))); 50 | } 51 | } catch (NumberFormatException e) { 52 | error("number format error: " + e.getMessage()); 53 | } 54 | }, Arrays.asList(new Parameter("strings", false, STRING)), true)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/PushAndPullPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.checkReference; 5 | import static org.kaivos.röda.Interpreter.emptyStream; 6 | 7 | import java.util.Arrays; 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Function; 10 | 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.RödaStream; 13 | import org.kaivos.röda.RödaValue; 14 | import org.kaivos.röda.runtime.Function.Parameter; 15 | import org.kaivos.röda.type.RödaBoolean; 16 | import org.kaivos.röda.type.RödaNativeFunction; 17 | 18 | public final class PushAndPullPopulator { 19 | 20 | private PushAndPullPopulator() {} 21 | 22 | private static void addPushingFunction(RödaScope S, String name, boolean isIn, BiConsumer body) { 23 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 24 | if (args.isEmpty()) 25 | argumentUnderflow(name, 1, 0); 26 | for (RödaValue value : args) { 27 | body.accept(isIn ? in : out, value); 28 | } 29 | }, Arrays.asList(new Parameter("values", false)), true)); 30 | } 31 | 32 | private static void addPullingFunction(RödaScope S, String name, 33 | boolean returnSuccess, Function body) { 34 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 35 | if (args.isEmpty()) { 36 | if (returnSuccess) argumentUnderflow(name, 1, 0); 37 | RödaValue value = body.apply(in); 38 | if (value == null) emptyStream("empty stream"); 39 | out.push(value); 40 | return; 41 | } 42 | 43 | for (RödaValue value : args) { 44 | checkReference(name, value); 45 | 46 | RödaValue pulled = body.apply(in); 47 | if (returnSuccess) { 48 | out.push(RödaBoolean.of(pulled != null)); 49 | } 50 | else if (pulled == null) emptyStream("empty stream"); 51 | value.assignLocal(pulled); 52 | } 53 | }, Arrays.asList(new Parameter("variables", true)), true)); 54 | } 55 | 56 | public static void populatePushAndPull(RödaScope S) { 57 | addPushingFunction(S, "push", false, RödaStream::push); 58 | addPushingFunction(S, "unpull", true, RödaStream::unpull); 59 | 60 | addPullingFunction(S, "pull", false, RödaStream::pull); 61 | addPullingFunction(S, "tryPull", true, RödaStream::pull); 62 | 63 | addPullingFunction(S, "peek", false, RödaStream::peek); 64 | addPullingFunction(S, "tryPeek", true, RödaStream::peek); 65 | 66 | S.setLocal("open", RödaNativeFunction.of("open", (typeargs, args, kwargs, scope, in, out) -> { 67 | out.push(RödaBoolean.of(in.open())); 68 | }, Arrays.asList(new Parameter("values", false)), true)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/RandomPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkReference; 4 | 5 | import java.util.Arrays; 6 | import java.util.Random; 7 | import java.util.function.Supplier; 8 | 9 | import org.kaivos.röda.Interpreter.RödaScope; 10 | import org.kaivos.röda.RödaValue; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaBoolean; 13 | import org.kaivos.röda.type.RödaFloating; 14 | import org.kaivos.röda.type.RödaInteger; 15 | import org.kaivos.röda.type.RödaNativeFunction; 16 | 17 | public final class RandomPopulator { 18 | 19 | private static Random rnd = new Random(); 20 | 21 | private RandomPopulator() {} 22 | 23 | private static void addQueryType(RödaScope S, String name, Supplier supplier) { 24 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 25 | if (args.size() == 0) { 26 | out.push(supplier.get()); 27 | return; 28 | } 29 | for (RödaValue variable : args) { 30 | checkReference(name, variable); 31 | variable.assignLocal(supplier.get()); 32 | } 33 | }, Arrays.asList(new Parameter("variables", true)), true)); 34 | } 35 | 36 | public static void populateRandom(RödaScope S) { 37 | 38 | S.setLocal("randomize", RödaNativeFunction.of("randomize", (typeargs, args, kwargs, scope, in, out) -> { 39 | RödaValue seed = args.get(0); 40 | if (seed.is(RödaValue.INTEGER)) { 41 | synchronized (rnd) { 42 | rnd = new Random(seed.integer()); 43 | } 44 | } 45 | else { 46 | synchronized (rnd) { 47 | rnd = new Random(seed.str().hashCode()); 48 | } 49 | } 50 | }, Arrays.asList(new Parameter("seed", false)), false)); 51 | 52 | addQueryType(S, "randomInteger", () -> RödaInteger.of(rnd.nextInt())); 53 | addQueryType(S, "randomFloating", () -> RödaFloating.of(rnd.nextDouble())); 54 | addQueryType(S, "randomBoolean", () -> RödaBoolean.of(rnd.nextBoolean())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ReadAndWritePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.error; 5 | import static org.kaivos.röda.RödaValue.STRING; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.nio.file.Files; 11 | import java.util.Arrays; 12 | import java.util.stream.Stream; 13 | 14 | import org.kaivos.röda.IOUtils; 15 | import org.kaivos.röda.Interpreter; 16 | import org.kaivos.röda.Parser; 17 | import org.kaivos.röda.Interpreter.RödaScope; 18 | import org.kaivos.röda.RödaValue; 19 | import org.kaivos.röda.runtime.Function.Parameter; 20 | import org.kaivos.röda.type.RödaNativeFunction; 21 | import org.kaivos.röda.type.RödaString; 22 | 23 | public final class ReadAndWritePopulator { 24 | 25 | public static void populateReadAndWrite(Interpreter I, RödaScope S) { 26 | S.setLocal("readLines", RödaNativeFunction.of("readLines", (typeargs, args, kwargs, scope, in, out) -> { 27 | if (args.size() < 1) argumentUnderflow("readLines", 1, args.size()); 28 | long skip = kwargs.get("skip").integer(); 29 | long limit = kwargs.get("limit").integer(); 30 | for (RödaValue value : args) { 31 | String filename = value.str(); 32 | File file = IOUtils.getMaybeRelativeFile(I.currentDir, filename); 33 | try { 34 | Stream stream = Files.lines(file.toPath()).skip(skip); 35 | if (limit >= 0) stream = stream.limit(limit); 36 | stream.map(RödaString::of).forEach(out::push); 37 | } catch (IOException e) { 38 | error(e); 39 | } 40 | } 41 | }, Arrays.asList(new Parameter("files", false, STRING)), true, 42 | Arrays.asList( 43 | new Parameter("skip", false, Parser.expressionInt("", 0, 0)), 44 | new Parameter("limit", false, Parser.expressionInt("", 0, -1)) 45 | ))); 46 | 47 | S.setLocal("writeStrings", RödaNativeFunction.of("writeStrings", (typeargs, args, kwargs, scope, in, out) -> { 48 | String filename = args.get(0).str(); 49 | File file = IOUtils.getMaybeRelativeFile(I.currentDir, filename); 50 | try { 51 | PrintWriter writer = new PrintWriter(file); 52 | for (RödaValue input : in) { 53 | writer.print(input.str()); 54 | } 55 | writer.close(); 56 | } catch (IOException e) { 57 | error(e); 58 | } 59 | }, Arrays.asList(new Parameter("file", false, STRING)), false)); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ReducePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkList; 4 | import static org.kaivos.röda.Interpreter.emptyStream; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | import static org.kaivos.röda.RödaValue.FLOATING; 7 | import static org.kaivos.röda.RödaValue.INTEGER; 8 | import static org.kaivos.röda.RödaValue.LIST; 9 | import static org.kaivos.röda.RödaValue.STRING; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import org.kaivos.röda.Interpreter.RödaScope; 17 | import org.kaivos.röda.Parser.ExpressionTree.CType; 18 | import org.kaivos.röda.RödaValue; 19 | import org.kaivos.röda.runtime.Function.Parameter; 20 | import org.kaivos.röda.type.RödaFloating; 21 | import org.kaivos.röda.type.RödaInteger; 22 | import org.kaivos.röda.type.RödaList; 23 | import org.kaivos.röda.type.RödaNativeFunction; 24 | import org.kaivos.röda.type.RödaString; 25 | 26 | public class ReducePopulator { 27 | 28 | private ReducePopulator() {} 29 | 30 | public static void addMinMaxFunction(RödaScope S, String name, boolean min) { 31 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 32 | RödaValue first = kwargs.get("fst"); 33 | if (args.size() == 0) { 34 | RödaValue val = first != null ? first : in.pull(); 35 | if (val == null) { 36 | emptyStream("empty stream"); 37 | return; 38 | } 39 | while (true) { 40 | RödaValue val2 = in.pull(); 41 | if (val2 == null) break; 42 | RödaValue a = min ? val2 : val; 43 | RödaValue b = min ? val : val2; 44 | if (a.callOperator(CType.LT, b).bool()) val = val2; 45 | } 46 | out.push(val); 47 | } 48 | else { 49 | for (RödaValue list : args) { 50 | checkList(name, list); 51 | if (list.list().isEmpty()) { 52 | if (first != null) 53 | out.push(first); 54 | else 55 | emptyStream("empty stream"); 56 | continue; 57 | } 58 | RödaValue val = first != null ? first : list.list().get(0); 59 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) { 60 | RödaValue a = min ? list.list().get(i) : val; 61 | RödaValue b = min ? val : list.list().get(i); 62 | if (a.callOperator(CType.LT, b).bool()) val = list.list().get(i); 63 | } 64 | out.push(val); 65 | } 66 | } 67 | }, Arrays.asList(new Parameter("values", false, LIST)), true, Collections.emptyList(), true)); 68 | } 69 | 70 | public static void populateReduce(RödaScope S) { 71 | S.setLocal("reduce", RödaNativeFunction.of("reduce", (typeargs, args, kwargs, scope, in, out) -> { 72 | if (in.open()) { 73 | in.unpull(args.get(0)); 74 | } 75 | else { 76 | out.push(args.get(0)); 77 | } 78 | }, Arrays.asList(new Parameter("value", false)), false)); 79 | S.setLocal("reduceSteps", RödaNativeFunction.of("reduceSteps", (typeargs, args, kwargs, scope, in, out) -> { 80 | if (in.open()) { 81 | in.unpull(args.get(0)); 82 | } 83 | out.push(args.get(0)); 84 | }, Arrays.asList(new Parameter("value", false)), false)); 85 | S.setLocal("sum", RödaNativeFunction.of("sum", (typeargs, args, kwargs, scope, in, out) -> { 86 | RödaValue first = kwargs.get("fst"); 87 | if (args.size() == 0) { 88 | RödaValue val = first != null ? first : in.pull(); 89 | if (val == null) { 90 | out.push(RödaInteger.of(0)); 91 | return; 92 | } 93 | if (val.is(INTEGER)) { 94 | long sum = val.integer(); 95 | while (true) { 96 | RödaValue val2 = in.pull(); 97 | if (val2 == null) break; 98 | sum += val2.integer(); 99 | } 100 | out.push(RödaInteger.of(sum)); 101 | } 102 | else if (val.is(FLOATING)) { 103 | double sum = val.floating(); 104 | while (true) { 105 | RödaValue val2 = in.pull(); 106 | if (val2 == null) break; 107 | sum += val2.floating(); 108 | } 109 | out.push(RödaFloating.of(sum)); 110 | } 111 | else if (val.is(LIST)) { 112 | List sum = new ArrayList<>(val.list()); 113 | while (true) { 114 | RödaValue val2 = in.pull(); 115 | if (val2 == null) break; 116 | sum.add(val2); 117 | } 118 | out.push(RödaList.of(sum)); 119 | } 120 | else typeMismatch("sum: expected list, integer of floating, got " + val.typeString()); 121 | } 122 | else { 123 | for (RödaValue list : args) { 124 | checkList("sum", list); 125 | if (list.list().isEmpty()) { 126 | out.push(first != null ? first : RödaInteger.of(0)); 127 | continue; 128 | } 129 | RödaValue val = first != null ? first : list.list().get(0); 130 | if (val.is(INTEGER)) { 131 | long sum = val.integer(); 132 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 133 | sum += list.list().get(i).integer(); 134 | out.push(RödaInteger.of(sum)); 135 | } 136 | else if (val.is(FLOATING)) { 137 | double sum = val.floating(); 138 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 139 | sum += list.list().get(i).floating(); 140 | out.push(RödaFloating.of(sum)); 141 | } 142 | else if (val.is(LIST)) { 143 | List sum = new ArrayList<>(val.list()); 144 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 145 | sum.add(list.list().get(i)); 146 | out.push(RödaList.of(sum)); 147 | } 148 | else typeMismatch("sum: expected list, integer or floating, got " + val.typeString()); 149 | } 150 | } 151 | }, Arrays.asList(new Parameter("values", false, LIST)), true, Collections.emptyList(), true)); 152 | S.setLocal("product", RödaNativeFunction.of("product", (typeargs, args, kwargs, scope, in, out) -> { 153 | RödaValue first = kwargs.get("fst"); 154 | if (args.size() == 0) { 155 | RödaValue val = first != null ? first : in.pull(); 156 | if (val == null) { 157 | out.push(RödaInteger.of(1)); 158 | return; 159 | } 160 | if (val.is(INTEGER)) { 161 | long sum = val.integer(); 162 | while (true) { 163 | RödaValue val2 = in.pull(); 164 | if (val2 == null) break; 165 | sum *= val2.integer(); 166 | } 167 | out.push(RödaInteger.of(sum)); 168 | } 169 | else if (val.is(FLOATING)) { 170 | double sum = val.floating(); 171 | while (true) { 172 | RödaValue val2 = in.pull(); 173 | if (val2 == null) break; 174 | sum *= val2.floating(); 175 | } 176 | out.push(RödaFloating.of(sum)); 177 | } 178 | else typeMismatch("product: expected integer of floating, got " + val.typeString()); 179 | } 180 | else { 181 | for (RödaValue list : args) { 182 | checkList("sum", list); 183 | if (list.list().isEmpty()) { 184 | out.push(first != null ? first : RödaInteger.of(1)); 185 | continue; 186 | } 187 | RödaValue val = first != null ? first : list.list().get(0); 188 | if (val.is(INTEGER)) { 189 | long sum = val.integer(); 190 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 191 | sum *= list.list().get(i).integer(); 192 | out.push(RödaInteger.of(sum)); 193 | } 194 | else if (val.is(FLOATING)) { 195 | double sum = val.floating(); 196 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 197 | sum *= list.list().get(i).floating(); 198 | out.push(RödaFloating.of(sum)); 199 | } 200 | else typeMismatch("product: expected integer or floating, got " + val.typeString()); 201 | } 202 | } 203 | }, Arrays.asList(new Parameter("values", false, LIST)), true, Collections.emptyList(), true)); 204 | S.setLocal("concat", RödaNativeFunction.of("concat", (typeargs, args, kwargs, scope, in, out) -> { 205 | RödaValue first = kwargs.get("fst"); 206 | if (args.size() == 0) { 207 | RödaValue val = first != null ? first : in.pull(); 208 | if (val == null) { 209 | out.push(RödaList.empty()); 210 | return; 211 | } 212 | if (val.is(LIST)) { 213 | List sum = new ArrayList<>(val.list()); 214 | while (true) { 215 | RödaValue val2 = in.pull(); 216 | if (val2 == null) break; 217 | sum.addAll(val2.list()); 218 | } 219 | out.push(RödaList.of(sum)); 220 | } 221 | else if (val.is(STRING)) { 222 | StringBuilder sum = new StringBuilder(val.str()); 223 | while (true) { 224 | RödaValue val2 = in.pull(); 225 | if (val2 == null) break; 226 | sum.append(val2.str()); 227 | } 228 | out.push(RödaString.of(sum.toString())); 229 | } 230 | else typeMismatch("concat: expected list or string, got " + val.typeString()); 231 | } 232 | else { 233 | for (RödaValue list : args) { 234 | checkList("concat", list); 235 | if (list.list().isEmpty()) { 236 | out.push(first == null ? RödaList.empty() : RödaList.of(first)); 237 | continue; 238 | } 239 | RödaValue val = first != null ? first : list.list().get(0); 240 | if (val.is(LIST)) { 241 | List sum = new ArrayList<>(val.list()); 242 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 243 | sum.addAll(list.list().get(i).list()); 244 | out.push(RödaList.of(sum)); 245 | } 246 | else if (val.is(STRING)) { 247 | StringBuilder sum = new StringBuilder(val.str()); 248 | for (int i = first != null ? 0 : 1; i < list.list().size(); i++) 249 | sum.append(list.list().get(i).str()); 250 | out.push(RödaString.of(sum.toString())); 251 | } 252 | else typeMismatch("concat: expected list or string, got " + val.typeString()); 253 | } 254 | } 255 | }, Arrays.asList(new Parameter("values", false, LIST)), true, Collections.emptyList(), true)); 256 | addMinMaxFunction(S, "min", true); 257 | addMinMaxFunction(S, "max", false); 258 | } 259 | 260 | } -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ReplacePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.error; 4 | import static org.kaivos.röda.Interpreter.illegalArguments; 5 | import static org.kaivos.röda.RödaValue.STRING; 6 | 7 | import java.util.Arrays; 8 | import java.util.regex.PatternSyntaxException; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.RödaValue; 12 | import org.kaivos.röda.runtime.Function.Parameter; 13 | import org.kaivos.röda.type.RödaNativeFunction; 14 | import org.kaivos.röda.type.RödaString; 15 | 16 | public final class ReplacePopulator { 17 | 18 | private ReplacePopulator() {} 19 | 20 | public static void populateReplace(RödaScope S) { 21 | S.setLocal("replace", RödaNativeFunction.of("replace", (typeargs, args, kwargs, scope, in, out) -> { 22 | if (args.size() % 2 != 0) 23 | illegalArguments("invalid arguments for replace: even number required (got " + args.size() + ")"); 24 | try { 25 | while (true) { 26 | RödaValue input = in.pull(); 27 | if (input == null) break; 28 | 29 | String text = input.str(); 30 | for (int i = 0; i < args.size(); i += 2) { 31 | String pattern = args.get(i).str(); 32 | String replacement = args.get(i + 1).str(); 33 | text = text.replaceAll(pattern, replacement); 34 | } 35 | out.push(RödaString.of(text)); 36 | } 37 | } catch (PatternSyntaxException e) { 38 | error("replace: pattern syntax exception: " + e.getMessage()); 39 | } 40 | }, Arrays.asList(new Parameter("patterns_and_replacements", false, STRING)), true)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/SearchPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentUnderflow; 4 | import static org.kaivos.röda.Interpreter.checkString; 5 | import static org.kaivos.röda.RödaValue.STRING; 6 | 7 | import java.util.Arrays; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.RödaValue; 13 | import org.kaivos.röda.runtime.Function.Parameter; 14 | import org.kaivos.röda.type.RödaNativeFunction; 15 | import org.kaivos.röda.type.RödaString; 16 | 17 | public final class SearchPopulator { 18 | 19 | private SearchPopulator() {} 20 | 21 | public static void populateSearch(RödaScope S) { 22 | S.setLocal("search", RödaNativeFunction.of("search", (typeargs, args, kwargs, scope, in, out) -> { 23 | if (args.size() < 1) 24 | argumentUnderflow("search", 1, 0); 25 | while (true) { 26 | RödaValue input = in.pull(); 27 | if (input == null) break; 28 | 29 | String text = input.str(); 30 | for (RödaValue value : args) { 31 | checkString("search", value); 32 | Pattern pattern = Pattern.compile(value.str()); 33 | Matcher m = pattern.matcher(text); 34 | while (m.find()) { 35 | out.push(RödaString.of(m.group())); 36 | } 37 | } 38 | } 39 | }, Arrays.asList(new Parameter("patterns", false, STRING)), true)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/SeqPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.outOfBounds; 4 | import static org.kaivos.röda.Parser.expressionInt; 5 | import static org.kaivos.röda.RödaValue.BOOLEAN; 6 | import static org.kaivos.röda.RödaValue.INTEGER; 7 | 8 | import java.util.Arrays; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.runtime.Function.Parameter; 12 | import org.kaivos.röda.type.RödaInteger; 13 | import org.kaivos.röda.type.RödaNativeFunction; 14 | 15 | public final class SeqPopulator { 16 | 17 | private SeqPopulator() {} 18 | 19 | public static void populateSeq(RödaScope S) { 20 | S.setLocal("seq", RödaNativeFunction.of("seq", (typeargs, args, kwargs, scope, in, out) -> { 21 | long from = args.get(0).integer(); 22 | long to = args.get(1).integer(); 23 | long step = kwargs.get("step").integer(); 24 | if (to < from && step > 0 || from < to && step < 0) step = -step; 25 | if (step > 0) { 26 | for (long i = from; i <= to; i += step) 27 | out.push(RödaInteger.of(i)); 28 | } 29 | else if (step < 0) { 30 | for (long i = from; i >= to; i += step) 31 | out.push(RödaInteger.of(i)); 32 | } 33 | else { 34 | outOfBounds("illegal use of seq: step must be non-zero"); 35 | } 36 | }, Arrays.asList(new Parameter("from", false, INTEGER), new Parameter("to", false, INTEGER)), false, 37 | Arrays.asList(new Parameter("step", false, expressionInt("", 0, 1))))); 38 | 39 | S.setLocal("makeSeq", RödaNativeFunction.of("makeSeq", (typeargs, args, kwargs, scope, in, out) -> { 40 | if (args.get(0).bool()) { 41 | out.push(args.get(args.size()-1)); 42 | for (int i = args.size()-1; i >= 1; i--) { 43 | in.unpull(args.get(i)); 44 | } 45 | } 46 | }, Arrays.asList(new Parameter("cond", false, BOOLEAN), new Parameter("first_value", false), new Parameter("other_values", false)), true)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ServerPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkArgs; 4 | import static org.kaivos.röda.Interpreter.error; 5 | import static org.kaivos.röda.Interpreter.outOfBounds; 6 | import static org.kaivos.röda.RödaValue.INTEGER; 7 | import static org.kaivos.röda.RödaValue.STRING; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.net.ServerSocket; 13 | import java.net.Socket; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | 17 | import org.kaivos.röda.Builtins; 18 | import org.kaivos.röda.Interpreter; 19 | import org.kaivos.röda.Interpreter.RödaScope; 20 | import org.kaivos.röda.RödaValue; 21 | import org.kaivos.röda.runtime.Function.Parameter; 22 | import org.kaivos.röda.runtime.Datatype; 23 | import org.kaivos.röda.runtime.Record; 24 | import org.kaivos.röda.type.RödaInteger; 25 | import org.kaivos.röda.type.RödaNativeFunction; 26 | import org.kaivos.röda.type.RödaRecordInstance; 27 | import org.kaivos.röda.type.RödaString; 28 | 29 | public final class ServerPopulator { 30 | 31 | private ServerPopulator() { 32 | } 33 | 34 | private static Record socketRecord; 35 | 36 | private static RödaValue createSocketObj(Socket socket, Interpreter I) { 37 | InputStream _in; 38 | OutputStream _out; 39 | try { 40 | _in = socket.getInputStream(); 41 | _out = socket.getOutputStream(); 42 | } catch (IOException e) { 43 | error(e); 44 | return null; 45 | } 46 | RödaValue socketObject = RödaRecordInstance.of(socketRecord, Collections.emptyList()); 47 | socketObject.setField("readBytes", Builtins.genericReadBytesOrString("Socket.readBytes", _in, I, false)); 48 | socketObject.setField("readString", Builtins.genericReadBytesOrString("Socket.readString", _in, I, true)); 49 | socketObject.setField("readLine", Builtins.genericReadLine("Socket.readLine", _in, I)); 50 | socketObject.setField("writeStrings", Builtins.genericWriteStrings("Socket.writeStrings", _out, I)); 51 | socketObject.setField("writeFile", Builtins.genericWriteFile("Socket.writeFile", _out, I)); 52 | socketObject.setField("close", RödaNativeFunction.of("Socket.close", (r, A, K, z, j, u) -> { 53 | checkArgs("Socket.close", 0, A.size()); 54 | try { 55 | _out.close(); 56 | _in.close(); 57 | socket.close(); 58 | } catch (IOException e) { 59 | error(e); 60 | } 61 | }, Collections.emptyList(), false)); 62 | socketObject.setField("ip", RödaString.of(socket.getInetAddress().getHostAddress())); 63 | socketObject.setField("hostname", RödaString.of(socket.getInetAddress().getCanonicalHostName())); 64 | socketObject.setField("port", RödaInteger.of(socket.getPort())); 65 | socketObject.setField("localport", RödaInteger.of(socket.getLocalPort())); 66 | return socketObject; 67 | } 68 | 69 | public static void populateServer(Interpreter I, RödaScope S) { 70 | Record serverRecord = new Record("Server", Collections.emptyList(), Collections.emptyList(), 71 | Arrays.asList(new Record.Field("accept", new Datatype("function")), 72 | new Record.Field("close", new Datatype("function"))), 73 | false, I.G); 74 | I.G.preRegisterRecord(serverRecord); 75 | 76 | socketRecord = new Record("Socket", Collections.emptyList(), Collections.emptyList(), Arrays.asList( 77 | new Record.Field("writeStrings", new Datatype("function")), 78 | new Record.Field("writeFile", new Datatype("function")), 79 | new Record.Field("readBytes", new Datatype("function")), 80 | new Record.Field("readString", new Datatype("function")), 81 | new Record.Field("readLine", new Datatype("function")), 82 | new Record.Field("close", new Datatype("function")), new Record.Field("ip", new Datatype("string")), 83 | new Record.Field("hostname", new Datatype("string")), new Record.Field("port", new Datatype("number")), 84 | new Record.Field("localport", new Datatype("number"))), false, I.G); 85 | I.G.preRegisterRecord(socketRecord); 86 | 87 | I.G.postRegisterRecord(serverRecord); 88 | I.G.postRegisterRecord(socketRecord); 89 | 90 | S.setLocal("server", RödaNativeFunction.of("server", (typeargs, args, kwargs, scope, in, out) -> { 91 | long port = args.get(0).integer(); 92 | if (port > Integer.MAX_VALUE) 93 | outOfBounds("can't open port greater than " + Integer.MAX_VALUE); 94 | if (port < 0) 95 | outOfBounds("can't open port less than 0"); 96 | 97 | try { 98 | 99 | ServerSocket server = new ServerSocket((int) port); 100 | 101 | RödaValue serverObject = RödaRecordInstance.of(serverRecord, Collections.emptyList()); 102 | serverObject.setField("accept", RödaNativeFunction.of("Server.accept", (ra, a, k, s, i, o) -> { 103 | checkArgs("Server.accept", 0, a.size()); 104 | Socket socket; 105 | try { 106 | socket = server.accept(); 107 | } catch (IOException e) { 108 | error(e); 109 | return; 110 | } 111 | o.push(createSocketObj(socket, I)); 112 | }, Collections.emptyList(), false)); 113 | serverObject.setField("close", RödaNativeFunction.of("Server.close", (ra, a, k, s, i, o) -> { 114 | checkArgs("Server.close", 0, a.size()); 115 | try { 116 | server.close(); 117 | } catch (Exception e) { 118 | error(e); 119 | } 120 | }, Collections.emptyList(), false)); 121 | out.push(serverObject); 122 | } catch (IOException e) { 123 | error(e); 124 | } 125 | }, Arrays.asList(new Parameter("port", false, INTEGER)), false)); 126 | 127 | S.setLocal("connect", RödaNativeFunction.of("connect", (typeargs, args, kwargs, scope, in, out) -> { 128 | String host = args.get(0).str(); 129 | long port = args.get(1).integer(); 130 | if (port > Integer.MAX_VALUE) 131 | outOfBounds("can't open port greater than " + Integer.MAX_VALUE); 132 | if (port < 0) 133 | outOfBounds("can't open port less than 0"); 134 | 135 | try { 136 | Socket socket = new Socket(host, (int) port); 137 | out.push(createSocketObj(socket, I)); 138 | } catch (IOException e) { 139 | error(e); 140 | } 141 | }, Arrays.asList(new Parameter("host", false, STRING), new Parameter("port", false, INTEGER)), false)); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ShiftPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkList; 4 | import static org.kaivos.röda.Interpreter.illegalArguments; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.kaivos.röda.Interpreter.RödaScope; 9 | import org.kaivos.röda.RödaValue; 10 | import org.kaivos.röda.runtime.Function.Parameter; 11 | import org.kaivos.röda.type.RödaNativeFunction; 12 | 13 | public final class ShiftPopulator { 14 | 15 | private ShiftPopulator() {} 16 | 17 | public static final void populateShift(RödaScope S) { 18 | S.setLocal("shift", RödaNativeFunction.of("shift", (typeargs, args, kwargs, scope, in, out) -> { 19 | for (RödaValue list : args) { 20 | checkList("shift", list); 21 | if (list.list().isEmpty()) illegalArguments("illegal use of shift: all lists must be non-empty"); 22 | list.modifiableList().remove(0); 23 | } 24 | }, Arrays.asList(new Parameter("first_list", false), new Parameter("other_lists", false)), true)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/SortPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.argumentOverflow; 4 | import static org.kaivos.röda.Interpreter.emptyStream; 5 | import static org.kaivos.röda.Interpreter.fullStream; 6 | import static org.kaivos.röda.Interpreter.illegalArguments; 7 | import static org.kaivos.röda.Interpreter.outOfBounds; 8 | import static org.kaivos.röda.Interpreter.typeMismatch; 9 | import static org.kaivos.röda.RödaValue.BOOLEAN; 10 | import static org.kaivos.röda.RödaValue.INTEGER; 11 | import static org.kaivos.röda.RödaValue.LIST; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import org.kaivos.röda.Interpreter; 19 | import org.kaivos.röda.Interpreter.RödaScope; 20 | import org.kaivos.röda.Parser.ExpressionTree.CType; 21 | import org.kaivos.röda.RödaStream; 22 | import org.kaivos.röda.RödaValue; 23 | import org.kaivos.röda.runtime.Function.Parameter; 24 | import org.kaivos.röda.type.RödaNativeFunction; 25 | 26 | public class SortPopulator { 27 | 28 | private SortPopulator() {} 29 | 30 | private static RödaValue evalKey(Interpreter I, RödaValue key, RödaValue arg) { 31 | RödaStream in = RödaStream.makeEmptyStream(); 32 | RödaStream out = RödaStream.makeStream(); 33 | I.exec("", 0, 34 | key, 35 | Collections.emptyList(), Arrays.asList(arg), Collections.emptyMap(), 36 | new RödaScope(I.G), in, out); 37 | out.finish(); 38 | RödaValue retval = null; 39 | while (true) { 40 | if (retval != null) fullStream("stream is full (in "); 41 | retval = out.pull(); 42 | if (retval == null) emptyStream("empty stream (in )"); 43 | return retval; 44 | } 45 | } 46 | 47 | private static int evalCmp(Interpreter I, RödaValue cmp, RödaValue a, RödaValue b) { 48 | RödaStream in = RödaStream.makeEmptyStream(); 49 | RödaStream out = RödaStream.makeStream(); 50 | I.exec("", 0, 51 | cmp, 52 | Collections.emptyList(), Arrays.asList(a, b), Collections.emptyMap(), 53 | new RödaScope(I.G), in, out); 54 | out.finish(); 55 | boolean useBool = false, retval = true; 56 | while (true) { 57 | RödaValue val = out.pull(); 58 | if (val == null) break; 59 | if (!useBool && val.is(INTEGER)) { 60 | long l = val.integer(); 61 | if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) 62 | outOfBounds("comparator returned an illegal integer: " + l + " (not in supported range " 63 | + Integer.MIN_VALUE + ".." + Integer.MAX_VALUE + ")"); 64 | return (int) l; 65 | } 66 | else useBool = true; 67 | if (!val.is(BOOLEAN)) 68 | typeMismatch("comparator returned a value of type '" + val.typeString() 69 | + "', it should only return a single integer or an arbitrary number of boolean values"); 70 | retval &= val.bool(); 71 | } 72 | return retval ? -1 : 1; 73 | } 74 | 75 | public static void populateSort(Interpreter I, RödaScope S) { 76 | S.setLocal("sort", RödaNativeFunction.of("sort", (typeargs, args, kwargs, scope, in, out) -> { 77 | if (args.size() > 1) 78 | argumentOverflow("head", 1, args.size()); 79 | List list; 80 | if (args.size() == 0) { 81 | list = new ArrayList<>(); 82 | in.forAll(list::add); 83 | } else { 84 | list = new ArrayList<>(args.get(0).list()); 85 | } 86 | if (kwargs.containsKey("key") && kwargs.containsKey("cmp")) { 87 | illegalArguments("received both 'key' and 'cmp', only one should be provided"); 88 | } 89 | if (kwargs.containsKey("key")) { 90 | RödaValue key = kwargs.get("key"); 91 | list.sort((a, b) -> { 92 | a = evalKey(I, key, a); 93 | b = evalKey(I, key, b); 94 | return a.callOperator(CType.LT, b).bool() ? -1 : a.strongEq(b) ? 0 : 1; 95 | }); 96 | } 97 | else if (kwargs.containsKey("cmp")) { 98 | RödaValue cmp = kwargs.get("cmp"); 99 | list.sort((a, b) -> { 100 | return evalCmp(I, cmp, a, b); 101 | }); 102 | } 103 | else { 104 | list.sort((a, b) -> a.callOperator(CType.LT, b).bool() ? -1 : a.strongEq(b) ? 0 : 1); 105 | } 106 | list.forEach(out::push); 107 | }, Arrays.asList(new Parameter("number", false, LIST)), true, 108 | Collections.emptyList(), true)); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/SplitPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.kaivos.röda.Interpreter.checkString; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.kaivos.röda.Interpreter.RödaScope; 9 | import org.kaivos.röda.Parser; 10 | import org.kaivos.röda.RödaStream; 11 | import org.kaivos.röda.RödaValue; 12 | import org.kaivos.röda.runtime.Function.Parameter; 13 | import org.kaivos.röda.type.RödaList; 14 | import org.kaivos.röda.type.RödaNativeFunction; 15 | import org.kaivos.röda.type.RödaString; 16 | 17 | public final class SplitPopulator { 18 | 19 | private SplitPopulator() {} 20 | 21 | private static interface Splitter { 22 | void pushSeparation(String str, String separator, RödaStream out); 23 | } 24 | 25 | private static void pushCollectedSeparation(String str, String separator, RödaStream out) { 26 | out.push(RödaList.of(Arrays.asList(str.split(separator)).stream().map(RödaString::of).collect(toList()))); 27 | } 28 | 29 | private static void pushUncollectedSeparation(String str, String separator, RödaStream out) { 30 | for (String s : str.split(separator)) { 31 | out.push(RödaString.of(s)); 32 | } 33 | } 34 | 35 | public static void addSplitter(RödaScope S, String name, Splitter s) { 36 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 37 | String separator = kwargs.get("sep").str(); 38 | if (args.size() > 0) { 39 | for (int i = 0; i < args.size(); i++) { 40 | RödaValue value = args.get(i); 41 | checkString(name, value); 42 | String str = value.str(); 43 | s.pushSeparation(str, separator, out); 44 | } 45 | } 46 | else { 47 | while (true) { 48 | RödaValue value = in.pull(); 49 | if (value == null) break; 50 | 51 | checkString(name, value); 52 | String str = value.str(); 53 | s.pushSeparation(str, separator, out); 54 | } 55 | } 56 | }, 57 | Arrays.asList(new Parameter("strings", false)), 58 | true, 59 | Arrays.asList( 60 | new Parameter("sep", false, Parser.expressionString("", 0, " ")) 61 | ))); 62 | } 63 | 64 | public static void populateSplit(RödaScope S) { 65 | addSplitter(S, "split", SplitPopulator::pushUncollectedSeparation); 66 | addSplitter(S, "splitMany", SplitPopulator::pushCollectedSeparation); 67 | S.setLocal("chars", RödaNativeFunction.of("chars", (typeargs, args, kwargs, scope, in, out) -> { 68 | if (args.size() > 0) { 69 | for (int i = 0; i < args.size(); i++) { 70 | RödaValue value = args.get(i); 71 | checkString("chars", value); 72 | String str = value.str(); 73 | str.chars().mapToObj(c -> RödaString.of(new String(Character.toChars(c)))).forEach(out::push);; 74 | } 75 | } 76 | else { 77 | while (true) { 78 | RödaValue value = in.pull(); 79 | if (value == null) break; 80 | 81 | checkString("chars", value); 82 | String str = value.str(); 83 | str.chars().mapToObj(c -> RödaString.of(new String(Character.toChars(c)))).forEach(out::push);; 84 | } 85 | } 86 | }, 87 | Arrays.asList(new Parameter("strings", false)), 88 | true)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/StreamPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | 6 | import org.kaivos.röda.Builtins; 7 | import org.kaivos.röda.Interpreter; 8 | import org.kaivos.röda.Interpreter.RödaScope; 9 | import org.kaivos.röda.RödaStream; 10 | import org.kaivos.röda.RödaValue; 11 | import org.kaivos.röda.runtime.Datatype; 12 | import org.kaivos.röda.runtime.Function.Parameter; 13 | import org.kaivos.röda.runtime.Record; 14 | import org.kaivos.röda.type.RödaNativeFunction; 15 | import org.kaivos.röda.type.RödaRecordInstance; 16 | 17 | public final class StreamPopulator { 18 | 19 | private StreamPopulator() { 20 | } 21 | 22 | private static Record streamRecord = null; 23 | 24 | public static RödaValue createStreamObj(RödaStream stream) { 25 | if (streamRecord == null) throw new RuntimeException("Stream record isn't created yet"); 26 | 27 | RödaValue streamObject = RödaRecordInstance.of(streamRecord, Collections.emptyList()); 28 | 29 | streamObject.setField("pull", Builtins.genericPull("Stream.pull", stream, false, true)); 30 | streamObject.setField("tryPull", Builtins.genericTryPull("Stream.tryPull", stream, false)); 31 | streamObject.setField("pullAll", Builtins.genericPull("Stream.pullAll", stream, false, false)); 32 | 33 | streamObject.setField("peek", Builtins.genericPull("Stream.peek", stream, true, true)); 34 | streamObject.setField("tryPeek", Builtins.genericTryPull("Stream.tryPeek", stream, true)); 35 | 36 | streamObject.setField("push", Builtins.genericPush("Stream.push", stream, false)); 37 | streamObject.setField("unpull", Builtins.genericPush("Stream.unpull", stream, true)); 38 | streamObject.setField("finish", RödaNativeFunction.of("Stream.finish", (ta, a, k, s, i, o) -> { 39 | stream.finish(); 40 | }, Collections.emptyList(), false)); 41 | return streamObject; 42 | } 43 | 44 | public static void populateStream(Interpreter I, RödaScope S) { 45 | streamRecord = new Record("Stream", Collections.emptyList(), Collections.emptyList(), 46 | Arrays.asList( 47 | new Record.Field("pull", new Datatype("function")), 48 | new Record.Field("tryPull", new Datatype("function")), 49 | new Record.Field("pullAll", new Datatype("function")), 50 | new Record.Field("peek", new Datatype("function")), 51 | new Record.Field("tryPeek", new Datatype("function")), 52 | new Record.Field("push", new Datatype("function")), 53 | new Record.Field("unpull", new Datatype("function")), 54 | new Record.Field("finish", new Datatype("function"))), 55 | false, I.G); 56 | I.G.preRegisterRecord(streamRecord); 57 | I.G.postRegisterRecord(streamRecord); 58 | 59 | S.setLocal("stream", RödaNativeFunction.of("stream", (typeargs, args, kwargs, scope, in, out) -> { 60 | if (args.size() == 0) { 61 | out.push(createStreamObj(RödaStream.makeStream())); 62 | return; 63 | } 64 | for (RödaValue ref : args) { 65 | ref.assignLocal(createStreamObj(RödaStream.makeStream())); 66 | } 67 | }, Arrays.asList(new Parameter("variables", true)), true)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/StrsizePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkString; 4 | import static org.kaivos.röda.RödaValue.STRING; 5 | 6 | import java.nio.charset.Charset; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Arrays; 9 | import java.util.function.Consumer; 10 | 11 | import org.kaivos.röda.Interpreter.RödaScope; 12 | import org.kaivos.röda.RödaValue; 13 | import org.kaivos.röda.runtime.Function.Parameter; 14 | import org.kaivos.röda.type.RödaInteger; 15 | import org.kaivos.röda.type.RödaNativeFunction; 16 | 17 | public final class StrsizePopulator { 18 | 19 | private StrsizePopulator() {} 20 | 21 | public static void populateStrsize(RödaScope S) { 22 | S.setLocal("strsize", RödaNativeFunction.of("strsize", (typeargs, args, kwargs, scope, in, out) -> { 23 | Charset chrset = StandardCharsets.UTF_8; 24 | Consumer convert = v -> { 25 | checkString("strsize", v); 26 | out.push(RödaInteger.of(v.str().getBytes(chrset).length)); 27 | }; 28 | if (args.size() > 0) { 29 | args.forEach(convert); 30 | } else { 31 | in.forAll(convert); 32 | } 33 | }, Arrays.asList(new Parameter("strings", false, STRING)), true)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/ThreadPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkArgs; 4 | import static org.kaivos.röda.Interpreter.error; 5 | import static org.kaivos.röda.RödaValue.FUNCTION; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | 10 | import org.kaivos.röda.Builtins; 11 | import org.kaivos.röda.Interpreter; 12 | import org.kaivos.röda.Interpreter.RödaException; 13 | import org.kaivos.röda.Interpreter.RödaScope; 14 | import org.kaivos.röda.RödaStream; 15 | import org.kaivos.röda.RödaValue; 16 | import org.kaivos.röda.runtime.Function.Parameter; 17 | import org.kaivos.röda.runtime.Datatype; 18 | import org.kaivos.röda.runtime.Record; 19 | import org.kaivos.röda.type.RödaNativeFunction; 20 | import org.kaivos.röda.type.RödaRecordInstance; 21 | 22 | public final class ThreadPopulator { 23 | 24 | private ThreadPopulator() {} 25 | 26 | public static void populateThread(Interpreter I, RödaScope S) { 27 | Record threadRecord = new Record("Thread", Collections.emptyList(), Collections.emptyList(), 28 | Arrays.asList(new Record.Field("start", new Datatype("function")), 29 | new Record.Field("finish", new Datatype("function")), 30 | new Record.Field("pull", new Datatype("function")), 31 | new Record.Field("tryPull", new Datatype("function")), 32 | new Record.Field("pullAll", new Datatype("function")), 33 | new Record.Field("peek", new Datatype("function")), 34 | new Record.Field("tryPeek", new Datatype("function")), 35 | new Record.Field("push", new Datatype("function"))), 36 | false, I.G); 37 | I.G.preRegisterRecord(threadRecord); 38 | I.G.postRegisterRecord(threadRecord); 39 | 40 | S.setLocal("thread", RödaNativeFunction.of("thread", (typeargs, args, kwargs, scope, in, out) -> { 41 | RödaValue function = args.get(0); 42 | 43 | RödaScope newScope = !function.is(RödaValue.NFUNCTION) && function.localScope() != null 44 | ? new RödaScope(function.localScope()) : new RödaScope(I.G); 45 | RödaStream _in = RödaStream.makeStream(); 46 | RödaStream _out = RödaStream.makeStream(); 47 | 48 | class P { 49 | boolean started = false; 50 | } 51 | P p = new P(); 52 | 53 | Runnable task = () -> { 54 | try { 55 | I.exec("", 0, function, 56 | Collections.emptyList(), Collections.emptyList(), Collections.emptyMap(), 57 | newScope, _in, _out); 58 | } catch (RödaException e) { 59 | System.err.println("[E] " + e.getMessage()); 60 | for (String step : e.getStack()) { 61 | System.err.println(step); 62 | } 63 | if (e.getCause() != null) 64 | e.getCause().printStackTrace(); 65 | } 66 | _out.finish(); 67 | }; 68 | 69 | RödaValue threadObject = RödaRecordInstance.of(threadRecord, Collections.emptyList()); 70 | threadObject.setField("start", RödaNativeFunction.of("Thread.start", (ra, a, k, s, i, o) -> { 71 | checkArgs("Thread.start", 0, a.size()); 72 | if (p.started) 73 | error("Thread has already been started"); 74 | p.started = true; 75 | Interpreter.executor.execute(task); 76 | }, Collections.emptyList(), false)); 77 | threadObject.setField("finish", RödaNativeFunction.of("Thread.finish", (ra, a, k, s, i, o) -> { 78 | checkArgs("Thread.finish", 0, a.size()); 79 | _in.finish(); 80 | }, Collections.emptyList(), false)); 81 | threadObject.setField("pull", Builtins.genericPull("Thread.pull", _out, false, true)); 82 | threadObject.setField("tryPull", Builtins.genericTryPull("Thread.tryPull", _out, false)); 83 | threadObject.setField("pullAll", Builtins.genericPull("Thread.pullAll", _out, false, false)); 84 | 85 | threadObject.setField("peek", Builtins.genericPull("Thread.peek", _out, true, true)); 86 | threadObject.setField("tryPeek", Builtins.genericTryPull("Thread.tryPeek", _out, true)); 87 | 88 | threadObject.setField("push", Builtins.genericPush("Thread.push", _in, false)); 89 | out.push(threadObject); 90 | }, Arrays.asList(new Parameter("runnable", false, FUNCTION)), false)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/TrueAndFalsePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.kaivos.röda.Interpreter.RödaScope; 6 | import org.kaivos.röda.type.RödaBoolean; 7 | import org.kaivos.röda.type.RödaNativeFunction; 8 | 9 | public final class TrueAndFalsePopulator { 10 | 11 | private TrueAndFalsePopulator() {} 12 | 13 | public static void populateTrueAndFalse(RödaScope S) { 14 | S.setLocal("true", RödaNativeFunction.of("true", (typeargs, args, kwargs, scope, in, out) -> { 15 | out.push(RödaBoolean.of(true)); 16 | }, Arrays.asList(), false)); 17 | 18 | S.setLocal("false", RödaNativeFunction.of("false", (typeargs, args, kwargs, scope, in, out) -> { 19 | out.push(RödaBoolean.of(false)); 20 | }, Arrays.asList(), false)); 21 | 22 | S.setLocal("TRUE", RödaBoolean.of(true)); 23 | S.setLocal("FALSE", RödaBoolean.of(false)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/UndefinePopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.checkReference; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.kaivos.röda.Interpreter.RödaScope; 8 | import org.kaivos.röda.RödaValue; 9 | import org.kaivos.röda.runtime.Function.Parameter; 10 | import org.kaivos.röda.type.RödaNativeFunction; 11 | 12 | public final class UndefinePopulator { 13 | 14 | private UndefinePopulator() {} 15 | 16 | public static void populateUndefine(RödaScope S) { 17 | S.setLocal("undefine", RödaNativeFunction.of("undefine", (typeargs, args, kwargs, scope, in, out) -> { 18 | for (RödaValue value : args) { 19 | checkReference("undefine", value); 20 | 21 | value.assign(null); 22 | } 23 | }, Arrays.asList(new Parameter("variables", true)), true)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/UniqPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.Set; 9 | 10 | import org.kaivos.röda.Interpreter.RödaScope; 11 | import org.kaivos.röda.RödaValue; 12 | import org.kaivos.röda.type.RödaInteger; 13 | import org.kaivos.röda.type.RödaNativeFunction; 14 | 15 | public class UniqPopulator { 16 | 17 | private UniqPopulator() {} 18 | 19 | private static class MutableInt { int i = 1; }; 20 | private static class MutableValue { RödaValue v = null; }; 21 | 22 | private static void addUniqFunction(RödaScope S, String name, boolean count) { 23 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 24 | MutableValue previous = new MutableValue(); 25 | MutableInt i = new MutableInt(); 26 | in.forAll(value -> { 27 | if (previous.v == null || !value.strongEq(previous.v)){ 28 | if (count && previous.v != null) out.push(RödaInteger.of(i.i)); 29 | out.push(value); 30 | i.i = 1; 31 | } 32 | else { 33 | i.i++; 34 | } 35 | previous.v = value; 36 | }); 37 | if (count) out.push(RödaInteger.of(i.i)); 38 | }, Collections.emptyList(), false)); 39 | } 40 | 41 | private static void addUnorderedUniqFunction(RödaScope S, String name, boolean count) { 42 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 43 | Map counts = new HashMap<>(); 44 | in.forAll(value -> { 45 | if (counts.containsKey(value)) { 46 | counts.put(value, Integer.valueOf(counts.get(value).intValue() + 1)); 47 | } 48 | else { 49 | counts.put(value, Integer.valueOf(1)); 50 | } 51 | }); 52 | if (count) { 53 | for (Entry entry : counts.entrySet()) { 54 | out.push(entry.getKey()); 55 | out.push(RödaInteger.of(entry.getValue())); 56 | } 57 | } 58 | else { 59 | counts.keySet().forEach(out::push); 60 | } 61 | }, Collections.emptyList(), false)); 62 | } 63 | 64 | private static void addOrderedUniqFunction(RödaScope S, String name) { 65 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 66 | Set counts = new HashSet<>(); 67 | in.forAll(value -> { 68 | if (!counts.contains(value)) { 69 | counts.add(value); 70 | out.push(value); 71 | } 72 | }); 73 | }, Collections.emptyList(), false)); 74 | } 75 | 76 | public static void populateUniq(RödaScope S) { 77 | addUniqFunction(S, "uniq", false); 78 | addUniqFunction(S, "count", true); 79 | addUnorderedUniqFunction(S, "unorderedUniq", false); 80 | addUnorderedUniqFunction(S, "unorderedCount", true); 81 | addOrderedUniqFunction(S, "orderedUniq"); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/commands/WcatPopulator.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.commands; 2 | 3 | import static org.kaivos.röda.Interpreter.error; 4 | import static org.kaivos.röda.Interpreter.illegalArguments; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | import static org.kaivos.röda.RödaValue.STRING; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.net.URLConnection; 14 | import java.nio.file.Files; 15 | import java.nio.file.StandardCopyOption; 16 | import java.util.Arrays; 17 | import java.util.Collections; 18 | 19 | import org.kaivos.röda.IOUtils; 20 | import org.kaivos.röda.Interpreter.RödaScope; 21 | import org.kaivos.röda.Parser; 22 | import org.kaivos.röda.Röda; 23 | import org.kaivos.röda.RödaValue; 24 | import org.kaivos.röda.runtime.Function.Parameter; 25 | import org.kaivos.röda.type.RödaNativeFunction; 26 | import org.kaivos.röda.type.RödaString; 27 | 28 | public final class WcatPopulator { 29 | 30 | private WcatPopulator() {} 31 | 32 | private static void addResourceLoader(RödaScope S, String name, boolean saveToFile) { 33 | S.setLocal(name, RödaNativeFunction.of(name, (typeargs, args, kwargs, scope, in, out) -> { 34 | try { 35 | String useragent = kwargs.get("ua").str(); 36 | 37 | String arg = args.get(0).str(); 38 | 39 | URL url = new URL(arg); 40 | URLConnection c = url.openConnection(); 41 | RödaValue headers = kwargs.get("headers"); 42 | if (headers.is(RödaValue.LIST)) { 43 | for (RödaValue v : headers.list()) { 44 | String pair = v.str(); 45 | if (pair.indexOf(":") < 0) 46 | illegalArguments("malformed http header field: no colon"); 47 | String fieldName = pair.substring(0, pair.indexOf(":")); 48 | String fieldValue = pair.substring(pair.indexOf(":")+1); 49 | if (fieldValue.length() > 0 && fieldValue.charAt(0) == ' ') 50 | fieldValue = fieldValue.substring(1); 51 | c.setRequestProperty(fieldName, fieldValue); 52 | } 53 | } 54 | else if (headers.is(RödaValue.MAP)) { 55 | for (String key : headers.map().keySet()) { 56 | c.setRequestProperty(key, headers.map().get(key).str()); 57 | } 58 | } 59 | else { 60 | typeMismatch("type mismatch: expected list or map, got " + headers.typeString()); 61 | } 62 | if (!useragent.isEmpty()) 63 | c.setRequestProperty("User-Agent", useragent); 64 | c.connect(); 65 | InputStream input = c.getInputStream(); 66 | if (saveToFile) { 67 | String outputFile = args.get(1).str(); 68 | Files.copy(input, new File(outputFile).toPath(), StandardCopyOption.REPLACE_EXISTING); 69 | } else { 70 | for (String line : IOUtils.streamLineIterator(input)) { 71 | out.push(RödaString.of(line)); 72 | } 73 | } 74 | input.close(); 75 | } catch (MalformedURLException e) { 76 | error(e); 77 | } catch (IOException e) { 78 | error(e); 79 | } 80 | }, saveToFile 81 | ? Arrays.asList(new Parameter("url", false, STRING)) 82 | : Arrays.asList(new Parameter("url", false, STRING), new Parameter("filename", false, STRING)), true, 83 | Arrays.asList( 84 | new Parameter("ua", false, Parser.expressionString("", 0, "Roeda/"+Röda.RÖDA_VERSION_STRING)), 85 | new Parameter("headers", false, Parser.expressionList("", 0, Collections.emptyList())) 86 | ))); 87 | } 88 | 89 | public static void populateWcat(RödaScope S) { 90 | addResourceLoader(S, "loadResourceLines", false); 91 | addResourceLoader(S, "saveResource", true); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/runtime/Datatype.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.runtime; 2 | 3 | import static org.kaivos.röda.Interpreter.unknownName; 4 | import static java.util.stream.Collectors.joining; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | import org.kaivos.röda.RödaValue; 11 | import org.kaivos.röda.Interpreter.RecordDeclaration; 12 | import org.kaivos.röda.Interpreter.RödaScope; 13 | 14 | public class Datatype { 15 | public final String name; 16 | public final List subtypes; 17 | public final Optional scope; 18 | 19 | public Datatype(String name, 20 | List subtypes, 21 | Optional scope) { 22 | this.name = name; 23 | this.subtypes = Collections.unmodifiableList(subtypes); 24 | this.scope = scope; 25 | } 26 | 27 | public Datatype(String name, List subtypes, RödaScope scope) { 28 | this(name, subtypes, Optional.of(scope)); 29 | } 30 | 31 | public Datatype(String name, List subtypes) { 32 | this(name, subtypes, Optional.empty()); 33 | } 34 | 35 | public Datatype(String name, RödaScope scope) { 36 | this.name = name; 37 | this.subtypes = Collections.emptyList(); 38 | this.scope = Optional.of(scope); 39 | } 40 | 41 | public Datatype(String name) { 42 | this.name = name; 43 | this.subtypes = Collections.emptyList(); 44 | this.scope = Optional.empty(); 45 | } 46 | 47 | private RecordDeclaration resolveDeclaration() { 48 | if (scope.isPresent()) { 49 | RecordDeclaration r = scope.get().getRecordDeclarations().get(name); 50 | if (r == null) 51 | unknownName("record class '" + name + "' not found"); 52 | return r; 53 | } 54 | unknownName("record class '" + name + "' not found (namespace not specified)"); 55 | return null; 56 | } 57 | 58 | public Record resolve() { 59 | return resolveDeclaration().tree; 60 | } 61 | 62 | public RödaValue resolveReflection() { 63 | return resolveDeclaration().reflection; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | if (subtypes.isEmpty()) 69 | return name; 70 | return name + "<" + subtypes.stream() 71 | .map(Datatype::toString) 72 | .collect(joining(", ")) + ">"; 73 | } 74 | 75 | @Override 76 | public boolean equals(Object obj) { 77 | if (!(obj instanceof Datatype)) 78 | return false; 79 | Datatype other = (Datatype) obj; 80 | if (!name.equals(other.name)) 81 | return false; 82 | if (!subtypes.equals(other.subtypes)) 83 | return false; 84 | 85 | return true; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return name.hashCode() + subtypes.hashCode(); 91 | } 92 | } -------------------------------------------------------------------------------- /src/org/kaivos/röda/runtime/Function.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.runtime; 2 | 3 | import java.util.List; 4 | 5 | import org.kaivos.röda.Parser.StatementTree; 6 | import org.kaivos.röda.Parser.ExpressionTree; 7 | 8 | public class Function { 9 | public String name; 10 | public List typeparams; 11 | public List parameters, kwparameters; 12 | public boolean isVarargs; 13 | public List body; 14 | 15 | public Function(String name, 16 | List typeparams, 17 | List parameters, 18 | boolean isVarargs, 19 | List kwparameters, 20 | List body) { 21 | 22 | for (Parameter p : parameters) 23 | if (p.defaultValue != null) 24 | throw new IllegalArgumentException("non-kw parameters can't have default values"); 25 | 26 | for (Parameter p : kwparameters) 27 | if (p.defaultValue == null) 28 | throw new IllegalArgumentException("kw parameters must have default values"); 29 | 30 | this.name = name; 31 | this.typeparams = typeparams; 32 | this.parameters = parameters; 33 | this.kwparameters = kwparameters; 34 | this.isVarargs = isVarargs; 35 | this.body = body; 36 | } 37 | 38 | public static class Parameter { 39 | public String name; 40 | public boolean reference; 41 | public Datatype type; 42 | public ExpressionTree defaultValue; 43 | public Parameter(String name, boolean reference) { 44 | this(name, reference, null, null); 45 | } 46 | public Parameter(String name, boolean reference, Datatype type) { 47 | this(name, reference, type, null); 48 | } 49 | public Parameter(String name, boolean reference, ExpressionTree dafaultValue) { 50 | this(name, reference, null, dafaultValue); 51 | } 52 | public Parameter(String name, boolean reference, Datatype type, ExpressionTree defaultValue) { 53 | if (reference && defaultValue != null) 54 | throw new IllegalArgumentException("a reference parameter can't have a default value"); 55 | this.name = name; 56 | this.reference = reference; 57 | this.type = type; 58 | this.defaultValue = defaultValue; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/org/kaivos/röda/runtime/Record.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.runtime; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.kaivos.röda.Parser.AnnotationTree; 7 | import org.kaivos.röda.Interpreter.RödaScope; 8 | import org.kaivos.röda.Parser.ExpressionTree; 9 | 10 | public class Record { 11 | public static class Field { 12 | public final String name; 13 | public final Datatype type; 14 | public final ExpressionTree defaultValue; 15 | public final List annotations; 16 | 17 | public Field(String name, 18 | Datatype type) { 19 | this(name, type, null, Collections.emptyList()); 20 | } 21 | 22 | public Field(String name, 23 | Datatype type, 24 | ExpressionTree defaultValue, 25 | List annotations) { 26 | this.name = name; 27 | this.type = type; 28 | this.defaultValue = defaultValue; 29 | this.annotations = Collections.unmodifiableList(annotations); 30 | } 31 | } 32 | public static class SuperExpression { 33 | public final Datatype type; 34 | public final List args; 35 | 36 | public SuperExpression(Datatype type, List args) { 37 | this.type = type; 38 | this.args = args; 39 | } 40 | } 41 | 42 | public final String name; 43 | public final List typeparams, params; 44 | public final List superTypes; 45 | public final List annotations; 46 | public final List fields; 47 | public final boolean isValueType; 48 | public final RödaScope declarationScope; 49 | 50 | public Record(String name, 51 | List typeparams, 52 | List superTypes, 53 | List fields, 54 | boolean isValueType, 55 | RödaScope declarationScope) { 56 | this(name, typeparams, Collections.emptyList(), superTypes, Collections.emptyList(), fields, isValueType, declarationScope); 57 | } 58 | 59 | public Record(String name, 60 | List typeparams, 61 | List params, 62 | List superTypes, 63 | List annotations, 64 | List fields, 65 | boolean isValueType, 66 | RödaScope declarationScope) { 67 | this.name = name; 68 | this.typeparams = Collections.unmodifiableList(typeparams); 69 | this.params = Collections.unmodifiableList(params); 70 | this.annotations = Collections.unmodifiableList(annotations); 71 | this.fields = Collections.unmodifiableList(fields); 72 | this.superTypes = superTypes; 73 | this.isValueType = isValueType; 74 | this.declarationScope = declarationScope; 75 | } 76 | } -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaBoolean.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.Parser; 4 | import org.kaivos.röda.RödaValue; 5 | 6 | public class RödaBoolean extends RödaValue { 7 | private boolean bool; 8 | 9 | private RödaBoolean(boolean bool) { 10 | assumeIdentity(BOOLEAN); 11 | this.bool = bool; 12 | } 13 | 14 | @Override public RödaValue copy() { 15 | return this; 16 | } 17 | 18 | @Override public String str() { 19 | return bool ? "" : ""; 20 | } 21 | 22 | @Override public boolean bool() { 23 | return bool; 24 | } 25 | 26 | @Override 27 | public RödaValue callOperator(Parser.ExpressionTree.CType operator, RödaValue value) { 28 | switch (operator) { 29 | case NOT: 30 | return RödaBoolean.of(!bool); 31 | default: 32 | return super.callOperator(operator, value); 33 | } 34 | } 35 | 36 | @Override public boolean strongEq(RödaValue value) { 37 | return value.is(BOOLEAN) && value.bool() == bool; 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Boolean.hashCode(bool); 43 | } 44 | 45 | public static RödaBoolean of(boolean value) { 46 | return new RödaBoolean(value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaFloating.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.Parser; 4 | import org.kaivos.röda.RödaValue; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | 7 | public class RödaFloating extends RödaValue { 8 | private double number; 9 | 10 | private RödaFloating(double number) { 11 | assumeIdentity(FLOATING); 12 | assumeIdentity(NUMBER); 13 | this.number = number; 14 | } 15 | 16 | @Override public RödaValue copy() { 17 | return this; 18 | } 19 | 20 | @Override public String str() { 21 | return String.valueOf(number); 22 | } 23 | 24 | @Override public double floating() { 25 | return number; 26 | } 27 | 28 | @Override 29 | public RödaValue callOperator(Parser.ExpressionTree.CType operator, RödaValue value) { 30 | switch (operator) { 31 | case NEG: 32 | return RödaFloating.of(-this.floating()); 33 | default: 34 | } 35 | if (!value.is(NUMBER)) typeMismatch("can't " + operator.name() + " " + typeString() + " and " + value.typeString()); 36 | switch (operator) { 37 | case POW: 38 | return RödaFloating.of(Math.pow(this.floating(), value.floating())); 39 | case MUL: 40 | return RödaFloating.of(this.floating()*value.floating()); 41 | case DIV: 42 | return RödaFloating.of(this.floating()/value.floating()); 43 | case IDIV: 44 | return RödaInteger.of((long) (this.floating()/value.floating())); 45 | case MOD: 46 | return RödaFloating.of(this.floating()%value.floating()); 47 | case ADD: 48 | return RödaFloating.of(this.floating()+value.floating()); 49 | case SUB: 50 | return RödaFloating.of(this.floating()-value.floating()); 51 | case LT: 52 | return RödaBoolean.of(this.floating()value.floating()); 55 | case LE: 56 | return RödaBoolean.of(this.floating()<=value.floating()); 57 | case GE: 58 | return RödaBoolean.of(this.floating()>=value.floating()); 59 | default: 60 | return super.callOperator(operator, value); 61 | } 62 | } 63 | 64 | @Override public boolean strongEq(RödaValue value) { 65 | return value.is(FLOATING) && value.floating() == number || value.is(INTEGER) && value.integer() == number; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return Double.hashCode(number); 71 | } 72 | 73 | public static RödaFloating of(double number) { 74 | return new RödaFloating(number); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaFunction.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.Interpreter.RödaScope; 4 | import org.kaivos.röda.RödaValue; 5 | import org.kaivos.röda.runtime.Function; 6 | 7 | public class RödaFunction extends RödaValue { 8 | private Function function; 9 | private RödaScope localScope; 10 | 11 | private RödaFunction(Function function) { 12 | assumeIdentity(FUNCTION); 13 | this.function = function; 14 | this.localScope = null; 15 | } 16 | 17 | private RödaFunction(Function function, RödaScope localScope) { 18 | assumeIdentity("function"); 19 | this.function = function; 20 | this.localScope = localScope; 21 | } 22 | 23 | @Override public RödaValue copy() { 24 | return this; 25 | } 26 | 27 | @Override public Function function() { 28 | return function; 29 | } 30 | 31 | @Override public String str() { 32 | return ""; 33 | } 34 | 35 | @Override public RödaScope localScope() { 36 | return localScope; 37 | } 38 | 39 | @Override public boolean strongEq(RödaValue value) { 40 | return value.is(FUNCTION) && !value.is(NFUNCTION) && value.function() == function; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return function.hashCode() + localScope.hashCode(); 46 | } 47 | 48 | public static RödaFunction of(Function function) { 49 | return new RödaFunction(function); 50 | } 51 | 52 | public static RödaFunction of(Function function, RödaScope localScope) { 53 | return new RödaFunction(function, localScope); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaInteger.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.Parser; 4 | import org.kaivos.röda.RödaValue; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | 7 | public class RödaInteger extends RödaValue { 8 | private long number; 9 | 10 | private RödaInteger(long number) { 11 | assumeIdentity(INTEGER); 12 | assumeIdentity(NUMBER); 13 | this.number = number; 14 | } 15 | 16 | @Override public RödaValue copy() { 17 | return this; 18 | } 19 | 20 | @Override public String str() { 21 | return String.valueOf(number); 22 | } 23 | 24 | @Override public long integer() { 25 | return number; 26 | } 27 | 28 | @Override public double floating() { 29 | return number; 30 | } 31 | 32 | @Override 33 | public RödaValue callOperator(Parser.ExpressionTree.CType operator, RödaValue value) { 34 | switch (operator) { 35 | case NEG: 36 | return RödaInteger.of(-this.integer()); 37 | case BNOT: 38 | return RödaInteger.of(~this.integer()); 39 | default: 40 | } 41 | if (value.is(FLOATING)) return RödaFloating.of(this.integer()).callOperator(operator, value); 42 | // TODO: ^ virheviestit eivät näyttävät tyypin olevan floating 43 | if (!value.is(INTEGER)) typeMismatch("can't " + operator.name() + " " + typeString() + " and " + value.typeString()); 44 | switch (operator) { 45 | case POW: 46 | return RödaInteger.of((long) Math.pow(this.integer(), value.integer())); 47 | case MUL: 48 | return RödaInteger.of(this.integer()*value.integer()); 49 | case DIV: 50 | return RödaFloating.of((double) this.integer()/value.integer()); 51 | case IDIV: 52 | return RödaInteger.of(this.integer()/value.integer()); 53 | case MOD: 54 | return RödaInteger.of(this.integer()%value.integer()); 55 | case ADD: 56 | return RödaInteger.of(this.integer()+value.integer()); 57 | case SUB: 58 | return RödaInteger.of(this.integer()-value.integer()); 59 | case BAND: 60 | return RödaInteger.of(this.integer()&value.integer()); 61 | case BOR: 62 | return RödaInteger.of(this.integer()|value.integer()); 63 | case BXOR: 64 | return RödaInteger.of(this.integer()^value.integer()); 65 | case BLSHIFT: 66 | return RödaInteger.of(this.integer()<>value.integer()); 69 | case BRRSHIFT: 70 | return RödaInteger.of(this.integer()>>>value.integer()); 71 | case LT: 72 | return RödaBoolean.of(this.integer()value.integer()); 75 | case LE: 76 | return RödaBoolean.of(this.integer()<=value.integer()); 77 | case GE: 78 | return RödaBoolean.of(this.integer()>=value.integer()); 79 | default: 80 | return super.callOperator(operator, value); 81 | } 82 | } 83 | 84 | @Override public boolean strongEq(RödaValue value) { 85 | return value.is(INTEGER) && value.integer() == number; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Long.hashCode(number); 91 | } 92 | 93 | public static RödaInteger of(long number) { 94 | return new RödaInteger(number); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaList.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import static java.util.stream.Collectors.joining; 4 | import static org.kaivos.röda.Interpreter.outOfBounds; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | import org.kaivos.röda.RödaValue; 13 | import org.kaivos.röda.Parser.ExpressionTree.CType; 14 | import org.kaivos.röda.runtime.Datatype; 15 | 16 | public class RödaList extends RödaValue { 17 | 18 | private Datatype type; 19 | private List list; 20 | 21 | private RödaList(List list) { 22 | assumeIdentity(LIST); 23 | this.type = null; 24 | this.list = list; 25 | } 26 | 27 | private RödaList(Datatype type, List list) { 28 | if (type != null) 29 | assumeIdentity(new Datatype(LIST.name, Arrays.asList(type))); 30 | assumeIdentity("list"); 31 | this.type = type; 32 | this.list = list; 33 | if (type != null) { 34 | for (RödaValue value : list) { 35 | if (!value.is(type)) { 36 | typeMismatch(typeString() 37 | + " can't contain a value of type " + value.typeString()); 38 | } 39 | } 40 | } 41 | } 42 | 43 | @Override public RödaValue copy() { 44 | List newList = new ArrayList<>(list.size()); 45 | for (RödaValue item : list) newList.add(item.copy()); 46 | return new RödaList(type, newList); 47 | } 48 | 49 | @Override public String str() { 50 | return "[" + list.stream().map(RödaValue::str).collect(joining(", ")) + "]"; 51 | } 52 | 53 | @Override public List list() { 54 | return Collections.unmodifiableList(list); 55 | } 56 | 57 | @Override public List modifiableList() { 58 | return list; 59 | } 60 | 61 | private void checkInRange(long index, boolean allowOneAfterLast) { 62 | if (list.size() + (allowOneAfterLast ? 1 : 0) <= index) 63 | outOfBounds("list index out of bounds: index " + index 64 | + ", size " + list.size()); 65 | if (index > Integer.MAX_VALUE) outOfBounds("list index out of bounds: too large index: "+index); 66 | if (index < 0) outOfBounds("list index out of bounds: too small index: "+index); 67 | } 68 | 69 | @Override public RödaValue get(RödaValue indexVal) { 70 | long index = indexVal.integer(); 71 | if (index < 0) index = list.size()+index; 72 | checkInRange(index, false); 73 | return list.get((int) index); 74 | } 75 | 76 | @Override public void set(RödaValue indexVal, RödaValue value) { 77 | long index = indexVal.integer(); 78 | if (index < 0) index = list.size()+index; 79 | checkInRange(index, false); 80 | if (type != null && !value.is(type)) 81 | typeMismatch("cannot put " + value.typeString() + " to " + typeString()); 82 | list.set((int) index, value); 83 | } 84 | 85 | private int sliceStart(long step, RödaValue startVal) { 86 | long start = startVal != null ? startVal.integer() : step > 0 ? 0 : -1; 87 | if (start < 0) start = list.size()+start; 88 | checkInRange(start, true); 89 | return (int) start; 90 | } 91 | 92 | private int sliceEnd(long step, int start, RödaValue endVal) { 93 | if (endVal == null && step < 0) return -1; 94 | long end = endVal != null ? endVal.integer() : list.size(); 95 | if (end < 0) end = list.size()+end; 96 | if (step > 0 && end == 0 && start > 0) end = list.size(); 97 | checkInRange(end, true); 98 | return (int) end; 99 | } 100 | 101 | private long sliceStep(RödaValue stepVal) { 102 | long step = stepVal == null ? 1 : stepVal.integer(); 103 | return step; 104 | } 105 | 106 | @Override public void setSlice(RödaValue startVal, RödaValue endVal, RödaValue stepVal, RödaValue value) { 107 | long step = sliceStep(stepVal); 108 | int start = sliceStart(step, startVal); 109 | int end = sliceEnd(step, start, endVal); 110 | List sublist = value.list(); 111 | if (step == 1) { 112 | for (int i = start; i < end; i++) list.remove(start); 113 | list.addAll(start, sublist); 114 | } 115 | else if (step == -1) { 116 | for (int i = start; i > end; i--) list.remove(end+1); 117 | sublist = new ArrayList<>(sublist); 118 | Collections.reverse(sublist); 119 | list.addAll(end+1, sublist); 120 | } 121 | else if (step > 0) { 122 | for (int i = start, j = 0; i < end; i += step, j++) list.set(i, sublist.get(j)); 123 | } 124 | else if (step < 0) { 125 | for (int i = start, j = 0; i > end; i += step, j++) list.set(i, sublist.get(j)); 126 | } 127 | } 128 | 129 | @Override public RödaValue slice(RödaValue startVal, RödaValue endVal, RödaValue stepVal) { 130 | long step = sliceStep(stepVal); 131 | int start = sliceStart(step, startVal); 132 | int end = sliceEnd(step, start, endVal); 133 | if (step == 1) 134 | return of(list.subList((int) start, (int) end)); 135 | List newList = new ArrayList<>(); 136 | if (step > 0) { 137 | for (int i = start; i < end; i += step) newList.add(list.get(i)); 138 | } 139 | else if (step < 0) { 140 | for (int i = start; i > end; i += step) newList.add(list.get(i)); 141 | } 142 | return of(newList); 143 | } 144 | 145 | @Override public void del(RödaValue indexVal) { 146 | long index = indexVal.integer(); 147 | if (index < 0) index = list.size()+index; 148 | checkInRange(index, false); 149 | list.remove((int) index); 150 | } 151 | 152 | @Override public void delSlice(RödaValue startVal, RödaValue endVal, RödaValue stepVal) { 153 | long step = sliceStep(stepVal); 154 | int start = sliceStart(step, startVal); 155 | int end = sliceEnd(step, start, endVal); 156 | if (step > 0) { 157 | for (int i = start; i < end; i += step-1, end--) list.remove(i); 158 | } 159 | else if (step < 0) { 160 | for (int i = start; i > end; i += step) list.remove(i); 161 | } 162 | } 163 | 164 | @Override public RödaValue contains(RödaValue indexVal) { 165 | long index = indexVal.integer(); 166 | if (index < 0) index = list.size()+index; 167 | return RödaBoolean.of(index < list.size()); 168 | } 169 | 170 | @Override public RödaValue containsValue(RödaValue value) { 171 | for (RödaValue element : list) { 172 | if (element.strongEq(value)) { 173 | return RödaBoolean.of(true); 174 | } 175 | } 176 | return RödaBoolean.of(false); 177 | } 178 | 179 | @Override public RödaValue length() { 180 | return RödaInteger.of(list.size()); 181 | } 182 | 183 | @Override public RödaValue join(RödaValue separatorVal) { 184 | String separator = separatorVal.str(); 185 | String text = ""; 186 | int i = 0; for (RödaValue val : list) { 187 | if (i++ != 0) text += separator; 188 | text += val.str(); 189 | } 190 | return RödaString.of(text); 191 | } 192 | 193 | @Override public void add(RödaValue value) { 194 | if (type != null && !value.is(type)) 195 | typeMismatch("cannot put " + value.typeString() + " to " + typeString()); 196 | list.add(value); 197 | } 198 | 199 | @Override public void addAll(List values) { 200 | if (type != null) { 201 | for (RödaValue value : values) { 202 | if (!value.is(type)) 203 | typeMismatch("cannot put " + value.typeString() + " to " + typeString()); 204 | } 205 | } 206 | list.addAll(values); 207 | } 208 | 209 | @Override public void remove(RödaValue value) { 210 | if (type != null && !value.is(type)) 211 | typeMismatch(typeString() + " can not contain " + value.typeString()); 212 | list.remove(value); 213 | } 214 | 215 | @Override public boolean strongEq(RödaValue value) { 216 | if (!value.is(LIST)) return false; 217 | if (list.size() != value.list().size()) return false; 218 | boolean ans = true; 219 | for (int i = 0; i < list.size(); i++) 220 | ans &= list.get(i).strongEq(value.list().get(i)); 221 | return ans; 222 | } 223 | 224 | private int compare(RödaList other) { 225 | List list2 = other.list(); 226 | for (int i = 0; i < Math.min(list.size(), list2.size()); i++) { 227 | RödaValue val1 = list.get(i); 228 | RödaValue val2 = list2.get(i); 229 | if (val1.callOperator(CType.LT, val2).bool()) return -1; 230 | if (val2.callOperator(CType.LT, val1).bool()) return 1; 231 | } 232 | if (list.size() < list2.size()) return -1; 233 | if (list.size() > list2.size()) return 1; 234 | return 0; 235 | } 236 | 237 | @Override 238 | public RödaValue callOperator(CType operator, RödaValue value) { 239 | switch (operator) { 240 | case MUL: 241 | if (!value.is(INTEGER)) 242 | typeMismatch("can't " + operator.name() + " " + typeString() + " and " + value.typeString()); 243 | break; 244 | case LT: 245 | case GT: 246 | case LE: 247 | case GE: 248 | if (!value.is(LIST)) 249 | typeMismatch("can't " + operator.name() + " " + typeString() + " and " + value.typeString()); 250 | break; 251 | default: 252 | } 253 | 254 | 255 | switch (operator) { 256 | case MUL: { 257 | List newList = new ArrayList<>(); 258 | for (int i = 0; i < value.integer(); i++) { 259 | newList.addAll(this.list); 260 | } 261 | return of(newList); 262 | } 263 | case ADD: { 264 | List newList = new ArrayList<>(this.list); 265 | newList.add(value); 266 | return of(newList); 267 | } 268 | case SUB: { 269 | List newList = new ArrayList<>(this.list); 270 | newList.remove(value); 271 | return of(newList); 272 | } 273 | case LT: 274 | return RödaBoolean.of(compare((RödaList) value) < 0); 275 | case GT: 276 | return RödaBoolean.of(compare((RödaList) value) > 0); 277 | case LE: 278 | return RödaBoolean.of(compare((RödaList) value) <= 0); 279 | case GE: 280 | return RödaBoolean.of(compare((RödaList) value) >= 0); 281 | default: 282 | return super.callOperator(operator, value); 283 | } 284 | } 285 | 286 | @Override 287 | public int hashCode() { 288 | return list.hashCode(); 289 | } 290 | 291 | public static RödaList of(List list) { 292 | return new RödaList(new ArrayList<>(list)); 293 | } 294 | 295 | public static RödaList of(Datatype type, List list) { 296 | return new RödaList(type, new ArrayList<>(list)); 297 | } 298 | 299 | public static RödaList of(String type, List list) { 300 | return new RödaList(new Datatype(type), new ArrayList<>(list)); 301 | } 302 | 303 | public static RödaList of(RödaValue... elements) { 304 | return new RödaList(new ArrayList<>(Arrays.asList(elements))); 305 | } 306 | 307 | public static RödaList empty() { 308 | return new RödaList(new ArrayList<>()); 309 | } 310 | 311 | public static RödaList empty(Datatype type) { 312 | return new RödaList(type, new ArrayList<>()); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaMap.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import static org.kaivos.röda.Interpreter.outOfBounds; 4 | import static org.kaivos.röda.Interpreter.typeMismatch; 5 | 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import org.kaivos.röda.RödaValue; 12 | import org.kaivos.röda.runtime.Datatype; 13 | 14 | public class RödaMap extends RödaValue { 15 | 16 | private Datatype type; 17 | private Map map; 18 | 19 | private RödaMap(Map map) { 20 | assumeIdentity(MAP); 21 | this.type = null; 22 | this.map = map; 23 | } 24 | 25 | private RödaMap(Datatype type, Map map) { 26 | if (type != null) 27 | assumeIdentity(new Datatype(MAP.name, Arrays.asList(type))); 28 | assumeIdentity("map"); 29 | this.type = type; 30 | this.map = map; 31 | if (type != null) { 32 | for (RödaValue value : map.values()) { 33 | if (!value.is(type)) { 34 | typeMismatch("can't make a " + typeString() 35 | + " that contains a " + value.typeString()); 36 | } 37 | } 38 | } 39 | } 40 | 41 | @Override public RödaValue copy() { 42 | Map newMap = new HashMap<>(map.size()); 43 | for (Map.Entry item : map.entrySet()) 44 | newMap.put(item.getKey(), item.getValue().copy()); 45 | return new RödaMap(type, newMap); 46 | } 47 | 48 | @Override public String str() { 49 | return ""; 50 | } 51 | 52 | @Override public Map map() { 53 | return Collections.unmodifiableMap(map); 54 | } 55 | 56 | @Override public RödaValue get(RödaValue indexVal) { 57 | String index = indexVal.str(); 58 | if (!map.containsKey(index)) outOfBounds("key does not exist: " + index); 59 | return map.get(index); 60 | } 61 | 62 | @Override public void set(RödaValue indexVal, RödaValue value) { 63 | String index = indexVal.str(); 64 | if (type != null && !value.is(type)) 65 | typeMismatch("cannot put " + value.typeString() + " to " + typeString()); 66 | map.put(index, value); 67 | } 68 | 69 | @Override public void del(RödaValue indexVal) { 70 | String index = indexVal.str(); 71 | map.remove(index); 72 | } 73 | 74 | @Override public RödaValue contains(RödaValue indexVal) { 75 | String index = indexVal.str(); 76 | return RödaBoolean.of(map.containsKey(index)); 77 | } 78 | 79 | @Override public RödaValue length() { 80 | return RödaInteger.of(map.size()); 81 | } 82 | 83 | @Override public boolean strongEq(RödaValue value) { 84 | if (!value.is(MAP)) return false; 85 | if (map.size() != value.map().size()) return false; 86 | boolean ans = true; 87 | for (int i = 0; i < map.size(); i++) 88 | ans &= map.get(i).strongEq(value.map().get(i)); 89 | return ans; 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | return map.hashCode(); 95 | } 96 | 97 | public static RödaMap of(Map map) { 98 | return new RödaMap(new HashMap<>(map)); 99 | } 100 | 101 | public static RödaMap of(Datatype type, Map map) { 102 | return new RödaMap(type, new HashMap<>(map)); 103 | } 104 | 105 | public static RödaMap of(String type, Map map) { 106 | return new RödaMap(new Datatype(type), new HashMap<>(map)); 107 | } 108 | 109 | public static RödaMap empty() { 110 | return new RödaMap(new HashMap<>()); 111 | } 112 | 113 | public static RödaMap empty(Datatype type) { 114 | return new RödaMap(type, new HashMap<>()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaNamespace.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.Interpreter.RödaScope; 4 | 5 | import static org.kaivos.röda.Interpreter.outOfBounds; 6 | import static org.kaivos.röda.Interpreter.unknownName; 7 | 8 | import java.util.Optional; 9 | 10 | import org.kaivos.röda.RödaValue; 11 | 12 | public class RödaNamespace extends RödaValue { 13 | 14 | private RödaScope scope; 15 | 16 | private RödaNamespace(RödaScope scope) { 17 | assumeIdentity(NAMESPACE); 18 | this.scope = scope; 19 | } 20 | 21 | @Override 22 | public RödaValue copy() { 23 | return this; 24 | } 25 | 26 | @Override 27 | public String str() { 28 | return "<" + typeString() + " instance " + hashCode() + ">"; 29 | } 30 | 31 | @Override public void setField(String name, RödaValue value) { 32 | scope.setLocal(name, value); 33 | } 34 | 35 | @Override public RödaValue getField(String name) { 36 | RödaValue value = scope.resolve(name); 37 | if (value == null) 38 | unknownName("variable '" + name + "' not found in namespace"); 39 | return value; 40 | } 41 | 42 | @Override public RödaValue get(RödaValue indexVal) { 43 | String index = indexVal.str(); 44 | RödaValue value = scope.resolve(index); 45 | if (value == null) outOfBounds("variable '" + index + "' not found in namespace"); 46 | return value; 47 | } 48 | 49 | @Override public void set(RödaValue indexVal, RödaValue value) { 50 | String index = indexVal.str(); 51 | scope.setLocal(index, value); 52 | } 53 | 54 | @Override public RödaValue contains(RödaValue indexVal) { 55 | String index = indexVal.str(); 56 | return RödaBoolean.of(scope.resolve(index) != null); 57 | } 58 | 59 | @Override public RödaScope scope() { 60 | return scope; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return scope.hashCode(); 66 | } 67 | 68 | public static RödaNamespace of(RödaScope scope) { 69 | return new RödaNamespace(scope); 70 | } 71 | 72 | public static RödaNamespace empty() { 73 | return new RödaNamespace(new RödaScope(Optional.empty())); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaNativeFunction.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.kaivos.röda.RödaStream; 8 | import org.kaivos.röda.RödaValue; 9 | import org.kaivos.röda.runtime.Datatype; 10 | import org.kaivos.röda.runtime.Function.Parameter; 11 | 12 | import static org.kaivos.röda.Interpreter.RödaScope; 13 | 14 | public class RödaNativeFunction extends RödaValue { 15 | public static class NativeFunction { 16 | public String name; 17 | public NativeFunctionBody body; 18 | public boolean isVarargs, isKwVarargs; 19 | public List parameters, kwparameters; 20 | } 21 | 22 | public static interface NativeFunctionBody { 23 | public void exec(List typeargs, 24 | List args, 25 | Map kwargs, 26 | RödaScope scope, 27 | RödaStream in, RödaStream out); 28 | } 29 | 30 | private NativeFunction function; 31 | 32 | private RödaNativeFunction(NativeFunction function) { 33 | assumeIdentity(NFUNCTION); 34 | assumeIdentity(FUNCTION); 35 | this.function = function; 36 | } 37 | 38 | @Override public RödaValue copy() { 39 | return this; 40 | } 41 | 42 | @Override public NativeFunction nfunction() { 43 | return function; 44 | } 45 | 46 | @Override public String str() { 47 | return ""; 48 | } 49 | 50 | @Override public boolean strongEq(RödaValue value) { 51 | return value.is(NFUNCTION) && value.nfunction() == function; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return function.body.hashCode(); 57 | } 58 | 59 | public static RödaNativeFunction of(NativeFunction function) { 60 | return new RödaNativeFunction(function); 61 | } 62 | 63 | public static RödaNativeFunction of(String name, NativeFunctionBody body, 64 | List parameters, boolean isVarargs) { 65 | return of(name, body, parameters, isVarargs, Collections.emptyList()); 66 | } 67 | 68 | public static RödaNativeFunction of(String name, NativeFunctionBody body, 69 | List parameters, boolean isVarargs, List kwparameters) { 70 | return of(name, body, parameters, isVarargs, kwparameters, false); 71 | } 72 | 73 | public static RödaNativeFunction of(String name, NativeFunctionBody body, 74 | List parameters, boolean isVarargs, List kwparameters, boolean isKwVarargs) { 75 | 76 | for (Parameter p : parameters) 77 | if (p.defaultValue != null) 78 | throw new IllegalArgumentException("non-kw parameters can't have default values"); 79 | 80 | for (Parameter p : kwparameters) 81 | if (p.defaultValue == null) 82 | throw new IllegalArgumentException("kw parameters must have default values"); 83 | 84 | NativeFunction function = new NativeFunction(); 85 | function.name = name; 86 | function.body = body; 87 | function.isVarargs = isVarargs; 88 | function.isKwVarargs = isKwVarargs; 89 | function.parameters = parameters; 90 | function.kwparameters = kwparameters; 91 | return of(function); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaRecordInstance.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import static org.kaivos.röda.Interpreter.error; 4 | import static org.kaivos.röda.Interpreter.illegalArguments; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | import static org.kaivos.röda.Interpreter.unknownName; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import org.kaivos.röda.RödaValue; 15 | import org.kaivos.röda.runtime.Datatype; 16 | import org.kaivos.röda.runtime.Record; 17 | 18 | public class RödaRecordInstance extends RödaValue { 19 | private boolean isValueType; 20 | private Map fields; 21 | private Map fieldTypes; 22 | 23 | private RödaRecordInstance(List identities, 24 | boolean isValueType, 25 | Map fields, 26 | Map fieldTypes) { 27 | assumeIdentities(identities); 28 | this.isValueType = isValueType; 29 | this.fields = fields; 30 | this.fieldTypes = fieldTypes; 31 | } 32 | 33 | @Override public RödaValue copy() { 34 | if (isValueType) { 35 | Map newFields = new HashMap<>(); 36 | for (Map.Entry item : fields.entrySet()) 37 | newFields.put(item.getKey(), item.getValue().copy()); 38 | return new RödaRecordInstance(identities(), 39 | true, 40 | newFields, 41 | fieldTypes); 42 | } else { 43 | return this; 44 | } 45 | } 46 | 47 | @Override public String str() { 48 | return "<" + typeString() + " instance " + super.hashCode() + ">"; 49 | } 50 | 51 | @Override public void setField(String field, RödaValue value) { 52 | if (fieldTypes.get(field) == null) 53 | unknownName(typeString() + " doesn't have field '" + field + "'"); 54 | if (!value.is(fieldTypes.get(field))) 55 | typeMismatch("can't put " + value.typeString() 56 | + " to " + fieldTypes.get(field) + " field"); 57 | this.fields.put(field, value); 58 | } 59 | 60 | @Override public RödaValue getField(String field) { 61 | if (fieldTypes.get(field) == null) 62 | unknownName(typeString() + " doesn't have field '" + field + "'"); 63 | RödaValue a = fields.get(field); 64 | if (a == null) 65 | error("field '" + field + "' hasn't been initialized"); 66 | return a; 67 | } 68 | 69 | @Override public boolean strongEq(RödaValue value) { 70 | if (!basicIdentity().equals(value.basicIdentity())) 71 | return false; 72 | boolean ans = true; 73 | for (Map.Entry entry : fields.entrySet()) 74 | ans &= entry.getValue().strongEq(value.fields().get(entry.getKey())); 75 | return ans; 76 | } 77 | 78 | @Override public Map fields() { 79 | return Collections.unmodifiableMap(fields); 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | return basicIdentity().hashCode() + fields.hashCode(); 85 | } 86 | 87 | public static RödaRecordInstance of(Record record, List typearguments) { 88 | Map fieldTypes = new HashMap<>(); 89 | List identities = new ArrayList<>(); 90 | construct(record, typearguments, fieldTypes, identities); 91 | return new RödaRecordInstance(identities, record.isValueType, new HashMap<>(), fieldTypes); 92 | } 93 | 94 | private static void construct(Record record, List typearguments, 95 | Map fieldTypes, 96 | List identities) { 97 | identities.add(new Datatype(record.name, typearguments, record.declarationScope)); 98 | for (Record.Field field : record.fields) { 99 | // TODO check double inheritance 100 | fieldTypes.put(field.name, substitute(field.type, record.typeparams, typearguments)); 101 | } 102 | for (Record.SuperExpression superExp : record.superTypes) { 103 | Datatype superType = substitute(superExp.type, record.typeparams, typearguments); 104 | Record r = superType.resolve(); 105 | if (r == null) 106 | unknownName("super type " + superType.name + " not found"); 107 | construct(r, superType.subtypes, fieldTypes, identities); 108 | } 109 | } 110 | 111 | private static Datatype substitute(Datatype type, List typeparams, List typeargs) { 112 | if (typeparams.size() != typeargs.size()) 113 | illegalArguments("wrong number of typearguments"); 114 | if (typeparams.contains(type.name)) { 115 | if (!type.subtypes.isEmpty()) 116 | error("a typeparameter can't have subtypes"); 117 | return typeargs.get(typeparams.indexOf(type.name)); 118 | } 119 | List subtypes = new ArrayList<>(); 120 | for (Datatype t : type.subtypes) { 121 | subtypes.add(substitute(t, typeparams, typeargs)); 122 | } 123 | return new Datatype(type.name, subtypes, type.scope); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaReference.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import org.kaivos.röda.RödaValue; 4 | import static org.kaivos.röda.Interpreter.error; 5 | import static org.kaivos.röda.Interpreter.unknownName; 6 | import static org.kaivos.röda.Interpreter.RödaScope; 7 | 8 | public class RödaReference extends RödaValue { 9 | private String target; 10 | private RödaScope scope; 11 | 12 | private String file; 13 | private int line; 14 | 15 | private RödaReference(String target, RödaScope scope, String file, int line) { 16 | assumeIdentity(REFERENCE); 17 | this.target = target; 18 | this.scope = scope; 19 | this.file = file; 20 | this.line = line; 21 | } 22 | 23 | @Override public RödaValue copy() { 24 | return this; 25 | } 26 | 27 | @Override public String str() { 28 | RödaValue targetVal = unsafeResolve(); 29 | return "" : targetVal.str()) + ">"; 30 | } 31 | 32 | @Override public String target() { 33 | return target; 34 | } 35 | 36 | @Override public RödaValue resolve(boolean implicite) { 37 | RödaValue t = scope.resolve(target); 38 | if (t == null) unknownName("variable not found " + (implicite ? "" : "(via explicite reference)") 39 | + ": " + target + " (at " + file + ":" + line + ")"); 40 | return t; 41 | } 42 | 43 | @Override public RödaValue unsafeResolve() { 44 | return scope.resolve(target); 45 | } 46 | 47 | @Override public RödaValue impliciteResolve() { 48 | return resolve(true); 49 | } 50 | 51 | @Override public void assign(RödaValue value) { 52 | scope.set(target, value); 53 | } 54 | 55 | @Override public void assignLocal(RödaValue value) { 56 | scope.setLocal(target, value); 57 | } 58 | 59 | @Override public boolean strongEq(RödaValue value) { 60 | error("can't compare a reference"); 61 | return false; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return target.hashCode() + scope.hashCode(); 67 | } 68 | 69 | public static RödaReference of(String target, RödaScope scope, String file, int line) { 70 | return new RödaReference(target, scope, file, line); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/org/kaivos/röda/type/RödaString.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.type; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | import static org.kaivos.röda.Interpreter.outOfBounds; 5 | import static org.kaivos.röda.Interpreter.typeMismatch; 6 | 7 | import java.util.Arrays; 8 | import java.util.regex.Pattern; 9 | 10 | import org.kaivos.röda.Parser.ExpressionTree.CType; 11 | import org.kaivos.röda.RödaValue; 12 | 13 | public class RödaString extends RödaValue { 14 | private String text; 15 | private Pattern pattern; 16 | 17 | private RödaString(String text) { 18 | assumeIdentity(STRING); 19 | this.text = text; 20 | } 21 | 22 | private RödaString(Pattern pattern) { 23 | this(pattern.pattern()); 24 | this.pattern = pattern; 25 | } 26 | 27 | @Override public RödaValue copy() { 28 | return this; 29 | } 30 | 31 | @Override public String str() { 32 | return text; 33 | } 34 | 35 | @Override public Pattern pattern() { 36 | if (pattern != null) return pattern; 37 | else return super.pattern(); 38 | } 39 | 40 | @Override public long integer() { 41 | try { 42 | return Long.parseLong(text); 43 | } catch (NumberFormatException e) { 44 | typeMismatch("can't convert '" + text + "' to a number"); 45 | return -1; 46 | } 47 | } 48 | 49 | @Override public RödaValue length() { 50 | return RödaInteger.of(text.length()); 51 | } 52 | 53 | @Override public RödaValue slice(RödaValue startVal, RödaValue endVal, RödaValue stepVal) { 54 | long step = stepVal == null ? 1 : stepVal.integer(); 55 | long start = startVal != null ? startVal.integer() : step > 0 ? 0 : -1; 56 | if (start < 0) start = text.length()+start; 57 | long end; 58 | if (endVal == null) { 59 | if (step < 0) end = -1; 60 | else end = text.length(); 61 | } 62 | else { 63 | end = endVal.integer(); 64 | if (end < 0) end = text.length()+end; 65 | if (step > 0 && end == 0 && start > 0) end = text.length(); 66 | } 67 | if (start > Integer.MAX_VALUE || end > Integer.MAX_VALUE) 68 | outOfBounds("string index out of bounds: too large number: " + (start > end ? start : end)); 69 | if (step == 1) 70 | return of(text.substring((int) start, (int) end)); 71 | StringBuilder newString = new StringBuilder(); 72 | if (step > 0) 73 | for (int i = (int) start; i < end; i += step) newString.append(text.charAt(i)); 74 | else if (step < 0) 75 | for (int i = (int) start; i > end; i += step) newString.append(text.charAt(i)); 76 | return of(newString.toString()); 77 | } 78 | 79 | @Override public RödaValue containsValue(RödaValue seq) { 80 | return RödaBoolean.of(text.indexOf(seq.str()) >= 0); 81 | } 82 | 83 | @Override 84 | public RödaValue callOperator(CType operator, RödaValue value) { 85 | if (operator == CType.MUL ? !value.is(INTEGER) : !value.is(STRING)) 86 | typeMismatch("can't " + operator.name() + " " + typeString() + " and " + value.typeString()); 87 | switch (operator) { 88 | case MUL: 89 | String a = ""; 90 | for (int i = 0; i < value.integer(); i++) a += this.str(); 91 | return RödaString.of(a); 92 | case DIV: 93 | return RödaList.of(Arrays.stream(this.str().split(value.str())).map(RödaString::of).collect(toList())); 94 | case LT: 95 | return RödaBoolean.of(this.str().compareTo(value.str()) < 0); 96 | case GT: 97 | return RödaBoolean.of(this.str().compareTo(value.str()) > 0); 98 | case LE: 99 | return RödaBoolean.of(this.str().compareTo(value.str()) <= 0); 100 | case GE: 101 | return RödaBoolean.of(this.str().compareTo(value.str()) >= 0); 102 | case MATCHES: 103 | if (!value.is(STRING)) typeMismatch("tried to MATCH " + value.typeString()); 104 | if (((RödaString) value).pattern != null) 105 | return RödaBoolean.of(((RödaString) value).pattern.matcher(text).matches()); 106 | else 107 | return RödaBoolean.of(text.matches(value.str())); 108 | case NO_MATCH: 109 | if (!value.is(STRING)) typeMismatch("tried to NO_MATCH " + value.typeString()); 110 | if (((RödaString) value).pattern != null) 111 | return RödaBoolean.of(!((RödaString) value).pattern.matcher(text).matches()); 112 | else 113 | return RödaBoolean.of(!text.matches(value.str())); 114 | default: 115 | return super.callOperator(operator, value); 116 | } 117 | } 118 | 119 | @Override public boolean strongEq(RödaValue value) { 120 | return value.is(STRING) && value.str().equals(text); 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | return text.hashCode(); 126 | } 127 | 128 | public static RödaString of(String text) { 129 | return new RödaString(text); 130 | } 131 | 132 | public static RödaString of(Pattern pattern) { 133 | return new RödaString(pattern); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /test/org/kaivos/röda/test/LexerTest.java: -------------------------------------------------------------------------------- 1 | package org.kaivos.röda.test; 2 | 3 | import static java.util.stream.Collectors.joining; 4 | 5 | import org.junit.*; 6 | import static org.junit.Assert.*; 7 | 8 | import org.kaivos.röda.Parser; 9 | import org.kaivos.nept.parser.TokenList; 10 | import org.kaivos.nept.parser.ParsingException; 11 | 12 | public class LexerTest { 13 | 14 | String lex(String code) { 15 | TokenList tl = Parser.t.tokenize(code, ""); 16 | return joinTokens(tl); 17 | } 18 | 19 | String joinTokens(TokenList tl) { 20 | return tl.toList().stream() 21 | .map(token -> token.toString().replaceAll(",", ",,")) 22 | .collect(joining(", ")); 23 | } 24 | 25 | @Test 26 | public void testSimpleProgram() { 27 | assertEquals("main, {, }, ", lex("main{}")); 28 | } 29 | 30 | @Test 31 | public void testEmptyProgram() { 32 | assertEquals("", lex("")); 33 | } 34 | 35 | @Test 36 | public void testEmptyProgramWithSpaces() { 37 | assertEquals("", lex(" \t \t")); 38 | } 39 | 40 | @Test 41 | public void testEmptyProgramWithNewlines() { 42 | assertEquals("\n, \n, ", lex(" \n \n")); 43 | } 44 | 45 | @Test 46 | public void testEmptyProgramWithEscapedNewlines() { 47 | assertEquals("", lex(" \\\n \\\n")); 48 | } 49 | 50 | @Test 51 | public void testOperators() { 52 | assertEquals("<, ->, ), ;, .., [, %, ., (, >, ~=, }, #, =, ], {, ++, |, \n, ..., :, \", , \", &, ", 53 | lex("<->);..[%.(>~=}#=]{++|\n...:\"\"&")); 54 | } 55 | 56 | @Test 57 | public void testOperatorsAndText() { 58 | assertEquals("_, <, a, ->, b, ), c, ;, d, .., e, [, f, %, g, ., h, (, i, >, j, " 59 | + "}, k, #, l, =, m, ], n, {, o, |, p, \n, q, ..., r, :, s, \", , \", t, &, u, ", 60 | lex("_b)c;d..e[f%g.h(i>j}k#l=m]n{o|p\nq...r:s\"\"t&u")); 61 | } 62 | 63 | @Test 64 | public void testDots() { 65 | assertEquals("..., ..., .., ..., .., ..., ., ., ", 66 | lex("...... .. ..... .... .")); 67 | } 68 | 69 | @Test 70 | public void testLinesAndAngles() { 71 | assertEquals("--, <, >, --, ->, <, --, <, >>, >, <, ->, --, ", 72 | lex("--<>---><--<>>><->--")); 73 | } 74 | 75 | @Test 76 | public void testLinesAndText() { 77 | assertEquals("a, -, b, --, --, d, -, e, f, --, gh, -, ij, -, k, -, ", 78 | lex("a-b-- --d -e f--gh-ij- k-")); 79 | } 80 | 81 | @Test 82 | public void testStrings() { 83 | assertEquals("\", abba, \", ", 84 | lex("\"abba\"")); 85 | } 86 | 87 | @Test 88 | public void testStringsAndEscapeCodes() { 89 | assertEquals("\", abb\na\"\u00e8t, \", ", 90 | lex("\"abb\\na\\\"\\xe8t\"")); 91 | } 92 | 93 | @Test(expected=ParsingException.class) 94 | public void testUnclosedString() { 95 | lex("\"abba"); 96 | } 97 | 98 | @Test(expected=ParsingException.class) 99 | public void testUnclosedStringWithEscapeCodeAtEnd() { 100 | lex("\"abba\\\""); 101 | } 102 | } 103 | --------------------------------------------------------------------------------