diff options
Diffstat (limited to 'src')
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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import lombok.extern.slf4j.Slf4j; | ||
| 4 | import lv.enes.orang.value.*; | ||
| 5 | |||
| 6 | import java.io.IOException; | ||
| 7 | import java.util.List; | ||
| 8 | import java.util.Map; | ||
| 9 | import java.util.Random; | ||
| 10 | |||
| 11 | import static lv.enes.orang.State.STDIN; | ||
| 12 | import static lv.enes.orang.State.STDOUT; | ||
| 13 | |||
| 14 | @Slf4j | ||
| 15 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import java.io.*; | ||
| 4 | import java.util.Iterator; | ||
| 5 | import java.util.function.BiFunction; | ||
| 6 | import java.util.function.Predicate; | ||
| 7 | import java.util.stream.IntStream; | ||
| 8 | import java.util.stream.Stream; | ||
| 9 | |||
| 10 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import java.io.FileReader; | ||
| 4 | import java.io.IOException; | ||
| 5 | |||
| 6 | import static lv.enes.orang.State.STDIN; | ||
| 7 | import static lv.enes.orang.State.STDOUT; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import java.util.AbstractList; | ||
| 4 | import java.util.List; | ||
| 5 | |||
| 6 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import lv.enes.orang.ast.*; | ||
| 4 | import lv.enes.orang.ast.IfElseExpression; | ||
| 5 | import lv.enes.orang.ast.Statement; | ||
| 6 | |||
| 7 | import java.io.InputStream; | ||
| 8 | import java.io.Reader; | ||
| 9 | import java.util.ArrayList; | ||
| 10 | import java.util.Collections; | ||
| 11 | import java.util.Iterator; | ||
| 12 | import java.util.List; | ||
| 13 | import java.util.function.Predicate; | ||
| 14 | |||
| 15 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import java.util.ArrayDeque; | ||
| 4 | import java.util.Deque; | ||
| 5 | import java.util.Iterator; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import jakarta.annotation.Nullable; | ||
| 4 | import lv.enes.orang.value.Value; | ||
| 5 | |||
| 6 | import java.util.HashMap; | ||
| 7 | import java.util.Map; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import java.io.BufferedReader; | ||
| 4 | import java.io.InputStreamReader; | ||
| 5 | import java.io.PrintWriter; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package lv.enes.orang; | ||
| 2 | |||
| 3 | import lv.enes.orang.ast.BinaryExpression; | ||
| 4 | import lv.enes.orang.ast.UnaryExpression; | ||
| 5 | |||
| 6 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Array; | ||
| 6 | import lv.enes.orang.value.Value; | ||
| 7 | |||
| 8 | import java.util.ArrayList; | ||
| 9 | import java.util.Collections; | ||
| 10 | import java.util.List; | ||
| 11 | |||
| 12 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.Scope; | ||
| 4 | import lv.enes.orang.value.OrangBoolean; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import java.util.List; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Undefined; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.NonEmptyList; | ||
| 4 | import lv.enes.orang.OrangRuntimeException; | ||
| 5 | import lv.enes.orang.Scope; | ||
| 6 | import lv.enes.orang.value.Function; | ||
| 7 | import lv.enes.orang.value.Value; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.OrangBoolean; | ||
| 6 | import lv.enes.orang.value.Value; | ||
| 7 | |||
| 8 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.Scope; | ||
| 4 | import lv.enes.orang.value.OrangInteger; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Undefined; | ||
| 6 | import lv.enes.orang.value.Value; | ||
| 7 | |||
| 8 | import java.util.HashMap; | ||
| 9 | import java.util.List; | ||
| 10 | |||
| 11 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | |||
| 6 | import java.util.List; | ||
| 7 | |||
| 8 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | |||
| 6 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.OrangString; | ||
| 6 | import lv.enes.orang.value.Value; | ||
| 7 | |||
| 8 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.value.Value; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | import java.util.ArrayList; | ||
| 6 | import java.util.Collections; | ||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.ast.Expression; | ||
| 6 | |||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lombok.EqualsAndHashCode; | ||
| 4 | import lv.enes.orang.OrangRuntimeException; | ||
| 5 | |||
| 6 | @EqualsAndHashCode | ||
| 7 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | import java.util.ArrayList; | ||
| 6 | import java.util.Collections; | ||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | import lv.enes.orang.Scope; | ||
| 5 | import lv.enes.orang.ast.Expression; | ||
| 6 | |||
| 7 | import java.util.ArrayList; | ||
| 8 | import java.util.Collections; | ||
| 9 | import java.util.HashMap; | ||
| 10 | import java.util.List; | ||
| 11 | |||
| 12 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lombok.EqualsAndHashCode; | ||
| 4 | |||
| 5 | @EqualsAndHashCode | ||
| 6 | public 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 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | module 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 @@ | |||
| 1 | def isRepl = __builtin_isRepl () | ||
| 2 | def len arrayOrString = __builtin_len arrayOrString | ||
| 3 | def parseInt stringOrInt = __builtin_parseInt stringOrInt | ||
| 4 | def print anything = __builtin_print anything | ||
| 5 | def printLn x = do print x; print "\n"; x end | ||
| 6 | def randInt min max = __builtin_randInt min max | ||
| 7 | def readInt _ = parseInt (readLn ()) | ||
| 8 | def readLn _ = __builtin_readLn () | ||
| 9 | |||
| 10 | def _ = | ||
| 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 | |||