├── .editorconfig
├── .gitignore
├── Makefile
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── me
│ │ └── arrdem
│ │ └── ox
│ │ ├── Named.java
│ │ ├── Namespaced.java
│ │ ├── Nat.java
│ │ └── Number.java
└── kotlin
│ └── me
│ └── arrdem
│ └── ox
│ ├── compiler.kt
│ ├── core.kt
│ ├── iterators.kt
│ ├── memfn.kt
│ ├── printer.kt
│ ├── reader.kt
│ ├── repl.kt
│ └── scanner.kt
└── test
└── kotlin
└── me
└── arrdem
└── ox
├── iterators.kt
├── natural.kt
├── printer.kt
├── reader.kt
└── scanner.kt
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 | charset = utf-8
10 | indent_style = space
11 | indent_size = 2
12 | ## tweete
13 | max_line_length = 100
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 | .idea/*
4 | .attach_pid*
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: format
2 | format:
3 | astyle --recursive -i --mode=java -s2 src/java\*
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ox - A tractable Lisp
2 |
3 | Ox is a small lisp for the JVM, intended to make it possible to write
4 | JVM programs without all the ceremony of Java.
5 |
6 | ## Usage
7 |
8 | Don't. Ox was built to make good on years of threats, and for fun.
9 |
10 | ## License
11 |
12 | Ox is copyright © Reid 'arrdem' McKenzie 2019.
13 |
14 | Source code is made available at my pleasure for the education of the
15 | reader. No license to package, use or distribute is granted, nor is
16 | any warranty of suitability for any purpose.
17 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | me.arrdem
5 | ox
6 | jar
7 | 1.0-SNAPSHOT
8 | ox
9 | http://ox.arrdem.com
10 |
11 |
12 | 1.3.31
13 | 4.12
14 | 1.10
15 | 1.10
16 |
17 |
18 |
19 |
20 |
21 | org.apache.maven.plugins
22 | maven-antrun-plugin
23 | 1.8
24 |
25 |
26 | ktlint
27 | verify
28 |
29 |
30 |
32 |
33 |
34 |
35 |
36 | run
37 |
38 |
39 | ktlint-format
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 | run
50 |
51 |
52 |
53 |
54 | com.pinterest
55 | ktlint
56 | 0.32.0
57 |
58 |
59 |
60 |
61 |
62 |
63 | org.jetbrains.kotlin
64 | kotlin-maven-plugin
65 | ${kotlin.version}
66 |
67 |
68 | compile
69 | compile
70 |
71 |
72 | ${project.basedir}/src/main/kotlin
73 | ${project.basedir}/src/main/java
74 |
75 |
76 |
77 |
78 | test-compile
79 | test-compile
80 |
81 |
82 | ${project.basedir}/src/test/kotlin
83 | ${project.basedir}/src/test/java
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | org.apache.maven.plugins
92 | maven-compiler-plugin
93 | 3.5.1
94 |
95 |
96 |
97 | default-compile
98 | none
99 |
100 |
101 |
102 | default-testCompile
103 | none
104 |
105 |
106 | java-compile
107 | compile
108 | compile
109 |
110 |
111 | java-test-compile
112 | test-compile
113 | testCompile
114 |
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-assembly-plugin
121 | 2.6
122 |
123 |
124 | make-assembly
125 | package
126 |
127 | single
128 |
129 |
130 |
131 |
132 | me.arrdem.ox.Scanners
133 |
134 |
135 |
136 | jar-with-dependencies
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | io.lacuna
148 | bifurcan
149 | 0.2.0-alpha1
150 |
151 |
152 |
153 | org.jetbrains.kotlin
154 | kotlin-stdlib
155 | ${kotlin.version}
156 |
157 |
158 |
159 | org.ow2.asm
160 | asm
161 | 7.1
162 |
163 |
164 |
165 | junit
166 | junit
167 | ${junit.version}
168 | test
169 |
170 |
171 |
172 | org.jetbrains.kotlin
173 | kotlin-test-junit
174 | ${kotlin.version}
175 | test
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/src/main/java/me/arrdem/ox/Named.java:
--------------------------------------------------------------------------------
1 | package me.arrdem.ox;
2 |
3 | public interface Named {
4 | public String name();
5 |
6 | static boolean isNamePiped(String name) {
7 | for (int chr: name.chars().toArray()) {
8 | if (Character.isWhitespace(chr)) {
9 | return true;
10 | }
11 | }
12 | return false;
13 | }
14 |
15 | default boolean isPiped() {
16 | return isNamePiped(this.name());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/me/arrdem/ox/Namespaced.java:
--------------------------------------------------------------------------------
1 | package me.arrdem.ox;
2 |
3 | public interface Namespaced extends Named {
4 | public T namespace();
5 |
6 | default boolean isPiped() {
7 | if (Named.isNamePiped(this.name())) {
8 | return Boolean.TRUE;
9 | } else if(this.namespace() != null) {
10 | return this.namespace().isPiped();
11 | } else {
12 | return Boolean.FALSE;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/me/arrdem/ox/Nat.java:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-9-13
3 | *
4 | * The natural numbers.
5 | *
6 | * Inspired by 'What about the natural numbers' ~ Runciman 1989
7 | * https://dl.acm.org/citation.cfm?id=66989
8 | * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.56.3442&rep=rep1&type=pdf
9 | *
10 | * With thanks to José Manuel Calderón Trilla who presented this paper at PWLConf 2019.
11 | * https://pwlconf.org/2019/jose-trilla/
12 | *
13 | * Provides an implementation of REALLY BIG NUMBERS modeled as a pair (BigInteger, [thunk]) where the
14 | * BigInt holds the realized portion of the number, and the thunks are continuations which
15 | * will produce addends when called. This allows the value of the Nat to be incrementally realized.
16 | *
17 | * Nat implements the usual operations - addition, subtraction (saturating at zero), multiplication
18 | * and division (with div/0 defined to be infinity per Runciman).
19 | */
20 |
21 | package me.arrdem.ox;
22 |
23 | import io.lacuna.bifurcan.IList;
24 | import io.lacuna.bifurcan.Lists;
25 | import kotlin.jvm.functions.Function0;
26 | import kotlin.jvm.functions.Function2;
27 | import org.jetbrains.annotations.NotNull;
28 |
29 | import java.math.BigInteger;
30 |
31 | public final class Nat implements Number {
32 | private static IList> FEL = (IList>) Lists.EMPTY;
33 |
34 | private BigInteger value;
35 | private IList> thunks;
36 |
37 | public static Nat ZERO = Nat.of(BigInteger.ZERO);
38 | public static Nat ONE = Nat.of(BigInteger.ONE);
39 | public static Nat TWO = Nat.of(BigInteger.TWO);
40 | public static Nat TEN = Nat.of(BigInteger.TEN);
41 | public static Nat INFINITY = Nat.of(BigInteger.valueOf(Long.MAX_VALUE), () -> Nat.INFINITY);
42 |
43 | private Nat(BigInteger value, IList> thunks) {
44 | assert value.compareTo(BigInteger.ZERO) >= 0;
45 | this.value = value;
46 | this.thunks = thunks;
47 | }
48 |
49 | public static Nat of(long value) {
50 | return new Nat(BigInteger.valueOf(value), FEL);
51 | }
52 |
53 | public static Nat of(BigInteger value) {
54 | return new Nat(value, FEL);
55 | }
56 |
57 | public static Nat of(long value, Function0 thunk) {
58 | if (!(thunk instanceof MemFn0))
59 | thunk = MemFns.of(thunk);
60 |
61 | Function0 finalThunk = thunk;
62 | return new Nat(BigInteger.valueOf(value), FEL.addLast(() -> {
63 | Object v = finalThunk.invoke();
64 | assert v instanceof Nat : "Contract violation! Thunk did not return nat!";
65 | return (Nat) v;
66 | }));
67 | }
68 |
69 | public static Nat of(BigInteger value, Function0 thunk) {
70 | if (!(thunk instanceof MemFn0))
71 | thunk = MemFns.of(thunk);
72 |
73 | Function0 finalThunk = thunk;
74 | return new Nat(value, FEL.addLast(() -> {
75 | Object v = finalThunk.invoke();
76 | assert v instanceof Nat : "Contract violation! Thunk did not return nat!";
77 | return (Nat) v;
78 | }));
79 | }
80 |
81 | public Nat succ() {
82 | return new Nat(BigInteger.ONE, FEL.addLast(() -> this));
83 | }
84 |
85 | public Nat add(Nat other) {
86 | return new Nat(this.value.add(other.value), Lists.concat(this.thunks, other.thunks));
87 | }
88 |
89 | private boolean maybeStep() {
90 | if(!this.thunks.equals(FEL)) {
91 | Function0 thunk = this.thunks.first();
92 | this.thunks = this.thunks.removeFirst();
93 |
94 | /**
95 | * FIXME (arrdem 2019-09-16)
96 | * This could be optimized by skipping zero terms maybe?
97 | */
98 | Nat next = thunk.invoke();
99 | if (next != null) {
100 | this.value = this.value.add(next.value);
101 | if(!next.thunks.equals(FEL))
102 | this.thunks = Lists.concat(this.thunks, next.thunks);
103 |
104 | return true;
105 | }
106 | }
107 | return false;
108 | }
109 |
110 | public Nat decs() {
111 | return this.subtract(ONE);
112 | }
113 |
114 | /**
115 | * Subtraction is implemented by LAZILY evaluating both ths and other until either other has been
116 | * fully evaluated, or the subtrand has been reduced to zero.
117 | *
118 | * Note that this is LAZY subtraction, so it WILL loop given infinite structures.
119 | */
120 | public Nat subtract(Nat other) {
121 | while(true) {
122 | // If the other side is smaller and it can be stepped, step it.
123 | if(!other.thunks.equals(Lists.EMPTY) && other.value.compareTo(this.value) <= 0)
124 | other.maybeStep();
125 |
126 | // If our side is smaller and it can be stepped, step it
127 | else if (!this.thunks.equals(Lists.EMPTY) && this.value.compareTo(other.value) <= 0)
128 | this.maybeStep();
129 |
130 | // If we've grounded out and one side is fully evaluated, do the math.
131 | else if (this.thunks.equals(Lists.EMPTY) || other.thunks.equals(Lists.EMPTY)) {
132 | if (this.value.compareTo(other.value) > 0)
133 | return new Nat(this.value.subtract(other.value), this.thunks);
134 | else
135 | return ZERO;
136 | }
137 | }
138 | }
139 |
140 | /**
141 | * Multiplication is tricky. Product of sums is sum of products, so one possible coding here is
142 | * to maintain a sequence of factors against which the Nat has been multiplied much like the
143 | * sequence of unrealized addends. Under this coding the tricky bit is as usual when does it
144 | * make sense to step what terms. Multiplying infinities for instance is a really essential case
145 | * which should be possible in a small amount of time - not a divergent computation.
146 | *
147 | * So the coding we use here is lazy iterated addition. We construct a new nat defined by
148 | * recursively (lazily!) adding this to itself other times. This strategy provides the incremental
149 | * realization behavior we want although perhaps not the performance one may wish.
150 | */
151 | public Nat multiply(Nat other) {
152 | // Multiplying by zero is the base case and zero
153 | if(this.equals(ZERO) || (other.thunks.equals(FEL) && other.value.equals(BigInteger.ZERO)))
154 | return ZERO;
155 |
156 | else if(other.equals(ONE))
157 | return this;
158 |
159 | // Multiplying realized numbers is done eagerly
160 | else if (other.thunks.equals(FEL) && this.thunks.equals(FEL))
161 | return Nat.of(this.value.multiply(other.value));
162 |
163 | // Otherwise we use the recursive lazy coding
164 | else
165 | return this.add(this).add(Nat.of(0, () -> this.multiply(other.subtract(TWO))));
166 | }
167 |
168 | /**
169 | * Division is slightly less tricky - Division by zero is well defined (to be infinity). The
170 | * machine number case of division is just delegation, and the general case is iterated
171 | * subtraction. That is - since quotient * divisor <= dividend we can compute the dividend by
172 | * counting the number of times we can subtract the divisor from the dividend.
173 | *
174 | * Consequently this implementation uses a helper function which constructs a thunk which tracks
175 | * the (remaining) dividend (as current) and the divisor (as other). Each thunk call produces a 1
176 | * with a new thunk reduced by the divisor - so this way we're incrementally counting down. Not
177 | * ideal - but correct for deeply thunked structures eg the minimal infinity.
178 | */
179 | private static final Function2> df = ((Nat current, Nat other) -> () -> {
180 | Nat difference = current.subtract(other);
181 | if (current.compareTo(other) >= 0 && difference.compareTo(ZERO) >= 0) {
182 | return Nat.of(1, () -> difference.divide(other));
183 | } else {
184 | return ZERO;
185 | }
186 | });
187 |
188 | public Nat divide(Nat other) {
189 | if(other.equals(ZERO))
190 | return INFINITY;
191 |
192 | else if(this.equals(ZERO))
193 | return ZERO;
194 |
195 | else if(this.thunks.equals(FEL) && other.thunks.equals(FEL))
196 | return Nat.of(this.value.divide(other.value));
197 |
198 | else
199 | return df.invoke(this, other).invoke();
200 | }
201 |
202 | /**
203 | * There are two obvious strategies here. One is to compute the division, then as division rounds
204 | * down multiply and subtract to get the loss. This is HUGELY expensive and undefined when
205 | * computing modulus by zero, so instead we re-implement the division machinery as a lazy cons of
206 | * 0-valued cells which perform one subtraction until the remainder is computed. Relative to the
207 | * first strategy this saves us a "lazy" multiplication which must be forced completely for an
208 | * equally "lazy" subtraction so the lexical complexity of duplication division is worthwhile.
209 | */
210 | private static final Function2> rf = ((Nat current, Nat other) -> () -> {
211 | Nat difference = current.subtract(other);
212 | System.out.println(String.format("current: %s, other: %s => %s", current, other, difference));
213 | if (difference.compareTo(other) < 0) {
214 | System.out.println(String.format("Yielding %s", difference));
215 | return difference;
216 | } else {
217 | System.out.println("Not done yet...");
218 | return Nat.of(0, () -> difference.remainder(other));
219 | }
220 | });
221 |
222 | public Nat remainder(Nat other) {
223 | if(other.equals(ZERO))
224 | return INFINITY;
225 |
226 | else if(this.equals(ZERO))
227 | return ZERO;
228 |
229 | else if(other.equals(ONE))
230 | return ZERO;
231 |
232 | else if(this.thunks.equals(FEL) && other.thunks.equals(FEL))
233 | return Nat.of(this.value.remainder(other.value));
234 |
235 | else
236 | return rf.invoke(this, other).invoke();
237 | }
238 |
239 | /**
240 | * A way to force the entire (potentially infinite!) value of the current Nat.
241 | */
242 | public BigInteger get() {
243 | while(this.maybeStep()) {}
244 | return this.value;
245 | }
246 |
247 | /**
248 | * Comparison. Which is hard because we want to realize as little of either side as we can
249 | * possibly get away with.
250 | */
251 | @Override
252 | public int compareTo(@NotNull Nat other) {
253 | if(this == other)
254 | return 0;
255 |
256 | while(true) {
257 | // We've exhausted this value, other has more AND is gt -> lt
258 | if (this.thunks.equals(Lists.EMPTY) &&
259 | this.value.compareTo(other.value) < 0)
260 | return -1;
261 |
262 | // We've exhausted the other, and other's lt -> we're gt
263 | else if (other.thunks.equals(Lists.EMPTY) &&
264 | this.value.compareTo(other.value) > 0)
265 | return 1;
266 |
267 | // We've exhausted both
268 | else if (this.thunks.equals(Lists.EMPTY) &&
269 | other.thunks.equals(Lists.EMPTY))
270 | return this.value.compareTo(other.value);
271 |
272 | else {
273 | // If the other side is smaller and it can be stepped, step it.
274 | if (!other.thunks.equals(Lists.EMPTY) &&
275 | other.value.compareTo(this.value) <= 0)
276 | other.maybeStep();
277 |
278 | // If our side is smaller and it can be stepped, step it
279 | else if (!this.thunks.equals(Lists.EMPTY) &&
280 | this.value.compareTo(other.value) <= 0)
281 | this.maybeStep();
282 | }
283 | }
284 | }
285 |
286 | public boolean equals(Object other) {
287 | if (this == other)
288 | return true;
289 |
290 | if (!(other instanceof Nat))
291 | return false;
292 |
293 | return this.compareTo((Nat) other) == 0;
294 | }
295 |
296 | public String toString() {
297 | return String.format("",
298 | this.value.toString(10),
299 | !this.thunks.equals(Lists.EMPTY));
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/java/me/arrdem/ox/Number.java:
--------------------------------------------------------------------------------
1 | package me.arrdem.ox;
2 |
3 | public interface Number extends Comparable {
4 | T add(T other);
5 | T subtract(T other);
6 | T multiply(T other);
7 | T divide(T other);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/compiler.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-09-07
3 | *
4 | * The Ox compiler
5 | */
6 |
7 | package me.arrdem.ox
8 |
9 | import io.lacuna.bifurcan.Maps
10 | import java.lang.ref.ReferenceQueue
11 | import java.lang.ref.SoftReference
12 | import java.time.Instant
13 | import java.util.*
14 | import javax.naming.Name
15 |
16 | class OxClassLoader : ClassLoader() {
17 | private var classCache = Maps.EMPTY as Map>>
18 | private val rq = ReferenceQueue>()
19 |
20 | @Synchronized
21 | private fun cleanCache() {
22 | while (rq.poll() != null)
23 | for (e in classCache) {
24 | val v = e.value()
25 | if (v.get() == null)
26 | classCache = classCache.remove(e.key())
27 | }
28 | }
29 |
30 | @Synchronized
31 | fun defineClass(name: String, bytes: ByteArray): Class<*> {
32 | cleanCache()
33 | val c = defineClass(name, bytes, 0, bytes.size)
34 | classCache = classCache.put(name, SoftReference(c, rq))
35 | return c
36 | }
37 |
38 | override fun findClass(name: String): Class<*> {
39 | val cr: SoftReference>? = classCache.get(name, null)
40 | if (cr != null) {
41 | val c = cr.get()
42 | if (c != null) return c
43 | else classCache.remove(name, cr)
44 | }
45 | cleanCache()
46 | return super.findClass(name)
47 | }
48 | }
49 |
50 | class Environment(val loader: OxClassLoader = OxClassLoader()) {
51 |
52 | }
53 |
54 | object Compiler {
55 | fun analyze(o: Any) {
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/core.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-9-31
3 | *
4 | * The Ox 'core'.
5 | * Really just a pile of small bits and bats that didn't deserve their own files.
6 | */
7 |
8 | package me.arrdem.ox
9 |
10 | /**
11 | * Type aliases that win by default so we just use Zach's fine datastructures everywhere.
12 | */
13 | typealias Map = io.lacuna.bifurcan.Map
14 | typealias List = io.lacuna.bifurcan.List
15 | typealias Set = io.lacuna.bifurcan.Set
16 |
17 | /**
18 | * A less fine datastructure
19 | */
20 | data class Cons(public val car: T, public val cdr: Cons? = null);
21 |
22 | /**
23 | * Symbol - and the Symbols helper class.
24 | */
25 | data class Symbol(private val name: String,
26 | private val namespace: Symbol? = null):
27 | Namespaced
28 | {
29 | override fun name(): String {
30 | return this.name
31 | }
32 |
33 | override fun namespace(): Symbol? {
34 | return this.namespace
35 | }
36 | }
37 |
38 | object Symbols {
39 | @JvmStatic
40 | fun of(s: String): Symbol {
41 | var name: String? = null
42 | var segments = List()
43 | val chunks = s.split(Regex("/"), 1)
44 | if (chunks.count() == 2) {
45 | name = chunks[1]
46 | for (segment in chunks[0].split(".")) {
47 | segments.addLast(segment)
48 | }
49 | var root = Symbol(segments.first(), null)
50 | segments = segments.removeFirst()
51 | for (segment in segments) {
52 | root = Symbol(segment, root)
53 | }
54 | return Symbol(name, root)
55 | } else {
56 | return Symbol(s, null)
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * Keyword - and the Keywords helper class
63 | *
64 | * Basically the same as symbols. Should really figure out how to factor this better.
65 | */
66 | data class Keyword(private val name: String,
67 | private val namespace: Keyword? = null):
68 | Namespaced
69 | {
70 | override fun name(): String {
71 | return this.name
72 | }
73 |
74 | override fun namespace(): Keyword? {
75 | return this.namespace
76 | }
77 | }
78 |
79 | object Keywords {
80 | @JvmStatic
81 | fun of(s: String): Keyword {
82 | var name: String? = null
83 | var segments = List()
84 | val chunks = s.split(Regex("/"), 1)
85 | if (chunks.count() == 2) {
86 | name = chunks[1]
87 | for (segment in chunks[0].split(".")) {
88 | segments.addLast(segment)
89 | }
90 | var root = Keyword(segments.first(), null)
91 | segments = segments.removeFirst()
92 | for (segment in segments) {
93 | root = Keyword(segment, root)
94 | }
95 | return Keyword(name, root)
96 | } else {
97 | return Keyword(s, null)
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * A container for "meta" tag syntax objects.
104 | */
105 | data class Meta(val meta: Any?, val expr: Any?);
106 |
107 | /**
108 | * A container for #tag <> objects.
109 | */
110 | data class Tag(val tag: Any, val expr: Any);
111 |
112 | fun sneakyThrow(e: Throwable) {
113 | throw e as E
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/iterators.kt:
--------------------------------------------------------------------------------
1 | package me.arrdem.ox
2 |
3 | import java.util.Iterator
4 |
5 | interface ICurrentIterator: Iterator {
6 | fun current(): T
7 | }
8 |
9 | class CurrentIterator(private val iter: Iterator,
10 | private var current: T? = if (iter.hasNext()) iter.next() else null):
11 | ICurrentIterator
12 | {
13 | override fun current(): T {
14 | return current as T
15 | }
16 |
17 | override fun hasNext(): Boolean {
18 | return iter.hasNext()
19 | }
20 |
21 | override fun next(): T {
22 | current = iter.next()
23 | return current as T
24 | }
25 | }
26 |
27 | interface IPeekIterator: ICurrentIterator {
28 | fun hasPeek(): Boolean
29 | fun peek(): T
30 | }
31 |
32 | class PeekIterator(private val iter: Iterator,
33 | private var current: T? = if (iter.hasNext()) iter.next() else null,
34 | private var next: T? = if (iter.hasNext()) iter.next() else null):
35 | IPeekIterator
36 | {
37 | override fun hasPeek(): Boolean {
38 | return next != null || iter.hasNext()
39 | }
40 |
41 | override fun peek(): T {
42 | return next as T
43 | }
44 |
45 | override fun current(): T {
46 | return current as T
47 | }
48 |
49 | override fun hasNext(): Boolean {
50 | return next != null
51 | }
52 |
53 | override fun next(): T {
54 | current = next
55 | if (iter.hasNext()) {
56 | next = iter.next()
57 | } else {
58 | next = null
59 | }
60 | return current as T
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/memfn.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-9-13
3 | *
4 | * A memoizing function.
5 | * Calls the parent thunk once, caches its value and always returns that value.
6 | * Also re-throws any exception raised on the first call every time.
7 | */
8 |
9 | package me.arrdem.ox
10 |
11 | class MemFn0 internal constructor(private val fn: Function0) : Function0 {
12 | private var `val`: R? = null
13 | private var ex: Exception? = null
14 | private var valid = false
15 |
16 | override fun invoke(): R {
17 | if (!this.valid) {
18 | this.valid = true
19 | try {
20 | this.`val` = fn.invoke()
21 | } catch (e: Exception) {
22 | this.ex = e
23 | throw e
24 | }
25 |
26 | }
27 | if (this.ex != null)
28 | sneakyThrow(this.ex!!)
29 | return this.`val`!!
30 | }
31 | }
32 |
33 | class MemFn1 internal constructor(private val fn: Function1) : Function1 {
34 | private var `val`: R? = null
35 | private var ex: Exception? = null
36 | private var valid = false
37 |
38 | override fun invoke(o0: A): R {
39 | if (!this.valid) {
40 | this.valid = true
41 | try {
42 | this.`val` = fn.invoke(o0)
43 | } catch (e: Exception) {
44 | this.ex = e
45 | throw e
46 | }
47 |
48 | }
49 | if (this.ex != null)
50 | sneakyThrow(this.ex!!)
51 | return this.`val`!!
52 | }
53 | }
54 |
55 | class MemFn2 internal constructor(private val fn: Function2) : Function2 {
56 | private var `val`: R? = null
57 | private var ex: Exception? = null
58 | private var valid = false
59 |
60 | override fun invoke(o0: A, o1: B): R {
61 | if (!this.valid) {
62 | this.valid = true
63 | try {
64 | this.`val` = fn.invoke(o0, o1)
65 | } catch (e: Exception) {
66 | this.ex = e
67 | throw e
68 | }
69 |
70 | }
71 | if (this.ex != null)
72 | sneakyThrow(this.ex!!)
73 | return this.`val`!!
74 | }
75 | }
76 |
77 | object MemFns {
78 | @JvmStatic
79 | fun of(fn: Function0): MemFn0 {
80 | return MemFn0(fn)
81 | }
82 |
83 | @JvmStatic
84 | fun of(fn: Function1): MemFn1 {
85 | return MemFn1(fn)
86 | }
87 |
88 | @JvmStatic
89 | fun of(fn: Function2): MemFn2 {
90 | return MemFn2(fn)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/printer.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-9-31
3 | *
4 | * The Ox printer.
5 | * Dual to the scanner/reader in theory.
6 | */
7 |
8 | package me.arrdem.ox
9 |
10 | import io.lacuna.bifurcan.Maps
11 | import java.io.ByteArrayOutputStream
12 | import java.io.OutputStreamWriter
13 | import io.lacuna.bifurcan.List as BList
14 | import io.lacuna.bifurcan.Map as BMap
15 | import io.lacuna.bifurcan.Set as BSet
16 |
17 |
18 | /**
19 | * Sadly in Kotlin, type aliases are not recursive.
20 | * They're just macros it seems.
21 | *
22 | * This means that I can't actually capture the recursive type of PrintFn in Kotlin.
23 | * So this whole package has to play some games.
24 | * Define EPM (Erased Print Map) - the ground type we'll have to upcast from
25 | * Define EFN (Erased Print FuNction) - the erased form of the individual print methods
26 | * Define the "Real"(est) PrintMap type
27 | * Define the "Real"(est) PrintFn type
28 | *
29 | * When we call PrintFns, we'll have ALMOST the fully recursive type signature to hand.
30 | * But when we go through the dispatch table, we'll have to "promote" the erased functions we get
31 | * out of the dispatch table to "real" functions which have a near-full signature.
32 | */
33 | typealias EPM = BMap>
34 | typealias EFN = ((Printer, EPM, OutputStreamWriter, Any) -> Unit)
35 | typealias PrintMap = BMap
36 | typealias PrintFn = ((Printer, PrintMap, OutputStreamWriter, Any?) -> Unit)
37 |
38 | interface Printable {
39 | fun print(p: Printer, pm: PrintMap, o: OutputStreamWriter): Unit
40 | }
41 |
42 | interface Tagged: Printable {
43 | fun tag(): Symbol
44 | fun value(): Any
45 |
46 | override fun print(p: Printer, pm: PrintMap, o: OutputStreamWriter) {
47 | o.write("#")
48 | p.print(pm, o, this.tag())
49 | p.print(pm, o, this.value())
50 | }
51 | }
52 |
53 | /**
54 | * Helper for printing sequential types, the grammar for which is "$start(<>($delim<>)+)$end"
55 | * where <> is taken to mean recursive printing.
56 | */
57 | fun printListy(p: Printer,
58 | pm: PrintMap,
59 | w: OutputStreamWriter,
60 | coll: Iterable,
61 | start: String,
62 | f: PrintFn,
63 | delim: String?,
64 | end: String) {
65 | var isFirst = true
66 | w.write(start)
67 | for (e in coll) {
68 | if (!isFirst && delim != null) {
69 | w.write(delim)
70 | } else {
71 | isFirst = false
72 | }
73 | f.invoke(p, pm, w, e)
74 | }
75 | w.write(end)
76 | }
77 |
78 | /**
79 | * Helper for printing named types, the grammar for which is
80 | * "$prefix(<>(.<>)+)/$name)$suffix"
81 | * where <> is taken to be a name segment pulled from a parent namespace.
82 | */
83 | fun >?>
84 | printNamespacy(p: Printer,
85 | pm: PrintMap,
86 | w: OutputStreamWriter,
87 | prefix: String,
88 | o: Namespaced,
89 | suffix: String) {
90 | var segments = BList()
91 | var parent = o.namespace()
92 | while (parent != null) {
93 | segments = segments.addFirst(parent.name())
94 | parent = parent.namespace() as T
95 | }
96 |
97 | w.write(prefix)
98 | if (segments.size() != 0L) {
99 | printListy(p, pm, w, segments, "", Printer::print, ".", "")
100 | w.write("/")
101 | }
102 | w.write(o.name())
103 | w.write(suffix)
104 | }
105 |
106 | /**
107 | * The Printer interface.
108 | *
109 | * The Printer is implemented as a trivial directly recursive call through a dispatch-by-type table
110 | * (this may later be refactored to extensible protocol / predicative dispatch) where table entries
111 | * are handlers which produce formatting for a given type and recursively call the printer with a
112 | * dispatching table.
113 | *
114 | * Callees may manipulate the dispatch table, but as it is immutable changes will not propagate
115 | * beyond the scope of a single print handler.
116 | *
117 | * The PrintMap is regrettably erased, but must be a map from Class instances to KFunction<>
118 | * instances.
119 | *
120 | * Each entry handles a concrete class (unlike a protocol which handles interfaces).
121 | * The handler convention is that the printer and the print map are arguments, and calless
122 | * are expected to recurse through the printer and print map rather than hijacking control.
123 | *
124 | * For instance, we know when printing a BMap that the elements will be Map.Entry and
125 | * so one could be tempted to directly call the appropriate function, but this leads to a loss of
126 | * extension capability as a user who isn't aware of this direct linking must discover it the hard
127 | * way and provide two overloads, not one.
128 | */
129 | class Printer(val defaultPrinter: PrintFn = Printer::printDefault as PrintFn) {
130 | fun printMapEntry(pm: PrintMap, w: OutputStreamWriter, o: Maps.Entry) {
131 | this.print(pm, w, o.key())
132 | w.write(" ")
133 | this.print(pm, w, o.value())
134 | }
135 |
136 | fun printMap(pm: PrintMap, w: OutputStreamWriter, o: BMap) {
137 | printListy(this, pm, w, o, "{", Printer::print, ", ", "}")
138 | }
139 |
140 | fun printList(pm: PrintMap, w: OutputStreamWriter, o: BList) {
141 | printListy(this, pm, w, o, "(", Printer::print, " ", ")")
142 | }
143 |
144 | fun printSet(pm: PrintMap, w: OutputStreamWriter, o: BSet) {
145 | printListy(this, pm, w, o, "#{", Printer::print, " ", "}")
146 | }
147 |
148 | fun printSymbol(pm: PrintMap, w: OutputStreamWriter, o: Symbol) {
149 | val prefix = if (o.isPiped) "|" else ""
150 | printNamespacy(this, pm, w, prefix, o, prefix)
151 | }
152 |
153 | fun printKeyword(pm: PrintMap, w: OutputStreamWriter, o: Keyword) {
154 | val prefix = if (o.isPiped) "|" else ""
155 | printNamespacy(this, pm, w, ":$prefix", o, prefix)
156 | }
157 |
158 | fun printString(pm: PrintMap, w: OutputStreamWriter, s: String) {
159 | // As a nice trick re-use the (reversed!) character escape map.
160 | val rem = Map.from(ESCAPE_MAP.map { i -> Maps.Entry(i.value(), i.key()) })
161 | assert(rem.size() == ESCAPE_MAP.size())
162 | w.write("\"")
163 | for (c in s) {
164 | when (val ech = rem.get(c, null)) {
165 | null -> w.write(c.toInt())
166 | else -> {w.write("\\"); w.write(ech.toInt())}
167 | }
168 | }
169 | w.write("\"")
170 | }
171 |
172 | fun printDefault(pm: PrintMap, s: OutputStreamWriter, o: Any?) {
173 | if (o == null)
174 | s.write("null")
175 | else if (o is Printable) {
176 | o.print(this, pm, s)
177 | } else {
178 | s.write(o.toString())
179 | }
180 | }
181 |
182 | fun print(pm: PrintMap, w: OutputStreamWriter, o: Any?): Unit {
183 | val fn = pm.get(o?.javaClass ?: null as Any?, this.defaultPrinter as EFN) as PrintFn
184 | fn(this, pm, w, o)
185 | }
186 | }
187 |
188 | object Printers {
189 | /**
190 | * Note that upcasts from EPM to PrintMap are presumed to be safe, as this is hwo we're breaking
191 | * type-level recursion which Kotlin doesn't support see the comment at the top of the file
192 | * which explains the type aliases.
193 | *
194 | * Note that upcasts FROM OBJECT TO THE KEY TYPE must be safe by definition of print().
195 | *
196 | * So we suppress all unchecked (generic to generic) casts.
197 | */
198 | @JvmStatic
199 | @Suppress("UNCHECKED_CAST")
200 | val BASE_PRINT_MAP: PrintMap = (
201 | PrintMap()
202 | // Print string
203 | .put(String::class.java as Any
204 | ) { p, pm, w, o -> p.printString(pm as PrintMap, w, o as String) }
205 | // Print Symbol
206 | .put(Symbol::class.java as Any
207 | ) { p, pm, w, o -> p.printSymbol(pm as PrintMap, w, o as Symbol) }
208 |
209 | // Print keyword
210 | .put(Keyword::class.java as Any
211 | ) { p, pm, w, o -> p.printKeyword(pm as PrintMap, w, o as Keyword) }
212 |
213 | // Print List
214 | .put(BList::class.java as Any
215 | ) { p, pm, w, o -> p.printList(pm as PrintMap, w, o as BList) }
216 |
217 | // Print Map.Entry
218 | .put(Maps.Entry::class.java as Any
219 | ) { p, pm, w, o -> p.printMapEntry(pm as PrintMap, w, o as Maps.Entry) }
220 |
221 | // Print Map
222 | .put(BMap::class.java as Any
223 | ) { p, pm , w, o -> p.printMap(pm as PrintMap, w, o as BMap) }
224 |
225 | // Print Set
226 | .put(BSet::class.java as Any
227 | ) { p, pm, w, o -> p.printSet(pm as PrintMap, w, o as BSet) }
228 |
229 | // Print TokenType, which is an enum and a witch to implement manually
230 | .put(TokenType::class.java as Any
231 | ) { printer, pm, w, v -> w.write(":ox.parser/${v.toString().toLowerCase()}") }
232 | )
233 |
234 | @JvmStatic
235 | fun pr(o: Any?): String {
236 | val w = ByteArrayOutputStream()
237 | val wtr = w.writer()
238 | Printer().print(BASE_PRINT_MAP, wtr, o)
239 | wtr.close()
240 | return String(w.toByteArray())
241 | }
242 |
243 | @JvmStatic
244 | fun println(o: Any?) {
245 | val w = System.out.writer()
246 | Printer().print(BASE_PRINT_MAP, w, o)
247 | w.write("\n")
248 | w.flush()
249 | }
250 | }
251 |
252 | object PrinterTest {
253 | @JvmStatic
254 | fun main(args: Array) {
255 | val p = Printer()
256 | val pm = Printers.BASE_PRINT_MAP
257 | val ow = System.out.writer()
258 |
259 | println("The print map ")
260 | for (kv in pm) {
261 | val key = kv.key()
262 | val value = kv.value()
263 | println(" $key => $value")
264 | }
265 |
266 | // symbols
267 | p.print(pm, ow, Symbols.of("test"))
268 | ow.write("\n")
269 | ow.flush()
270 |
271 | p.print(pm, ow, Symbols.of("me.arrdem/test"))
272 | ow.write("\n")
273 | ow.flush()
274 |
275 | p.print(pm, ow, Symbols.of("piped test"))
276 | ow.write("\n")
277 | ow.flush()
278 |
279 | // keywords
280 | p.print(pm, ow, Keywords.of("test2"))
281 | ow.write("\n")
282 | ow.flush()
283 |
284 | p.print(pm, ow, Keywords.of("io.oxlang/test2"))
285 | ow.write("\n")
286 | ow.flush()
287 |
288 | p.print(pm, ow, Keywords.of("piped test"))
289 | ow.write("\n")
290 | ow.flush()
291 | // maps
292 | p.print(pm, ow, BMap().put("a", 1).put("b", 2).put("c", 3))
293 | ow.write("\n")
294 | ow.flush()
295 |
296 | // lists
297 | p.print(pm, ow, BList.of(1L, 2L, 3L, 4L))
298 | ow.write("\n")
299 | ow.flush()
300 |
301 | // sets
302 | p.print(pm, ow, BSet.of(1L, 2L, 3L, 4L))
303 | ow.write("\n")
304 | ow.flush()
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/reader.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-8-31
3 | *
4 | * The Ox reader.
5 | * Consumes the Scanners to implement reading syntax or data (they aren't entirely the same).
6 | *
7 | * The data reader - DataReader - produces a tree of straight values which makes no attempt to
8 | * retain syntax information for later analysis.
9 | *
10 | * The syntax reader - SyntaxReader - produces a tree of datums wrapped in SyntaxObjects which
11 | * record start/end token positions. This provides some tools for the compiler to provide errors
12 | * and warnings without having to be tightly coupled to the precise machinery of the reader or
13 | * sharing any state between the two.
14 | */
15 |
16 | package me.arrdem.ox
17 |
18 | import io.lacuna.bifurcan.Maps
19 | import java.io.ByteArrayInputStream
20 | import java.util.Iterator
21 |
22 | typealias ERM = Map>
23 | typealias TokenIter = ICurrentIterator>
24 | typealias ReadFn = (Reader, ERM, Any, TokenIter) -> Any?
25 | typealias ReadMap = Map
26 |
27 | /**
28 | * The Reader.
29 | *
30 | * Contains shared machinery between the data and syntax readers. In fact the data reader is
31 | * expected to be pretty much "just" a shim to this reader.
32 | *
33 | * As a nod to the printer, which supports
34 | */
35 |
36 | data class SyntaxObject(
37 | val obj: Any?,
38 | val meta: Any? = null,
39 | val start: Token<*>? = null):
40 | Tagged
41 | {
42 | override fun tag(): Symbol {
43 | return Symbols.of("ox.reader/syntax")
44 | }
45 |
46 | override fun value(): Any {
47 | return List.of(this.obj, this.start)
48 | }
49 | }
50 |
51 | class ParseException(val location: StreamLocation<*>?,
52 | override val message: String?,
53 | override val cause: Throwable? = null) :
54 | Exception(message, cause) {
55 | override fun toString(): String {
56 | return "Parsing at $location: $message"
57 | }
58 | }
59 |
60 | open class Reader(private val notFound: ReadFn = Reader::readError as ReadFn) {
61 | open fun readListy(startType: TokenType,
62 | endType: TokenType,
63 | msg: String,
64 | ctor: ((Iterable) -> Any),
65 | rm: ReadMap,
66 | i: TokenIter): Any {
67 | val start = i.current().location
68 | val actualStartType = i.current().tokenType
69 | if (actualStartType != startType) {
70 | throw ParseException(start, "$msg: Expected open token $startType, got $actualStartType instead!")
71 | }
72 | try {
73 | i.next() // discard the start token
74 | var v = List()
75 | val sentinel = Object()
76 | val srm = rm.put(endType) { _: Any, _: Any, _: Any, _: Any -> sentinel } as ReadMap
77 | while (i.current().tokenType != endType) {
78 | val el = this.read(srm, i)
79 | if (el != sentinel)
80 | v = v.addLast(el)
81 | }
82 | i.next() // discard the end token
83 | return ctor(v)
84 | } catch (e: Exception) {
85 | throw ParseException(start, msg, e)
86 | }
87 | }
88 |
89 | fun readError(_rm: ERM, discard: Any, i: TokenIter): Any? {
90 | val t = i.current()
91 | val ttype = t.tokenType
92 | throw ParseException(t.location, "Unmapped token type: $ttype")
93 | }
94 |
95 | fun readNothing(_rm: ERM, discard: Any, i: TokenIter): Any? {
96 | i.next();
97 | return discard
98 | }
99 |
100 | fun readValue(_rm: ERM, discard: Any, i: TokenIter): Any? {
101 | val token = i.current()
102 | i.next()
103 | return token.value
104 | }
105 |
106 | fun readList(rm: ERM, discard: Any, i: TokenIter): Any? {
107 | return readListy(
108 | TokenType.LPAREN,
109 | TokenType.RPAREN,
110 | "While parsing () list, an exception occurred",
111 | { i: Iterable -> List.from(i) },
112 | rm as ReadMap,
113 | i
114 | )
115 | }
116 |
117 | fun readSqList(rm: ERM, discard: Any, i: TokenIter): Any? {
118 | return readListy(
119 | TokenType.LBRACKET,
120 | TokenType.RBRACKET,
121 | "While parsing [] list, an exception occurred",
122 | { i: Iterable -> List.from(i) },
123 | rm as ReadMap,
124 | i
125 | )
126 | }
127 |
128 | fun readSet(rm: ERM, discard: Any, i: TokenIter): Any {
129 | return readListy(
130 | TokenType.HASH_LBRACE,
131 | TokenType.RBRACE,
132 | "While parsing #{} set, an exception occurred",
133 | { i: Iterable -> Set.from(i) },
134 | rm as ReadMap,
135 | i
136 | )
137 | }
138 |
139 | fun readMap(rm: ERM, discard: Any, i: TokenIter): Any {
140 | val startLoc = i.current().location
141 | val kvs: Iterator = readListy(
142 | TokenType.LBRACE,
143 | TokenType.RBRACE,
144 | "While parsing {} map, an exception occurred",
145 | { i: Iterable -> i.iterator() },
146 | rm as ReadMap,
147 | i) as Iterator
148 |
149 | val m = Maps.EMPTY.linear()
150 | while (kvs.hasNext()) {
151 | val k = kvs.next()
152 | if (!kvs.hasNext())
153 | throw ParseException(startLoc, "Unmatched key $k")
154 | val v = kvs.next()
155 | m.put(k, v)
156 | }
157 | return m.forked()
158 | }
159 |
160 | /**
161 | * FIXME (arrdem 9/7/2019)
162 | * How to wire user constructors in here?
163 | */
164 | fun readTag(rm: ERM, discard: Any, i: TokenIter): Any? {
165 | i.next()
166 | val tag = this.read(rm as ReadMap, i)
167 | val obj = this.read(rm as ReadMap, i)
168 | return Tag(tag!!, obj!!)
169 | }
170 |
171 | fun readQuote(rm: ERM, discard: Any, i: TokenIter): Any? {
172 | i.next()
173 | val obj = this.read(rm as ReadMap, i)
174 | return List.of(Symbols.of("quote"), obj!!)
175 | }
176 |
177 | fun readMeta(rm: ERM, discard: Any, i: TokenIter): Any? {
178 | val startToc = i.current()
179 | i.next()
180 | val meta = this.read(rm as ReadMap, i)
181 | val obj = this.read(rm as ReadMap, i)
182 | return Meta(meta, obj)
183 | }
184 |
185 | /**
186 | * FIXME (arrdem 9/7/2019)
187 | * Can this be factored out as a reader parameter?
188 | */
189 | fun readSymbol(_rm: ERM, discard: Any, i: TokenIter): Any? {
190 | val token = i.current()
191 | i.next()
192 | return when (token.value as Symbol) {
193 | Symbols.of("null") -> null
194 | /**
195 | * FIXME (arrdem 9/7/2019)
196 | * Given a warning(s) / messages framework, warn on these or make 'em pluggable.
197 | */
198 | Symbols.of("nil") -> null
199 | Symbols.of("none") -> null
200 |
201 | Symbols.of("+nan"),
202 | Symbols.of("+NaN"),
203 | Symbols.of("NaN"),
204 | Symbols.of("nan") -> Double.NaN
205 |
206 | Symbols.of("-NaN"),
207 | Symbols.of("-nan") -> -1 * Double.NaN
208 |
209 | else -> token.value
210 | }
211 | }
212 |
213 | open fun read(rm: ReadMap, i: TokenIter): Any? {
214 | // yo dawg I heard u leik streams
215 | val startPos = i.current().location
216 | val discard: Any = Object()
217 | var read: Any? = discard
218 |
219 | while (i.current() != null && read == discard) {
220 | val handler = rm.get(i.current().tokenType, notFound) as ReadFn
221 | read = handler(this, rm as ERM, discard, i)
222 | }
223 |
224 | if (read != discard) {
225 | return read
226 | } else {
227 | throw ParseException(startPos, "Got end of file while reading!")
228 | }
229 | }
230 | }
231 |
232 | class SyntaxReader(private val notFound: ReadFn = Reader::readError as ReadFn):
233 | Reader(notFound = notFound) {
234 | override fun readListy(startType: TokenType,
235 | endType: TokenType,
236 | msg: String,
237 | ctor: ((Iterable) -> Any),
238 | rm: ReadMap,
239 | i: TokenIter): Any {
240 | val start = i.current().location
241 | val actualStartType = i.current().tokenType
242 | if (actualStartType != startType) {
243 | throw ParseException(start, "$msg: Expected open token $startType, got $actualStartType instead!")
244 | }
245 | try {
246 | i.next() // discard the start token
247 | var v = List()
248 | val sentinel = Object()
249 | val srm = rm.put(endType) { _: Any, _: Any, _: Any, _: Any -> sentinel } as ReadMap
250 | while (i.current().tokenType != endType) {
251 | val el: SyntaxObject? = this.read(srm, i)
252 | if (el!!.obj != sentinel)
253 | v = v.addLast(el)
254 | }
255 | i.next() // discard the end token
256 | return ctor(v)
257 | } catch (e: Exception) {
258 | throw ParseException(start, msg, e)
259 | }
260 | }
261 |
262 | override fun read(rm: ReadMap, i: TokenIter): SyntaxObject? {
263 | // yo dawg I heard u leik streams
264 | val current: Token<*> = i.current()
265 | return SyntaxObject(super.read(rm, i), current)
266 | }
267 | }
268 |
269 | object Readers {
270 | @JvmStatic
271 | val BASE_READ_MAP: ReadMap = (
272 | ReadMap()
273 | // Things we discard by default
274 | .put(TokenType.COMMENT, Reader::readNothing)
275 | .put(TokenType.WHITESPACE, Reader::readNothing)
276 | .put(TokenType.NEWLINE, Reader::readNothing)
277 |
278 | // Atoms
279 | .put(TokenType.NUMBER, Reader::readValue)
280 | .put(TokenType.KEYWORD, Reader::readValue)
281 | .put(TokenType.SYMBOL, Reader::readSymbol)
282 | .put(TokenType.STRING, Reader::readValue)
283 |
284 | // Lists
285 | .put(TokenType.LPAREN, Reader::readList)
286 | .put(TokenType.LBRACKET, Reader::readSqList)
287 | .put(TokenType.LBRACE, Reader::readMap)
288 | .put(TokenType.HASH_LBRACE, Reader::readSet)
289 |
290 | .put(TokenType.META, Reader::readMeta)
291 | .put(TokenType.HASH, Reader::readTag)
292 | .put(TokenType.QUOTE, Reader::readQuote)
293 |
294 | // RHS stuff that is an error by default
295 | .put(TokenType.RBRACE, Reader::readError)
296 | .put(TokenType.RBRACKET, Reader::readError)
297 | .put(TokenType.RPAREN, Reader::readError)
298 | )
299 |
300 | @JvmStatic
301 | val SYNTAX_READ_MAP: ReadMap = (
302 | BASE_READ_MAP
303 | .put(TokenType.COMMENT, Reader::readValue)
304 | .put(TokenType.WHITESPACE, Reader::readValue)
305 | .put(TokenType.NEWLINE, Reader::readValue)
306 | )
307 |
308 | fun read(rm: ReadMap, reader: java.io.Reader, streamIdentifier: Any): Any? {
309 | return Reader().read(rm, CurrentIterator(Scanners.scan(reader, streamIdentifier) as Iterator>))
310 | }
311 |
312 | fun read(rm: ReadMap, buff: String, streamIdentifier: Any): Any? {
313 | return read(rm, ByteArrayInputStream(buff.toByteArray()).reader(), streamIdentifier)
314 | }
315 |
316 | fun read(buff: String, streamIdentifier: Any): Any? {
317 | return read(BASE_READ_MAP, buff, streamIdentifier)
318 | }
319 |
320 | fun read(rdr: java.io.Reader, streamIdentifier: Any): Any? {
321 | return read(BASE_READ_MAP, rdr, streamIdentifier)
322 | }
323 | }
324 |
325 | object ReaderTest {
326 | @JvmStatic
327 | fun main(args: Array) {
328 | val iter = CurrentIterator(Scanners.scan(System.`in`.reader(), "STDIN") as Iterator>)
329 | val rdr = Reader()
330 |
331 | while (iter.hasNext()) {
332 | val obj = rdr.read(Readers.BASE_READ_MAP, iter)
333 | if (obj != null) {
334 | Printers.println(obj)
335 | }
336 | }
337 | }
338 | }
339 |
340 | object SyntaxReaderTest {
341 | @JvmStatic
342 | fun main(args: Array) {
343 | val iter = CurrentIterator(Scanners.scan(System.`in`.reader(), "STDIN") as Iterator>)
344 | val rdr = SyntaxReader()
345 |
346 | while (iter.hasNext()) {
347 | val obj = rdr.read(Readers.BASE_READ_MAP, iter)
348 | if (obj != null) {
349 | Printers.println(obj)
350 | }
351 | }
352 | }
353 | }
354 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/repl.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-09-07
3 | *
4 | * A sketch driver for a command line REPL.
5 | *
6 | * When complete, this'll be the "bare" REPL entry point.
7 | */
8 |
9 | package me.arrdem.ox
10 |
11 | import java.io.IOError
12 | import java.io.Reader
13 | import java.lang.Exception
14 |
15 | object ConsoleRepl {
16 | @JvmStatic
17 | fun main(args: Array) {
18 | val rdr = System.`in`.reader()
19 | var environment = null
20 | var input = 0
21 |
22 | while(true) {
23 | try {
24 | print("=> ")
25 | val expr = Readers.read(rdr as Reader, "STDIN: $input")
26 | val res = expr // FIXME: evaluate
27 | Printers.println(res)
28 | } catch (e: IOError) {
29 | break
30 | } catch (e: Exception) {
31 |
32 | } finally {
33 | input += 1
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/kotlin/me/arrdem/ox/scanner.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Reid 'arrdem' McKenzie 2019-5-20
3 | *
4 | * The Ox scanner.
5 | * Used to implement the reader.
6 | */
7 |
8 | package me.arrdem.ox
9 |
10 | import java.io.PushbackReader
11 | import java.io.Reader
12 | import java.io.StringReader
13 | import java.lang.IllegalStateException
14 | import java.nio.charset.Charset
15 | import java.util.Iterator
16 |
17 | /**
18 | * Tokens are read from streams, which are identified by a
19 | * StreamIdentifer of type T, defined by the user.
20 | */
21 | data class StreamLocation(
22 | val streamIdentifer: T,
23 | val offset: Long,
24 | val lineNumber: Long,
25 | val columnNumber: Long
26 | ): Tagged {
27 | override fun tag(): Symbol {
28 | return Symbols.of("ox.scanner/streamloc")
29 | }
30 |
31 | override fun value(): Any {
32 | return List.of(streamIdentifer, offset, lineNumber, columnNumber)
33 | }
34 | }
35 |
36 | // Tokens are of a type
37 | enum class TokenType {
38 | // lists are ()
39 | LPAREN,
40 | RPAREN,
41 |
42 | // lists are also []
43 | LBRACKET,
44 | RBRACKET,
45 |
46 | // mappings and sets use {}
47 | LBRACE,
48 | RBRACE,
49 |
50 | // Whitespace
51 | NEWLINE,
52 | WHITESPACE,
53 |
54 | // reader macros
55 | HASH, // Used for #foo reader macros
56 | HASH_LPAREN, // Used for #()
57 | HASH_LBRACE, // Used for #{}
58 | HASH_LBRACKET, // Used for #[]
59 |
60 | // More weirdness
61 | QUOTE,
62 | META,
63 | COMMENT,
64 |
65 | // atoms
66 | STRING,
67 | NUMBER, SYMBOL, KEYWORD
68 |
69 | // FIXME:
70 | // - NaN
71 | // - Inf
72 | // - Nil / Null
73 | // True, False
74 | }
75 |
76 | // Tokens themselves
77 |
78 | data class Token(
79 | val tokenType: TokenType,
80 | val location: StreamLocation,
81 | val value: Any
82 | ): Tagged {
83 | override fun tag(): Symbol {
84 | return Symbols.of("ox/token")
85 | }
86 |
87 | override fun value(): Any {
88 | return List.of(tokenType, location, value)
89 | }
90 | }
91 |
92 | // Before we can define the scanner, we need the exception it throws
93 |
94 | class ScannerException(
95 | val location: StreamLocation