├── .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, \",