summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/lv/enes/orang/Builtins.java95
-rw-r--r--src/main/java/lv/enes/orang/Codepoint.java24
-rw-r--r--src/main/java/lv/enes/orang/Lexer.java168
-rw-r--r--src/main/java/lv/enes/orang/Main.java77
-rw-r--r--src/main/java/lv/enes/orang/NonEmptyList.java23
-rw-r--r--src/main/java/lv/enes/orang/OrangException.java11
-rw-r--r--src/main/java/lv/enes/orang/OrangRuntimeException.java11
-rw-r--r--src/main/java/lv/enes/orang/Parser.java291
-rw-r--r--src/main/java/lv/enes/orang/ParserException.java7
-rw-r--r--src/main/java/lv/enes/orang/PeekableStream.java38
-rw-r--r--src/main/java/lv/enes/orang/Scope.java54
-rw-r--r--src/main/java/lv/enes/orang/State.java12
-rw-r--r--src/main/java/lv/enes/orang/Token.java15
-rw-r--r--src/main/java/lv/enes/orang/TokenType.java91
-rw-r--r--src/main/java/lv/enes/orang/ast/ArrayExpression.java21
-rw-r--r--src/main/java/lv/enes/orang/ast/BinaryExpression.java53
-rw-r--r--src/main/java/lv/enes/orang/ast/BooleanLiteral.java12
-rw-r--r--src/main/java/lv/enes/orang/ast/CallExpression.java12
-rw-r--r--src/main/java/lv/enes/orang/ast/DefSpec.java6
-rw-r--r--src/main/java/lv/enes/orang/ast/Definition.java15
-rw-r--r--src/main/java/lv/enes/orang/ast/DoExpression.java19
-rw-r--r--src/main/java/lv/enes/orang/ast/Expression.java14
-rw-r--r--src/main/java/lv/enes/orang/ast/FnExpression.java14
-rw-r--r--src/main/java/lv/enes/orang/ast/IfElseExpression.java22
-rw-r--r--src/main/java/lv/enes/orang/ast/IntLiteral.java12
-rw-r--r--src/main/java/lv/enes/orang/ast/LetInExpression.java27
-rw-r--r--src/main/java/lv/enes/orang/ast/Program.java17
-rw-r--r--src/main/java/lv/enes/orang/ast/Statement.java8
-rw-r--r--src/main/java/lv/enes/orang/ast/StringLiteral.java13
-rw-r--r--src/main/java/lv/enes/orang/ast/UnaryExpression.java23
-rw-r--r--src/main/java/lv/enes/orang/ast/VariableExpression.java12
-rw-r--r--src/main/java/lv/enes/orang/value/Array.java65
-rw-r--r--src/main/java/lv/enes/orang/value/BuiltinFunction.java31
-rw-r--r--src/main/java/lv/enes/orang/value/Function.java28
-rw-r--r--src/main/java/lv/enes/orang/value/OrangBoolean.java55
-rw-r--r--src/main/java/lv/enes/orang/value/OrangInteger.java70
-rw-r--r--src/main/java/lv/enes/orang/value/OrangString.java49
-rw-r--r--src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java32
-rw-r--r--src/main/java/lv/enes/orang/value/PartialFunction.java37
-rw-r--r--src/main/java/lv/enes/orang/value/Undefined.java21
-rw-r--r--src/main/java/lv/enes/orang/value/Value.java77
-rw-r--r--src/main/java/module-info.java9
-rw-r--r--src/main/resources/lv/enes/orang/prelude.orang20
43 files changed, 1711 insertions, 0 deletions
diff --git a/src/main/java/lv/enes/orang/Builtins.java b/src/main/java/lv/enes/orang/Builtins.java
new file mode 100644
index 0000000..0a76387
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Builtins.java
@@ -0,0 +1,95 @@
1package lv.enes.orang;
2
3import lombok.extern.slf4j.Slf4j;
4import lv.enes.orang.value.*;
5
6import java.io.IOException;
7import java.util.List;
8import java.util.Map;
9import java.util.Random;
10
11import static lv.enes.orang.State.STDIN;
12import static lv.enes.orang.State.STDOUT;
13
14@Slf4j
15public final class Builtins {
16 public static final Map<String, Value> BUILTINS = Map.ofEntries(
17 Map.entry("__builtin_isRepl", new BuiltinFunction(1, Builtins::isRepl)),
18 Map.entry("__builtin_len", new BuiltinFunction(1, Builtins::len)),
19 Map.entry("__builtin_parseInt", new BuiltinFunction(1, Builtins::parseInt)),
20 Map.entry("__builtin_print", new BuiltinFunction(1, Builtins::print)),
21 Map.entry("__builtin_randInt", new BuiltinFunction(2, Builtins::randInt)),
22 Map.entry("__builtin_readLn", new BuiltinFunction(1, Builtins::readLn))
23 );
24
25 private Builtins() {}
26
27 private static Value isRepl(List<Value> args) {
28 return OrangBoolean.TRUE;
29 }
30
31 private static Value len(List<Value> args) throws OrangRuntimeException {
32 assert args.size() == 1;
33 var arg = args.getFirst();
34
35 if (arg instanceof OrangString(var value)) {
36 return new OrangInteger(value.length());
37 } else if (arg instanceof Array(var items)) {
38 return new OrangInteger(items.size());
39 } else {
40 throw new OrangRuntimeException(STR."len \{arg.typeName()} is not implemented");
41 }
42 }
43
44 private static Value parseInt(List<Value> args) throws OrangRuntimeException {
45 assert args.size() == 1;
46
47 var arg = args.getFirst();
48 if (arg instanceof OrangInteger) {
49 log.warn("Attempted to parseInt an Integer!");
50 return arg;
51 } else if (arg instanceof OrangString(var value)) {
52 try {
53 return new OrangInteger(Integer.parseInt(value.trim()));
54 } catch (NumberFormatException ex) {
55 throw new OrangRuntimeException(ex);
56 }
57 } else {
58 throw new OrangRuntimeException(STR."parseInt \{arg.typeName()} is not implemented");
59 }
60 }
61
62 private static Value print(List<Value> args) {
63 assert args.size() == 1;
64 STDOUT.print(args.getFirst().display());
65 STDOUT.flush();
66 return args.getFirst();
67 }
68
69 private static Value randInt(List<Value> args) throws OrangRuntimeException {
70 assert args.size() == 2;
71
72 var minv = args.getFirst();
73 var maxv = args.get(1);
74 if (minv instanceof OrangInteger(var min) && maxv instanceof OrangInteger(var max)) {
75 return new OrangInteger(new Random().nextInt(min, max));
76 } else {
77 throw new OrangRuntimeException(STR."randInt \{minv.typeName()} \{maxv.typeName()} is not implemented");
78 }
79 }
80
81 private static Value readLn(List<Value> args) throws OrangRuntimeException {
82 assert args.size() == 1;
83
84 var arg = args.getFirst();
85 if (!(arg instanceof Array) || !((Array) arg).items().isEmpty()) {
86 log.warn("You should call readLn with an empty tuple like `readLn ()`");
87 }
88
89 try {
90 return new OrangString(STDIN.readLine());
91 } catch (IOException e) {
92 throw new OrangRuntimeException(e);
93 }
94 }
95}
diff --git a/src/main/java/lv/enes/orang/Codepoint.java b/src/main/java/lv/enes/orang/Codepoint.java
new file mode 100644
index 0000000..0dfaa2b
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Codepoint.java
@@ -0,0 +1,24 @@
1package lv.enes.orang;
2
3public record Codepoint(int cp) {
4 @Override
5 public String toString() {
6 return Character.toString(cp);
7 }
8
9 public boolean isIdentInitial() {
10 return Character.isLetter(cp) || cp == '_';
11 }
12
13 public boolean isIdentFinal() {
14 return isIdentInitial() || Character.isDigit(cp);
15 }
16
17 public boolean isNumeral() {
18 return Character.isDigit(cp);
19 }
20
21 public boolean isWhitespace() {
22 return Character.isWhitespace(cp);
23 }
24}
diff --git a/src/main/java/lv/enes/orang/Lexer.java b/src/main/java/lv/enes/orang/Lexer.java
new file mode 100644
index 0000000..0ee2503
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Lexer.java
@@ -0,0 +1,168 @@
1package lv.enes.orang;
2
3import java.io.*;
4import java.util.Iterator;
5import java.util.function.BiFunction;
6import java.util.function.Predicate;
7import java.util.stream.IntStream;
8import java.util.stream.Stream;
9
10public class Lexer implements Iterator<Token> {
11 private final PeekableStream<Codepoint> input;
12
13 public Lexer(InputStream input) {
14 this(new InputStreamReader(input));
15 }
16
17 public Lexer(Reader input) {
18 var cpStream = new BufferedReader(input)
19 .lines()
20 .flatMapToInt(str -> IntStream.concat(str.codePoints(), IntStream.of('\n')))
21 .mapToObj(Codepoint::new);
22 var theEof = Stream.of(new Codepoint(-1));
23 this.input = new PeekableStream<>(Stream.concat(cpStream, theEof).iterator());
24 }
25
26 public Lexer(String input) {
27 this(new StringReader(input));
28 }
29
30 private boolean hasNext = true;
31
32 @Override
33 public Token next() {
34 var tok = nextToken();
35 if (tok.type() == TokenType.EOF) {
36 hasNext = false;
37 }
38 return tok;
39 }
40
41 @Override
42 public boolean hasNext() {
43 return hasNext;
44 }
45
46 private Token nextToken() {
47 skipWhitespace();
48 return switch (input.peek().cp()) {
49 case -1 -> new Token(TokenType.EOF, "");
50
51 case '*' -> new Token(TokenType.ASTERISK, input.next());
52 case '!' -> new Token(TokenType.BANG, input.next());
53 case '[' -> new Token(TokenType.BRACKET_LEFT, input.next());
54 case ']' -> new Token(TokenType.BRACKET_RIGHT, input.next());
55 case ',' -> new Token(TokenType.COMMA, input.next());
56 case '=' -> new Token(TokenType.EQUAL, input.next());
57 case '>' -> {
58 var first = input.next();
59 if (input.peek().cp() == '=') {
60 yield new Token(TokenType.GREATER_EQUAL, first, input.next());
61 } else {
62 yield new Token(TokenType.GREATER, first);
63 }
64 }
65 case '<' -> {
66 var first = input.next();
67 if (input.peek().cp() == '=') {
68 yield new Token(TokenType.LESS_EQUAL, first, input.next());
69 } else {
70 yield new Token(TokenType.LESS, first);
71 }
72 }
73 case '-' -> {
74 var first = input.next();
75 if (input.peek().cp() == '>') {
76 yield new Token(TokenType.MINUS_GREATER, first, input.next());
77 } else {
78 yield new Token(TokenType.MINUS, first);
79 }
80 }
81 case '(' -> new Token(TokenType.PAREN_LEFT, input.next());
82 case ')' -> new Token(TokenType.PAREN_RIGHT, input.next());
83 case '+' -> new Token(TokenType.PLUS, input.next());
84 case '?' -> {
85 var first = input.next();
86 if (input.peek().cp() == '=') {
87 yield new Token(TokenType.QUESTION_EQUAL, first, input.next());
88 } else {
89 yield new Token(TokenType.ILLEGAL, first, input.next());
90 }
91 }
92 case ';' -> new Token(TokenType.SEMICOLON, input.next());
93 case '/' -> {
94 var first = input.next();
95 if (input.peek().cp() == '=') {
96 yield new Token(TokenType.SLASH_EQUAL, first, input.next());
97 } else {
98 yield new Token(TokenType.SLASH, first);
99 }
100 }
101
102 case '"' -> new Token(TokenType.STRING, readString());
103
104 default -> {
105 if (input.peek().isIdentInitial()) {
106 var ident = readIdentifier();
107 var type = switch (ident) {
108 case "and" -> TokenType.AND;
109 case "def" -> TokenType.DEF;
110 case "do" -> TokenType.DO;
111 case "else" -> TokenType.ELSE;
112 case "end" -> TokenType.END;
113 case "false" -> TokenType.FALSE;
114 case "fn" -> TokenType.FN;
115 case "if" -> TokenType.IF;
116 case "in" -> TokenType.IN;
117 case "let" -> TokenType.LET;
118 case "then" -> TokenType.THEN;
119 case "true" -> TokenType.TRUE;
120 default -> TokenType.IDENTIFIER;
121 };
122 yield new Token(type, ident);
123 } else if (input.peek().isNumeral()) {
124 yield new Token(TokenType.INTEGER, readInteger());
125 } else {
126 yield new Token(TokenType.ILLEGAL, input.next());
127 }
128 }
129 };
130 }
131
132 private <T> T foldWhile(Predicate<Codepoint> pred, T initial, BiFunction<T, Codepoint, T> combine) {
133 var res = initial;
134 var ch = input.peek();
135 while (pred.test(ch)) {
136 res = combine.apply(res, input.next());
137 ch = input.peek();
138 }
139 return res;
140 }
141
142 private String readWhile(Predicate<Codepoint> pred) {
143 return foldWhile(pred, new StringBuilder(), StringBuilder::append).toString();
144 }
145
146 private void skipWhile(Predicate<Codepoint> pred) {
147 foldWhile(pred, Object.class, (x, _) -> x);
148 }
149
150 private String readIdentifier() {
151 return readWhile(Codepoint::isIdentFinal);
152 }
153
154 private String readInteger() {
155 return readWhile(Codepoint::isNumeral);
156 }
157
158 private String readString() {
159 input.next();
160 var literal = readWhile(cp -> cp.cp() != '"');
161 input.next();
162 return literal;
163 }
164
165 private void skipWhitespace() {
166 skipWhile(Codepoint::isWhitespace);
167 }
168}
diff --git a/src/main/java/lv/enes/orang/Main.java b/src/main/java/lv/enes/orang/Main.java
new file mode 100644
index 0000000..9a607ed
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Main.java
@@ -0,0 +1,77 @@
1package lv.enes.orang;
2
3import java.io.FileReader;
4import java.io.IOException;
5
6import static lv.enes.orang.State.STDIN;
7import static lv.enes.orang.State.STDOUT;
8
9public class Main {
10 public static final String PROMPT = ">> ";
11
12 public static void main() throws IOException {
13 repl();
14 }
15
16 private static void repl() throws IOException {
17 var scope = new Scope();
18
19 try (var stream = Main.class.getResourceAsStream("prelude.orang")) {
20 var prog = Parser.parseProgram(stream);
21 scope = prog.runStatement(scope);
22 } catch (OrangException ex) {
23 STDOUT.println(STR."While evaluating prelude: \{ex}");
24 throw new RuntimeException(ex);
25 }
26
27 boolean running = true;
28 while (running) {
29 STDOUT.print(PROMPT);
30 STDOUT.flush();
31
32 var line = STDIN.readLine();
33 if (line == null) {
34 return;
35 }
36
37 if (line.isEmpty()) {
38 continue;
39 }
40
41 if (line.charAt(0) == ':') {
42 if (line.length() == 1) {
43 continue;
44 }
45
46 switch (line.charAt(1)) {
47 case 'l':
48 var filename = line.substring(2).trim();
49 try (var reader = new FileReader((filename))) {
50 var prog = Parser.parseProgram(reader);
51 scope = prog.runStatement(scope);
52 } catch (IOException | OrangException ex) {
53 STDOUT.println(ex);
54 }
55 break;
56 case 'q':
57 running = false;
58 break;
59 default:
60 STDOUT.println("Unrecognised REPL command");
61 }
62 continue;
63 }
64
65 try {
66 var prog = Parser.parseProgram(line);
67 scope = prog.runStatement(scope);
68 if (scope.getLastResult() != null) {
69 STDOUT.print("-> ");
70 STDOUT.println(scope.getLastResult().stringify());
71 }
72 } catch (OrangException ex) {
73 STDOUT.println(ex);
74 }
75 }
76 }
77}
diff --git a/src/main/java/lv/enes/orang/NonEmptyList.java b/src/main/java/lv/enes/orang/NonEmptyList.java
new file mode 100644
index 0000000..b532a18
--- /dev/null
+++ b/src/main/java/lv/enes/orang/NonEmptyList.java
@@ -0,0 +1,23 @@
1package lv.enes.orang;
2
3import java.util.AbstractList;
4import java.util.List;
5
6public class NonEmptyList<E> extends AbstractList<E> {
7 private final List<E> delegate;
8
9 public NonEmptyList(List<E> delegate) {
10 assert !delegate.isEmpty();
11 this.delegate = List.copyOf(delegate);
12 }
13
14 @Override
15 public E get(int index) {
16 return delegate.get(index);
17 }
18
19 @Override
20 public int size() {
21 return delegate.size();
22 }
23}
diff --git a/src/main/java/lv/enes/orang/OrangException.java b/src/main/java/lv/enes/orang/OrangException.java
new file mode 100644
index 0000000..9308643
--- /dev/null
+++ b/src/main/java/lv/enes/orang/OrangException.java
@@ -0,0 +1,11 @@
1package lv.enes.orang;
2
3public abstract class OrangException extends Exception {
4 protected OrangException(String message) {
5 super(message);
6 }
7
8 protected OrangException(Throwable cause) {
9 super(cause);
10 }
11}
diff --git a/src/main/java/lv/enes/orang/OrangRuntimeException.java b/src/main/java/lv/enes/orang/OrangRuntimeException.java
new file mode 100644
index 0000000..9648c30
--- /dev/null
+++ b/src/main/java/lv/enes/orang/OrangRuntimeException.java
@@ -0,0 +1,11 @@
1package lv.enes.orang;
2
3public class OrangRuntimeException extends OrangException {
4 public OrangRuntimeException(String message) {
5 super(message);
6 }
7
8 public OrangRuntimeException(Throwable cause) {
9 super(cause);
10 }
11}
diff --git a/src/main/java/lv/enes/orang/Parser.java b/src/main/java/lv/enes/orang/Parser.java
new file mode 100644
index 0000000..fd4b9bb
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Parser.java
@@ -0,0 +1,291 @@
1package lv.enes.orang;
2
3import lv.enes.orang.ast.*;
4import lv.enes.orang.ast.IfElseExpression;
5import lv.enes.orang.ast.Statement;
6
7import java.io.InputStream;
8import java.io.Reader;
9import java.util.ArrayList;
10import java.util.Collections;
11import java.util.Iterator;
12import java.util.List;
13import java.util.function.Predicate;
14
15public class Parser {
16 public static Program parseProgram(InputStream in) throws ParserException {
17 var parser = new Parser(in);
18 return parser.parseProgram();
19 }
20
21 public static Program parseProgram(Reader in) throws ParserException {
22 var parser = new Parser(in);
23 return parser.parseProgram();
24 }
25
26 public static Program parseProgram(String in) throws ParserException {
27 var parser = new Parser(in);
28 return parser.parseProgram();
29 }
30
31 private final PeekableStream<Token> input;
32
33 public Parser(InputStream in) {
34 this(new Lexer(in));
35 }
36
37 public Parser(Reader in) {
38 this(new Lexer(in));
39 }
40
41 public Parser(String in) {
42 this(new Lexer(in));
43 }
44
45 public Parser(Iterator<Token> input) {
46 this.input = new PeekableStream<>(input);
47 }
48
49 public Program parseProgram() throws ParserException {
50 var statements = new ArrayList<Statement>();
51 while (!maybeConsumeToken(TokenType.EOF)) {
52 statements.add(parseStatement());
53 maybeConsumeToken(TokenType.SEMICOLON);
54 }
55 return new Program(Collections.unmodifiableList(statements));
56 }
57
58 private Token consume(Predicate<Token> pred, String msg) throws ParserException {
59 var tok = input.next();
60 if (!pred.test(tok)) {
61 throw new ParserException(STR."\{msg}, got \{tok}");
62 }
63 return tok;
64 }
65
66 private Token consumeToken(TokenType type) throws ParserException {
67 return consume(tok -> tok.type() == type, STR."Expected \{type}");
68 }
69
70 private boolean maybeConsumeToken(TokenType type) {
71 if (input.peek().type() == type) {
72 input.next();
73 return true;
74 }
75 return false;
76 }
77
78 private ArrayExpression parseArray() throws ParserException {
79 consumeToken(TokenType.BRACKET_LEFT);
80 if (maybeConsumeToken(TokenType.BRACKET_RIGHT)) {
81 return new ArrayExpression(List.of());
82 }
83
84 var items = new ArrayList<Expression>();
85 do {
86 items.add(parseExpression());
87 } while (maybeConsumeToken(TokenType.COMMA));
88 consumeToken(TokenType.BRACKET_RIGHT);
89
90 return new ArrayExpression(Collections.unmodifiableList(items));
91 }
92
93 private List<String> parseArgSpecs() {
94 var argSpecs = new ArrayList<String>();
95 while (true) {
96 if (input.peek().type() == TokenType.IDENTIFIER) {
97 argSpecs.add(input.next().literal());
98 } else {
99 break;
100 }
101 }
102 return Collections.unmodifiableList(argSpecs);
103 }
104
105 private Expression parseBinaryExpression() throws ParserException {
106 var lhs = parseCallExpression();
107 if (!input.peek().type().isBinaryOp()) {
108 return lhs;
109 }
110
111 return parseBinaryExpressionRhs(lhs, input.next().type().toBinaryOp());
112 }
113
114 private Expression parseBinaryExpressionRhs(Expression lhs, BinaryExpression.Operator op) throws ParserException {
115 var rhs = parseCallExpression();
116 if (!input.peek().type().isBinaryOp()) {
117 return new BinaryExpression(op, lhs, rhs);
118 }
119
120 var op2 = input.next().type().toBinaryOp();
121 if (op2.bindsStrongerThan(op)) {
122 return new BinaryExpression(op, lhs, parseBinaryExpressionRhs(rhs, op2));
123 } else {
124 return parseBinaryExpressionRhs(new BinaryExpression(op, lhs, rhs), op2);
125 }
126 }
127
128 private BooleanLiteral parseBoolean() throws ParserException {
129 var t = consume(tok -> tok.type() == TokenType.FALSE || tok.type() == TokenType.TRUE, "Expected TRUE or FALSE");
130 return new BooleanLiteral(t.type() == TokenType.TRUE);
131 }
132
133 private Expression parseCallExpression() throws ParserException {
134 var callee = parseSimpleExpression();
135 while (couldStartSimpleExpression(input.peek().type())) {
136 var arg = parseSimpleExpression();
137 callee = new CallExpression(callee, arg);
138 }
139 return callee;
140 }
141
142 private Definition parseDefinition() throws ParserException {
143 consumeToken(TokenType.DEF);
144 var defSpec = parseDefSpec();
145 consumeToken(TokenType.EQUAL);
146 var value = parseExpression();
147 if (defSpec.args().isEmpty()) {
148 return new Definition(defSpec.name(), value);
149 } else {
150 return new Definition(defSpec.name(), new FnExpression(new NonEmptyList<>(defSpec.args()), value));
151 }
152 }
153
154 private DefSpec parseDefSpec() throws ParserException {
155 var name = consumeToken(TokenType.IDENTIFIER).literal();
156 var argSpecs = parseArgSpecs();
157 return new DefSpec(name, argSpecs);
158 }
159
160 private DoExpression parseDoExpression() throws ParserException {
161 consumeToken(TokenType.DO);
162 var exprs = new ArrayList<Expression>();
163 do {
164 exprs.add(parseExpression());
165 } while (maybeConsumeToken(TokenType.SEMICOLON));
166 consumeToken(TokenType.END);
167 return new DoExpression(Collections.unmodifiableList(exprs));
168 }
169
170 private Expression parseExpression() throws ParserException {
171 if (input.peek().type().isUnaryOp()) {
172 return parseUnaryExpression();
173 }
174 return parseBinaryExpression();
175 }
176
177 private FnExpression parseFnExpression() throws ParserException {
178 consumeToken(TokenType.FN);
179 var argSpecs = parseArgSpecs();
180 if (argSpecs.isEmpty()) {
181 throw new ParserException("Function definition with no arguments");
182 }
183 var body = maybeConsumeToken(TokenType.MINUS_GREATER) ? parseExpression() : parseDoExpression();
184 return new FnExpression(new NonEmptyList<>(argSpecs), body);
185 }
186
187 private IfElseExpression parseIfElseExpression() throws ParserException {
188 consumeToken(TokenType.IF);
189 var cond = parseExpression();
190 consumeToken(TokenType.THEN);
191 var trueBranch = parseExpression();
192 consumeToken(TokenType.ELSE);
193 var falseBranch = parseExpression();
194 return new IfElseExpression(cond, trueBranch, falseBranch);
195 }
196
197 private IntLiteral parseInteger() throws ParserException {
198 var tok = consumeToken(TokenType.INTEGER);
199 return new IntLiteral(Integer.parseInt(tok.literal()));
200 }
201
202 private LetInExpression parseLetInExpression() throws ParserException {
203 consumeToken(TokenType.LET);
204 var bindings = new ArrayList<LetInExpression.Binding>();
205 do {
206 var defSpec = parseDefSpec();
207 consumeToken(TokenType.EQUAL);
208 var value = parseExpression();
209 if (defSpec.args().isEmpty()) {
210 bindings.add(new LetInExpression.Binding(defSpec.name(), value));
211 } else {
212 var fn = new FnExpression(new NonEmptyList<>(defSpec.args()), value);
213 bindings.add(new LetInExpression.Binding(defSpec.name(), fn));
214 }
215 } while (maybeConsumeToken(TokenType.AND));
216 consumeToken(TokenType.IN);
217 var body = parseExpression();
218 return new LetInExpression(Collections.unmodifiableList(bindings), body);
219 }
220
221 private Expression parseSimpleExpression() throws ParserException {
222 return switch (input.peek().type()) {
223 case PAREN_LEFT -> {
224 consumeToken(TokenType.PAREN_LEFT);
225 if (maybeConsumeToken(TokenType.PAREN_RIGHT)) {
226 yield new ArrayExpression(List.of());
227 }
228 var expr = parseExpression();
229 consumeToken(TokenType.PAREN_RIGHT);
230 yield expr;
231 }
232 case TRUE, FALSE -> parseBoolean();
233 case INTEGER -> parseInteger();
234 case IDENTIFIER -> new VariableExpression(input.next().literal());
235 case STRING -> parseString();
236 case BRACKET_LEFT -> parseArray();
237 case IF -> parseIfElseExpression();
238 case LET -> parseLetInExpression();
239 case FN -> parseFnExpression();
240 case DO -> parseDoExpression();
241 default -> throw new ParserException(STR."Unexpected token \{input.peek()}");
242 };
243 }
244
245 private boolean couldStartSimpleExpression(TokenType type) {
246 return switch (type) {
247 case PAREN_LEFT, TRUE, FALSE, INTEGER, IDENTIFIER, STRING, BRACKET_LEFT, IF, LET, FN, DO -> true;
248 default -> false;
249 };
250 }
251
252 private Statement parseStatement() throws ParserException {
253 if (input.peek().type() == TokenType.DEF) {
254 return parseDefinition();
255 } else {
256 return parseExpression();
257 }
258 }
259
260 private Expression parseString() throws ParserException {
261 var sb = new StringBuilder();
262 var cps = input.next().literal().codePoints().iterator();
263 while (cps.hasNext()) {
264 var cp = cps.next();
265 if (cp == '\\') {
266 var escapeChar = cps.next();
267 //noinspection UnnecessaryUnboxing
268 sb.append(switch (escapeChar.intValue()) {
269 case '\'' -> '\'';
270 case '"' -> '"';
271 case 'r' -> '\r';
272 case 'n' -> '\n';
273 case 't' -> '\t';
274 default -> throw new ParserException(STR."Unknown string escape '\\\{escapeChar}'");
275 });
276 } else {
277 sb.appendCodePoint(cp);
278 }
279 }
280 return new StringLiteral(sb.toString());
281 }
282
283 private Expression parseUnaryExpression() throws ParserException {
284 if (input.peek().type().isUnaryOp()) {
285 var op = input.next().type().toUnaryOp();
286 return new UnaryExpression(op, parseUnaryExpression());
287 } else {
288 return parseSimpleExpression();
289 }
290 }
291}
diff --git a/src/main/java/lv/enes/orang/ParserException.java b/src/main/java/lv/enes/orang/ParserException.java
new file mode 100644
index 0000000..9000a93
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ParserException.java
@@ -0,0 +1,7 @@
1package lv.enes.orang;
2
3public class ParserException extends OrangException {
4 public ParserException(String message) {
5 super(message);
6 }
7}
diff --git a/src/main/java/lv/enes/orang/PeekableStream.java b/src/main/java/lv/enes/orang/PeekableStream.java
new file mode 100644
index 0000000..b77bab1
--- /dev/null
+++ b/src/main/java/lv/enes/orang/PeekableStream.java
@@ -0,0 +1,38 @@
1package lv.enes.orang;
2
3import java.util.ArrayDeque;
4import java.util.Deque;
5import java.util.Iterator;
6
7public class PeekableStream<T> implements Iterator<T> {
8 private final Iterator<T> input;
9 private final Deque<T> buffer = new ArrayDeque<>();
10
11 public PeekableStream(Iterator<T> input) {
12 this.input = input;
13 }
14
15 @Override
16 public boolean hasNext() {
17 return !buffer.isEmpty() || input.hasNext();
18 }
19
20 @Override
21 public T next() {
22 if (!buffer.isEmpty()) {
23 return buffer.pop();
24 } else {
25 return input.next();
26 }
27 }
28
29 public T peek() {
30 var value = next();
31 putBack(value);
32 return value;
33 }
34
35 public void putBack(T value) {
36 buffer.push(value);
37 }
38}
diff --git a/src/main/java/lv/enes/orang/Scope.java b/src/main/java/lv/enes/orang/Scope.java
new file mode 100644
index 0000000..3625c69
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Scope.java
@@ -0,0 +1,54 @@
1package lv.enes.orang;
2
3import jakarta.annotation.Nullable;
4import lv.enes.orang.value.Value;
5
6import java.util.HashMap;
7import java.util.Map;
8
9public class Scope {
10 private final Map<String, Value> definitions;
11
12 private final Value lastResult;
13
14 public Scope() {
15 this(new HashMap<>(Builtins.BUILTINS), null);
16 }
17
18 private Scope(Map<String, Value> definitions, Value lastResult) {
19 this.definitions = definitions;
20 this.lastResult = lastResult;
21 }
22
23 public Value getDefinition(String name) throws OrangRuntimeException {
24 if (definitions.containsKey(name)) {
25 return definitions.get(name);
26 }
27 throw new OrangRuntimeException(STR."Value named \{name} is not defined!");
28 }
29
30 @Nullable
31 public Value getLastResult() {
32 return lastResult;
33 }
34
35 public Scope withDefinition(String key, Value value) {
36 var newDefs = new HashMap<>(definitions);
37 newDefs.put(key, value);
38 return new Scope(newDefs, null);
39 }
40
41 public Scope withDefinitions(Map<String, Value> definitions) {
42 var newDefs = new HashMap<>(this.definitions);
43 newDefs.putAll(definitions);
44 return new Scope(newDefs, null);
45 }
46
47 public void setDefinition(String key, Value value) {
48 definitions.put(key, value);
49 }
50
51 public Scope withLastResult(Value value) {
52 return new Scope(definitions, value);
53 }
54}
diff --git a/src/main/java/lv/enes/orang/State.java b/src/main/java/lv/enes/orang/State.java
new file mode 100644
index 0000000..46b115b
--- /dev/null
+++ b/src/main/java/lv/enes/orang/State.java
@@ -0,0 +1,12 @@
1package lv.enes.orang;
2
3import java.io.BufferedReader;
4import java.io.InputStreamReader;
5import java.io.PrintWriter;
6
7public final class State {
8 public static final BufferedReader STDIN = new BufferedReader(new InputStreamReader(System.in));
9 public static final PrintWriter STDOUT = new PrintWriter(System.out);
10
11 private State() {}
12}
diff --git a/src/main/java/lv/enes/orang/Token.java b/src/main/java/lv/enes/orang/Token.java
new file mode 100644
index 0000000..4456b8f
--- /dev/null
+++ b/src/main/java/lv/enes/orang/Token.java
@@ -0,0 +1,15 @@
1package lv.enes.orang;
2
3public record Token(TokenType type, String literal) {
4 public Token(TokenType type, Codepoint... cps) {
5 this(type, codepointsToString(cps));
6 }
7
8 private static String codepointsToString(Codepoint... cps) {
9 var sb = new StringBuilder(cps.length);
10 for (var cp : cps) {
11 sb.append(cp);
12 }
13 return sb.toString();
14 }
15}
diff --git a/src/main/java/lv/enes/orang/TokenType.java b/src/main/java/lv/enes/orang/TokenType.java
new file mode 100644
index 0000000..960435e
--- /dev/null
+++ b/src/main/java/lv/enes/orang/TokenType.java
@@ -0,0 +1,91 @@
1package lv.enes.orang;
2
3import lv.enes.orang.ast.BinaryExpression;
4import lv.enes.orang.ast.UnaryExpression;
5
6public enum TokenType {
7 ILLEGAL,
8 EOF,
9
10 // Literals
11 IDENTIFIER,
12 INTEGER,
13 STRING,
14
15 // Keywords
16 AND,
17 DEF,
18 DO,
19 ELSE,
20 END,
21 FALSE,
22 FN,
23 IF,
24 IN,
25 LET,
26 THEN,
27 TRUE,
28
29 // Special chars
30 ASTERISK,
31 BANG,
32 BRACKET_LEFT,
33 BRACKET_RIGHT,
34 COMMA,
35 EQUAL,
36 GREATER,
37 GREATER_EQUAL,
38 LESS,
39 LESS_EQUAL,
40 MINUS,
41 MINUS_GREATER,
42 PAREN_LEFT,
43 PAREN_RIGHT,
44 PLUS,
45 QUESTION_EQUAL,
46 SEMICOLON,
47 SLASH,
48 SLASH_EQUAL,
49
50 ;
51
52 public boolean isBinaryOp() {
53 return switch (this) {
54 case ASTERISK, SLASH, PLUS, MINUS, QUESTION_EQUAL, SLASH_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL
55 -> true;
56 default -> false;
57 };
58 }
59
60 public BinaryExpression.Operator toBinaryOp() {
61 return switch (this) {
62 case ASTERISK -> BinaryExpression.Operator.MULTIPLY;
63 case SLASH -> BinaryExpression.Operator.DIVIDE;
64 case PLUS -> BinaryExpression.Operator.ADD;
65 case MINUS -> BinaryExpression.Operator.SUBTRACT;
66 case QUESTION_EQUAL -> BinaryExpression.Operator.EQUALS;
67 case SLASH_EQUAL -> BinaryExpression.Operator.NOT_EQUALS;
68 case GREATER -> BinaryExpression.Operator.GT;
69 case GREATER_EQUAL -> BinaryExpression.Operator.GTE;
70 case LESS -> BinaryExpression.Operator.LT;
71 case LESS_EQUAL -> BinaryExpression.Operator.LTE;
72 default -> throw new IllegalStateException("Token " + this + " is not a binary operator");
73 };
74 }
75
76 public boolean isUnaryOp() {
77 return switch (this) {
78 case PLUS, MINUS, BANG -> true;
79 default -> false;
80 };
81 }
82
83 public UnaryExpression.Operator toUnaryOp() {
84 return switch (this) {
85 case PLUS -> UnaryExpression.Operator.PLUS;
86 case MINUS -> UnaryExpression.Operator.NEGATE;
87 case BANG -> UnaryExpression.Operator.NOT;
88 default -> throw new IllegalStateException("Token " + this + " is not a unary operator");
89 };
90 }
91}
diff --git a/src/main/java/lv/enes/orang/ast/ArrayExpression.java b/src/main/java/lv/enes/orang/ast/ArrayExpression.java
new file mode 100644
index 0000000..d2437cf
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/ArrayExpression.java
@@ -0,0 +1,21 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Array;
6import lv.enes.orang.value.Value;
7
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.List;
11
12public record ArrayExpression(List<Expression> items) implements Expression {
13 @Override
14 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
15 var values = new ArrayList<Value>();
16 for (var item : items) {
17 values.add(item.evaluateExpression(scope));
18 }
19 return new Array(Collections.unmodifiableList(values));
20 }
21}
diff --git a/src/main/java/lv/enes/orang/ast/BinaryExpression.java b/src/main/java/lv/enes/orang/ast/BinaryExpression.java
new file mode 100644
index 0000000..c0b988c
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/BinaryExpression.java
@@ -0,0 +1,53 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7public record BinaryExpression(Operator operator, Expression lhs, Expression rhs) implements Expression {
8 public enum Operator {
9 EQUALS,
10 NOT_EQUALS,
11 GT,
12 GTE,
13 LT,
14 LTE,
15 ADD,
16 SUBTRACT,
17 MULTIPLY,
18 DIVIDE,
19 ;
20
21 public boolean bindsStrongerThan(Operator other) {
22 return switch (this) {
23 case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE -> false;
24 case ADD, SUBTRACT -> switch (other) {
25 case EQUALS, NOT_EQUALS, GT, GTE, LTE -> true;
26 default -> false;
27 };
28 case MULTIPLY, DIVIDE -> switch (other) {
29 case EQUALS, NOT_EQUALS, GT, GTE, LT, LTE, ADD, SUBTRACT -> true;
30 default -> false;
31 };
32 };
33 }
34 }
35
36 @Override
37 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
38 var lhs = lhs().evaluateExpression(scope);
39 var rhs = rhs().evaluateExpression(scope);
40 return switch (operator) {
41 case EQUALS -> lhs.orangEquals(rhs);
42 case NOT_EQUALS -> lhs.notEquals(rhs);
43 case GT -> lhs.greaterThan(rhs);
44 case GTE -> lhs.greaterThanOrEqual(rhs);
45 case LT -> lhs.lessThan(rhs);
46 case LTE -> lhs.lessThanOrEqual(rhs);
47 case ADD -> lhs.add(rhs);
48 case SUBTRACT -> lhs.subtract(rhs);
49 case MULTIPLY -> lhs.multiply(rhs);
50 case DIVIDE -> lhs.divide(rhs);
51 };
52 }
53}
diff --git a/src/main/java/lv/enes/orang/ast/BooleanLiteral.java b/src/main/java/lv/enes/orang/ast/BooleanLiteral.java
new file mode 100644
index 0000000..9110f67
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/BooleanLiteral.java
@@ -0,0 +1,12 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.Scope;
4import lv.enes.orang.value.OrangBoolean;
5import lv.enes.orang.value.Value;
6
7public record BooleanLiteral(boolean value) implements Expression {
8 @Override
9 public Value evaluateExpression(Scope scope) {
10 return OrangBoolean.of(value);
11 }
12}
diff --git a/src/main/java/lv/enes/orang/ast/CallExpression.java b/src/main/java/lv/enes/orang/ast/CallExpression.java
new file mode 100644
index 0000000..8f05496
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/CallExpression.java
@@ -0,0 +1,12 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7public record CallExpression(Expression callee, Expression arg) implements Expression {
8 @Override
9 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
10 return callee.evaluateExpression(scope).call(arg.evaluateExpression(scope));
11 }
12}
diff --git a/src/main/java/lv/enes/orang/ast/DefSpec.java b/src/main/java/lv/enes/orang/ast/DefSpec.java
new file mode 100644
index 0000000..2233d3f
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/DefSpec.java
@@ -0,0 +1,6 @@
1package lv.enes.orang.ast;
2
3import java.util.List;
4
5public record DefSpec(String name, List<String> args) {
6}
diff --git a/src/main/java/lv/enes/orang/ast/Definition.java b/src/main/java/lv/enes/orang/ast/Definition.java
new file mode 100644
index 0000000..715daae
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/Definition.java
@@ -0,0 +1,15 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Undefined;
6
7public record Definition(String name, Expression body) implements Statement {
8 @Override
9 public Scope runStatement(Scope scope) throws OrangRuntimeException {
10 scope.setDefinition(name, Undefined.INSTANCE);
11 var value = body.evaluateExpression(scope);
12 scope.setDefinition(name, value);
13 return scope;
14 }
15}
diff --git a/src/main/java/lv/enes/orang/ast/DoExpression.java b/src/main/java/lv/enes/orang/ast/DoExpression.java
new file mode 100644
index 0000000..9d69a3e
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/DoExpression.java
@@ -0,0 +1,19 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7import java.util.List;
8
9public record DoExpression(List<Expression> body) implements Expression {
10 // assert body.!isEmpty()
11
12 @Override
13 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
14 for (var i = 0; i < body.size() - 1; i++) {
15 body.get(i).evaluateExpression(scope);
16 }
17 return body.getLast().evaluateExpression(scope);
18 }
19}
diff --git a/src/main/java/lv/enes/orang/ast/Expression.java b/src/main/java/lv/enes/orang/ast/Expression.java
new file mode 100644
index 0000000..a0fc3af
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/Expression.java
@@ -0,0 +1,14 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7public interface Expression extends Statement {
8 Value evaluateExpression(Scope scope) throws OrangRuntimeException;
9
10 @Override
11 default Scope runStatement(Scope scope) throws OrangRuntimeException {
12 return scope.withLastResult(evaluateExpression(scope));
13 }
14}
diff --git a/src/main/java/lv/enes/orang/ast/FnExpression.java b/src/main/java/lv/enes/orang/ast/FnExpression.java
new file mode 100644
index 0000000..3c3522f
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/FnExpression.java
@@ -0,0 +1,14 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.NonEmptyList;
4import lv.enes.orang.OrangRuntimeException;
5import lv.enes.orang.Scope;
6import lv.enes.orang.value.Function;
7import lv.enes.orang.value.Value;
8
9public record FnExpression(NonEmptyList<String> args, Expression body) implements Expression {
10 @Override
11 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
12 return new Function(scope, args, body);
13 }
14}
diff --git a/src/main/java/lv/enes/orang/ast/IfElseExpression.java b/src/main/java/lv/enes/orang/ast/IfElseExpression.java
new file mode 100644
index 0000000..9b52cc5
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/IfElseExpression.java
@@ -0,0 +1,22 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.OrangBoolean;
6import lv.enes.orang.value.Value;
7
8public record IfElseExpression(Expression condition, Expression trueBranch, Expression falseBranch) implements Expression {
9 @Override
10 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
11 var condValue = condition.evaluateExpression(scope);
12 if (condValue instanceof OrangBoolean value) {
13 if (value.value()) {
14 return trueBranch.evaluateExpression(scope);
15 } else {
16 return falseBranch.evaluateExpression(scope);
17 }
18 } else {
19 throw new OrangRuntimeException(STR."Condition in an if should be a Boolean not a \{condValue.typeName()}");
20 }
21 }
22}
diff --git a/src/main/java/lv/enes/orang/ast/IntLiteral.java b/src/main/java/lv/enes/orang/ast/IntLiteral.java
new file mode 100644
index 0000000..339ba3b
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/IntLiteral.java
@@ -0,0 +1,12 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.Scope;
4import lv.enes.orang.value.OrangInteger;
5import lv.enes.orang.value.Value;
6
7public record IntLiteral(int value) implements Expression {
8 @Override
9 public Value evaluateExpression(Scope scope) {
10 return new OrangInteger(value);
11 }
12}
diff --git a/src/main/java/lv/enes/orang/ast/LetInExpression.java b/src/main/java/lv/enes/orang/ast/LetInExpression.java
new file mode 100644
index 0000000..2379902
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/LetInExpression.java
@@ -0,0 +1,27 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Undefined;
6import lv.enes.orang.value.Value;
7
8import java.util.HashMap;
9import java.util.List;
10
11public record LetInExpression(List<Binding> bindings, Expression body) implements Expression {
12 @Override
13 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
14 var newDefs = new HashMap<String, Value>();
15 for (var binding : bindings) {
16 newDefs.put(binding.name, Undefined.INSTANCE);
17 }
18 var newState = scope.withDefinitions(newDefs);
19 for (var binding : bindings) {
20 var value = binding.value.evaluateExpression(newState);
21 newState.setDefinition(binding.name, value);
22 }
23 return body.evaluateExpression(newState);
24 }
25
26 public record Binding(String name, Expression value) {}
27}
diff --git a/src/main/java/lv/enes/orang/ast/Program.java b/src/main/java/lv/enes/orang/ast/Program.java
new file mode 100644
index 0000000..ae5d31e
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/Program.java
@@ -0,0 +1,17 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5
6import java.util.List;
7
8public record Program(List<Statement> statements) implements Statement {
9 @Override
10 public Scope runStatement(Scope inScope) throws OrangRuntimeException {
11 var state = inScope;
12 for (var statement : statements) {
13 state = statement.runStatement(state);
14 }
15 return state;
16 }
17}
diff --git a/src/main/java/lv/enes/orang/ast/Statement.java b/src/main/java/lv/enes/orang/ast/Statement.java
new file mode 100644
index 0000000..62b76bd
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/Statement.java
@@ -0,0 +1,8 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5
6public interface Statement {
7 Scope runStatement(Scope scope) throws OrangRuntimeException, OrangRuntimeException;
8}
diff --git a/src/main/java/lv/enes/orang/ast/StringLiteral.java b/src/main/java/lv/enes/orang/ast/StringLiteral.java
new file mode 100644
index 0000000..367dff5
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/StringLiteral.java
@@ -0,0 +1,13 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.OrangString;
6import lv.enes.orang.value.Value;
7
8public record StringLiteral(String value) implements Expression {
9 @Override
10 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
11 return new OrangString(value);
12 }
13}
diff --git a/src/main/java/lv/enes/orang/ast/UnaryExpression.java b/src/main/java/lv/enes/orang/ast/UnaryExpression.java
new file mode 100644
index 0000000..a0f565b
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/UnaryExpression.java
@@ -0,0 +1,23 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7public record UnaryExpression(Operator operator, Expression child) implements Expression {
8 public enum Operator {
9 PLUS,
10 NEGATE,
11 NOT,
12 }
13
14 @Override
15 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
16 var child = child().evaluateExpression(scope);
17 return switch (operator) {
18 case PLUS -> child.plus();
19 case NEGATE -> child.negate();
20 case NOT -> child.not();
21 };
22 }
23}
diff --git a/src/main/java/lv/enes/orang/ast/VariableExpression.java b/src/main/java/lv/enes/orang/ast/VariableExpression.java
new file mode 100644
index 0000000..1f5334d
--- /dev/null
+++ b/src/main/java/lv/enes/orang/ast/VariableExpression.java
@@ -0,0 +1,12 @@
1package lv.enes.orang.ast;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.value.Value;
6
7public record VariableExpression(String name) implements Expression {
8 @Override
9 public Value evaluateExpression(Scope scope) throws OrangRuntimeException {
10 return scope.getDefinition(name);
11 }
12}
diff --git a/src/main/java/lv/enes/orang/value/Array.java b/src/main/java/lv/enes/orang/value/Array.java
new file mode 100644
index 0000000..c3b645d
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/Array.java
@@ -0,0 +1,65 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9public record Array(List<Value> items) implements Value {
10 @Override
11 public String typeName() {
12 return "Array";
13 }
14
15 @Override
16 public String stringify() {
17 if (items.isEmpty()) {
18 return "[]";
19 }
20 var sb = new StringBuilder("[");
21 sb.append(items.getFirst().stringify());
22 for (int i = 1; i < items.size(); i++) {
23 sb.append(", ").append(items.get(i).stringify());
24 }
25 sb.append("]");
26 return sb.toString();
27 }
28
29 @Override
30 public String display() {
31 if (items.isEmpty()) {
32 return "";
33 }
34 var sb = new StringBuilder();
35 sb.append(items.getFirst().display());
36 for (var i = 1; i < items.size(); i++) {
37 sb.append(" ").append(items.get(i).display());
38 }
39 return sb.toString();
40 }
41
42 @Override
43 public Value add(Value rhs) throws OrangRuntimeException {
44 if (rhs instanceof Array(var rhsi)) {
45 var newItems = new ArrayList<>(this.items);
46 newItems.addAll(rhsi);
47 return new Array(Collections.unmodifiableList(newItems));
48 } else {
49 throw new OrangRuntimeException(STR."add not implemented for Array and \{rhs.typeName()}");
50 }
51 }
52
53 @Override
54 public Value multiply(Value rhs) throws OrangRuntimeException {
55 if (rhs instanceof OrangInteger(var repeat)) {
56 var newItems = new ArrayList<Value>(items.size() * repeat);
57 for (var i = 0; i < repeat; i++) {
58 newItems.addAll(items);
59 }
60 return new Array(Collections.unmodifiableList(newItems));
61 } else {
62 throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}");
63 }
64 }
65}
diff --git a/src/main/java/lv/enes/orang/value/BuiltinFunction.java b/src/main/java/lv/enes/orang/value/BuiltinFunction.java
new file mode 100644
index 0000000..92ccc2e
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/BuiltinFunction.java
@@ -0,0 +1,31 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5import java.util.List;
6
7public record BuiltinFunction(int argCount, Impl impl) implements Value {
8 @FunctionalInterface
9 public interface Impl {
10 Value apply(List<Value> args) throws OrangRuntimeException;
11 }
12
13 @Override
14 public String typeName() {
15 return "BuiltinFunction";
16 }
17
18 @Override
19 public String stringify() {
20 return "#builtinFunction";
21 }
22
23 @Override
24 public Value call(Value param) throws OrangRuntimeException {
25 if (argCount == 1) {
26 return impl.apply(List.of(param));
27 } else {
28 return new PartialBuiltinFunction(argCount, List.of(param), impl);
29 }
30 }
31}
diff --git a/src/main/java/lv/enes/orang/value/Function.java b/src/main/java/lv/enes/orang/value/Function.java
new file mode 100644
index 0000000..1f288c6
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/Function.java
@@ -0,0 +1,28 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.ast.Expression;
6
7import java.util.List;
8
9public record Function(Scope scope, List<String> args, Expression body) implements Value {
10 @Override
11 public String typeName() {
12 return "Function";
13 }
14
15 @Override
16 public String stringify() {
17 return "#function";
18 }
19
20 @Override
21 public Value call(Value param) throws OrangRuntimeException {
22 if (args.size() == 1) {
23 return body.evaluateExpression(scope.withDefinition(args.getFirst(), param));
24 } else {
25 return new PartialFunction(scope, args, List.of(param), body);
26 }
27 }
28}
diff --git a/src/main/java/lv/enes/orang/value/OrangBoolean.java b/src/main/java/lv/enes/orang/value/OrangBoolean.java
new file mode 100644
index 0000000..8a4776c
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/OrangBoolean.java
@@ -0,0 +1,55 @@
1package lv.enes.orang.value;
2
3import lombok.EqualsAndHashCode;
4import lv.enes.orang.OrangRuntimeException;
5
6@EqualsAndHashCode
7public final class OrangBoolean implements Value {
8 public final static OrangBoolean TRUE = new OrangBoolean(true);
9 public final static OrangBoolean FALSE = new OrangBoolean(false);
10
11 private final boolean value;
12
13 private OrangBoolean(boolean value) {
14 this.value = value;
15 }
16
17 public static OrangBoolean of(boolean value) {
18 if (value) {
19 return TRUE;
20 }
21 return FALSE;
22 }
23
24 public boolean value() {
25 return value;
26 }
27
28 @Override
29 public String typeName() {
30 return "Boolean";
31 }
32
33 @Override
34 public String stringify() {
35 if (value) {
36 return "true";
37 } else {
38 return "false";
39 }
40 }
41
42 @Override
43 public OrangBoolean not() {
44 return new OrangBoolean(!value);
45 }
46
47 @Override
48 public OrangBoolean or(Value rhs) throws OrangRuntimeException {
49 if (rhs instanceof OrangBoolean rhsb) {
50 return new OrangBoolean(value || rhsb.value);
51 } else {
52 throw new OrangRuntimeException(STR."or is not implemented for Boolean and \{rhs.typeName()}");
53 }
54 }
55}
diff --git a/src/main/java/lv/enes/orang/value/OrangInteger.java b/src/main/java/lv/enes/orang/value/OrangInteger.java
new file mode 100644
index 0000000..9b8d505
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/OrangInteger.java
@@ -0,0 +1,70 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5public record OrangInteger(int value) implements Value {
6 @Override
7 public String typeName() {
8 return "Integer";
9 }
10
11 @Override
12 public String stringify() {
13 return Integer.toString(value);
14 }
15
16 @Override
17 public OrangInteger negate() {
18 return new OrangInteger(-value);
19 }
20
21 @Override
22 public OrangInteger plus() {
23 return this;
24 }
25
26 @Override
27 public OrangInteger add(Value rhs) throws OrangRuntimeException {
28 if (rhs instanceof OrangInteger(int rhsi)) {
29 return new OrangInteger(value + rhsi);
30 } else {
31 throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}");
32 }
33 }
34
35 @Override
36 public OrangInteger divide(Value rhs) throws OrangRuntimeException {
37 if (rhs instanceof OrangInteger(int rhsi)) {
38 return new OrangInteger(value / rhsi);
39 } else {
40 throw new OrangRuntimeException(STR."divide is not implemented for Integer and \{rhs.typeName()}");
41 }
42 }
43
44 @Override
45 public OrangInteger multiply(Value rhs) throws OrangRuntimeException {
46 if (rhs instanceof OrangInteger(int rhsi)) {
47 return new OrangInteger(value * rhsi);
48 } else {
49 throw new OrangRuntimeException(STR."multiply is not implemented for Integer and \{rhs.typeName()}");
50 }
51 }
52
53 @Override
54 public OrangInteger subtract(Value rhs) throws OrangRuntimeException {
55 if (rhs instanceof OrangInteger(int rhsi)) {
56 return new OrangInteger(value - rhsi);
57 } else {
58 throw new OrangRuntimeException(STR."subtract is not implemented for Integer and \{rhs.typeName()}");
59 }
60 }
61
62 @Override
63 public OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException {
64 if (rhs instanceof OrangInteger(int rhsi)) {
65 return OrangBoolean.of(value > rhsi);
66 } else {
67 throw new OrangRuntimeException(STR."greaterThan is not implemented for Integer and \{rhs.typeName()}");
68 }
69 }
70}
diff --git a/src/main/java/lv/enes/orang/value/OrangString.java b/src/main/java/lv/enes/orang/value/OrangString.java
new file mode 100644
index 0000000..c03f7ac
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/OrangString.java
@@ -0,0 +1,49 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5public record OrangString(String value) implements Value {
6 @Override
7 public String typeName() {
8 return "String";
9 }
10
11 @Override
12 public String stringify() {
13 var sb = new StringBuilder("\"");
14 var cps = value.codePoints().iterator();
15 while (cps.hasNext()) {
16 var cp = cps.next();
17 if (cp == '"') {
18 sb.append("\\\"");
19 } else {
20 sb.appendCodePoint(cp);
21 }
22 }
23 sb.append('"');
24 return sb.toString();
25 }
26
27 @Override
28 public String display() {
29 return value;
30 }
31
32 @Override
33 public OrangString add(Value rhs) throws OrangRuntimeException {
34 if (rhs instanceof OrangString(String rhss)) {
35 return new OrangString(value + rhss);
36 } else {
37 throw new OrangRuntimeException(STR."add is not implemented for Integer and \{rhs.typeName()}");
38 }
39 }
40
41 @Override
42 public Value multiply(Value rhs) throws OrangRuntimeException {
43 if (rhs instanceof OrangInteger(var repeat)) {
44 return new OrangString(value.repeat(Math.max(0, repeat)));
45 } else {
46 throw new OrangRuntimeException(STR."multiply not implemented for Array and \{rhs.typeName()}");
47 }
48 }
49}
diff --git a/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java b/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java
new file mode 100644
index 0000000..37278a4
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/PartialBuiltinFunction.java
@@ -0,0 +1,32 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9public record PartialBuiltinFunction(int argCount, List<Value> params, BuiltinFunction.Impl impl) implements Value {
10 @Override
11 public String typeName() {
12 return "BuiltinFunction";
13 }
14
15 @Override
16 public String stringify() {
17 return "#builtinFunction";
18 }
19
20 @Override
21 public Value call(Value param) throws OrangRuntimeException {
22 List<Value> newParams = new ArrayList<>(params);
23 newParams.add(param);
24 newParams = Collections.unmodifiableList(newParams);
25
26 if (newParams.size() == argCount) {
27 return impl.apply(newParams);
28 } else {
29 return new PartialBuiltinFunction(argCount, newParams, impl);
30 }
31 }
32}
diff --git a/src/main/java/lv/enes/orang/value/PartialFunction.java b/src/main/java/lv/enes/orang/value/PartialFunction.java
new file mode 100644
index 0000000..59e1466
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/PartialFunction.java
@@ -0,0 +1,37 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4import lv.enes.orang.Scope;
5import lv.enes.orang.ast.Expression;
6
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.HashMap;
10import java.util.List;
11
12public record PartialFunction(Scope scope, List<String> args, List<Value> params, Expression body) implements Value {
13 @Override
14 public String typeName() {
15 return "Function";
16 }
17
18 @Override
19 public String stringify() {
20 return "#function";
21 }
22
23 @Override
24 public Value call(Value param) throws OrangRuntimeException {
25 var newParams = new ArrayList<>(params);
26 newParams.add(param);
27 if (newParams.size() == args.size()) {
28 var newDefs = new HashMap<String, Value>();
29 for (var i = 0; i < args.size(); i++) {
30 newDefs.put(args.get(i), newParams.get(i));
31 }
32 return body.evaluateExpression(scope.withDefinitions(newDefs));
33 }
34
35 return new PartialFunction(scope, args, Collections.unmodifiableList(newParams), body);
36 }
37}
diff --git a/src/main/java/lv/enes/orang/value/Undefined.java b/src/main/java/lv/enes/orang/value/Undefined.java
new file mode 100644
index 0000000..a341ee8
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/Undefined.java
@@ -0,0 +1,21 @@
1package lv.enes.orang.value;
2
3import lombok.EqualsAndHashCode;
4
5@EqualsAndHashCode
6public final class Undefined implements Value {
7 public static final Undefined INSTANCE = new Undefined();
8
9 private Undefined() {
10 }
11
12 @Override
13 public String typeName() {
14 return "Undefined";
15 }
16
17 @Override
18 public String stringify() {
19 return "#undefined";
20 }
21}
diff --git a/src/main/java/lv/enes/orang/value/Value.java b/src/main/java/lv/enes/orang/value/Value.java
new file mode 100644
index 0000000..fa8275c
--- /dev/null
+++ b/src/main/java/lv/enes/orang/value/Value.java
@@ -0,0 +1,77 @@
1package lv.enes.orang.value;
2
3import lv.enes.orang.OrangRuntimeException;
4
5public sealed interface Value
6 permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction,
7 PartialFunction, Undefined {
8 String typeName();
9 String stringify();
10
11 default String display() {
12 return stringify();
13 }
14
15
16 default Value negate() throws OrangRuntimeException {
17 throw new OrangRuntimeException(STR."negate is not implemented for \{typeName()}");
18 }
19
20 default Value not() throws OrangRuntimeException {
21 throw new OrangRuntimeException(STR."not is not implemented for \{typeName()}");
22 }
23
24 default Value plus() throws OrangRuntimeException {
25 throw new OrangRuntimeException(STR."plus is not implemented for \{typeName()}");
26 }
27
28
29 default Value add(Value rhs) throws OrangRuntimeException {
30 throw new OrangRuntimeException(STR."add is not implemented for \{typeName()} and \{rhs.typeName()}");
31 }
32
33 default Value call(Value rhs) throws OrangRuntimeException {
34 throw new OrangRuntimeException(STR."call is not implemented for \{typeName()} and \{rhs.typeName()}");
35 }
36
37 default Value divide(Value rhs) throws OrangRuntimeException {
38 throw new OrangRuntimeException(STR."divide is not implemented for \{typeName()} and \{rhs.typeName()}");
39 }
40
41 default Value multiply(Value rhs) throws OrangRuntimeException {
42 throw new OrangRuntimeException(STR."multiply is not implemented for \{typeName()} and \{rhs.typeName()}");
43 }
44
45 default Value or(Value rhs) throws OrangRuntimeException {
46 throw new OrangRuntimeException(STR."or is not implemented for \{typeName()} and \{rhs.typeName()}");
47 }
48
49 default Value subtract(Value rhs) throws OrangRuntimeException {
50 throw new OrangRuntimeException(STR."subtract is not implemented for \{typeName()} and \{rhs.typeName()}");
51 }
52
53
54 default OrangBoolean greaterThan(Value rhs) throws OrangRuntimeException {
55 throw new OrangRuntimeException(STR."greater than is not implemented for \{typeName()} and \{rhs.typeName()}");
56 }
57
58 default OrangBoolean greaterThanOrEqual(Value rhs) throws OrangRuntimeException {
59 return greaterThan(rhs).or(orangEquals(rhs));
60 }
61
62 default OrangBoolean lessThan(Value rhs) throws OrangRuntimeException {
63 return greaterThanOrEqual(rhs).not();
64 }
65
66 default OrangBoolean lessThanOrEqual(Value rhs) throws OrangRuntimeException {
67 return greaterThan(rhs).not();
68 }
69
70 default OrangBoolean orangEquals(Value rhs) {
71 return OrangBoolean.of(this.equals(rhs));
72 }
73
74 default OrangBoolean notEquals(Value rhs) {
75 return orangEquals(rhs).not();
76 }
77}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000..a4bdb66
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,9 @@
1module lv.enes.orang {
2 exports lv.enes.orang;
3
4 requires static jakarta.annotation;
5 requires static lombok;
6
7 requires org.slf4j;
8 requires org.slf4j.simple;
9} \ No newline at end of file
diff --git a/src/main/resources/lv/enes/orang/prelude.orang b/src/main/resources/lv/enes/orang/prelude.orang
new file mode 100644
index 0000000..36bf197
--- /dev/null
+++ b/src/main/resources/lv/enes/orang/prelude.orang
@@ -0,0 +1,20 @@
1def isRepl = __builtin_isRepl ()
2def len arrayOrString = __builtin_len arrayOrString
3def parseInt stringOrInt = __builtin_parseInt stringOrInt
4def print anything = __builtin_print anything
5def printLn x = do print x; print "\n"; x end
6def randInt min max = __builtin_randInt min max
7def readInt _ = parseInt (readLn ())
8def readLn _ = __builtin_readLn ()
9
10def _ =
11 if isRepl then do
12 printLn "Hello! This is the Orang Programming Language!";
13 printLn "You can execute code right here at the prompt.";
14 printLn "If you wish to load & execute a file, use `:l filename.orang'.";
15 printLn "If you wish to quit, use `:q'.";
16 printLn "Have a nice day :)";
17 printLn ""
18 end else
19 ()
20