From 2c68ffa348c9fd45ad64e4cfb084a67af3a0f189 Mon Sep 17 00:00:00 2001 From: zeroflag Date: Fri, 25 Jun 2021 13:19:20 +0200 Subject: [PATCH] package moves --- Dictionary.java | 53 +++ Fcl.java | 585 +++++++++++++++++++++++++++++++++ FclStack.java | 108 ++++++ RamTranscript.java | 19 ++ Transcript.java | 17 + exceptions/Aborted.java | 7 + exceptions/FclException.java | 11 + exceptions/InterOpFailed.java | 11 + exceptions/NotUnderstood.java | 7 + exceptions/TypeMismatched.java | 21 ++ interop/JvmInterOp.java | 50 +++ interop/MethodSpec.java | 123 +++++++ types/ArithmeticOperand.java | 8 + types/Bool.java | 94 ++++++ types/Dic.java | 124 +++++++ types/JvmObj.java | 69 ++++ types/LogicOperand.java | 7 + types/Lst.java | 174 ++++++++++ types/Nil.java | 61 ++++ types/Num.java | 260 +++++++++++++++ types/Obj.java | 11 + types/Primitive.java | 84 +++++ types/Quot.java | 73 ++++ types/Range.java | 97 ++++++ types/Str.java | 175 ++++++++++ types/Word.java | 8 + 26 files changed, 2257 insertions(+) create mode 100644 Dictionary.java create mode 100644 Fcl.java create mode 100644 FclStack.java create mode 100644 RamTranscript.java create mode 100644 Transcript.java create mode 100644 exceptions/Aborted.java create mode 100644 exceptions/FclException.java create mode 100644 exceptions/InterOpFailed.java create mode 100644 exceptions/NotUnderstood.java create mode 100644 exceptions/TypeMismatched.java create mode 100644 interop/JvmInterOp.java create mode 100644 interop/MethodSpec.java create mode 100644 types/ArithmeticOperand.java create mode 100644 types/Bool.java create mode 100644 types/Dic.java create mode 100644 types/JvmObj.java create mode 100644 types/LogicOperand.java create mode 100644 types/Lst.java create mode 100644 types/Nil.java create mode 100644 types/Num.java create mode 100644 types/Obj.java create mode 100644 types/Primitive.java create mode 100644 types/Quot.java create mode 100644 types/Range.java create mode 100644 types/Str.java create mode 100644 types/Word.java diff --git a/Dictionary.java b/Dictionary.java new file mode 100644 index 0000000..9a14171 --- /dev/null +++ b/Dictionary.java @@ -0,0 +1,53 @@ +package com.vectron.fcl; + +import com.vectron.fcl.types.Word; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Dictionary { + private final List dict = new ArrayList<>(); + private final Set immediate = new HashSet<>(); + + public Dictionary() { + immediate.addAll(Arrays.asList(";", "immediate", "override")); + } + + public void add(Word word) { + dict.add(word); + } + + public Word at(String name) { + for (int i = dict.size() - 1; i >= 0; i--) { + Word each = dict.get(i); + if (each.visible() && name.equals(each.name())) + return each; + } + return null; + } + + public void remove(String name) { + Word exiting = at(name); + if (exiting != null) + dict.remove(exiting); + } + + public boolean isImmediate(String name) { + return immediate.contains(name); + } + + public void makeImmediate(Word word) { + immediate.add(word.name()); + } + + public Set wordList() { + Set result = new HashSet<>(); + for (Word word : dict) { + result.add(word.name()); + } + return result; + } +} diff --git a/Fcl.java b/Fcl.java new file mode 100644 index 0000000..ef91f1e --- /dev/null +++ b/Fcl.java @@ -0,0 +1,585 @@ +package com.vectron.fcl; + +import com.vectron.fcl.exceptions.Aborted; +import com.vectron.fcl.exceptions.TypeMismatched; +import com.vectron.fcl.interop.JvmInterOp; +import com.vectron.fcl.types.ArithmeticOperand; +import com.vectron.fcl.types.Bool; +import com.vectron.fcl.types.LogicOperand; +import com.vectron.fcl.types.Nil; +import com.vectron.fcl.types.Num; +import com.vectron.fcl.types.Obj; +import com.vectron.fcl.types.Primitive; +import com.vectron.fcl.types.Str; +import com.vectron.fcl.types.Word; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Set; + +public class Fcl { + public static final boolean STRICT = true; + private static final String EXIT = "exit"; + private final int SCRATCH_SIZE = 1024; + private enum Mode { COMPILE, INTERPRET } + private final Dictionary dict = new Dictionary(); + private final FclStack rstack = new FclStack(); + private final JvmInterOp interOp; + private final FclStack stack; + private final Transcript transcript; + private Word lastWord; + private Reader reader; + private Mode mode = Mode.INTERPRET; + private final Object[] heap; + private int dp = SCRATCH_SIZE; + private int ip = 0; + + class ColonDef implements Word { + private final int address; + private final String name; + private boolean visible = true; + + public ColonDef(int address, String name) { + this.address = address; + this.name = name; + } + + @Override + public void enter() { + rstack.push(new Num(ip)); + innerLoop(address); + ip = rstack.pop().intValue(); + } + + @Override + public String name() { + return name; + } + + @Override + public void visible(boolean isVisible) { + this.visible = isVisible; + } + + @Override + public boolean visible() { + return visible; + } + + @Override + public String toString() { + return "xt_" + name + " (" + address + ")"; + } + + @Override + public long longValue() { + return address; + } + + @Override + public int intValue() { + return address; + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return address; + } + + @Override + public int compareTo(Obj other) { + return other instanceof ColonDef + ? name.compareTo(((ColonDef) other).name) + : -1; + } + } + + public class Var implements Word { + private final int address; + private final String name; + private boolean visible = true; + + public Var(int address, String name) { + this.address = address; + this.name = name; + heap[address] = new Num(0); + } + + @Override + public void visible(boolean isVisible) { + this.visible = isVisible; + } + + @Override + public boolean visible() { + return visible; + } + + @Override + public void enter() { + stack.push(new Num(address)); + } + + @Override + public String name() { + return name; + } + + @Override + public String toString() { + return "var_" + name + " (" + address + ")"; + } + + @Override + public long longValue() { + return address; + } + + @Override + public int intValue() { + return address; + } + + @Override + public double doubleValue() { + return address; + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return address; + } + + @Override + public int compareTo(Obj other) { + return other instanceof Var + ? name.compareTo(((Var) other).name) + : -1; + } + } + + public class Val implements Word { + private final String name; + private final Obj value; + private boolean visible = true; + + public Val(String name, Obj value) { + this.name = name; + this.value = value; + } + + @Override + public void visible(boolean isVisible) { + this.visible = isVisible; + } + + @Override + public boolean visible() { + return visible; + } + + @Override + public void enter() { + stack.push(value); + } + + @Override + public String name() { + return name; + } + + @Override + public String toString() { + return "val_" + name + " (" + value + ")"; + } + + @Override + public long longValue() { + return value.longValue(); + } + + @Override + public int intValue() { + return value.intValue(); + } + + @Override + public double doubleValue() { + return value.doubleValue(); + } + + @Override + public boolean boolValue() { + return value.boolValue(); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return value; + } + + @Override + public int compareTo(Obj other) { + return other instanceof Val + ? name.compareTo(((Val) other).name) + : -1; + } + } + + + public Fcl(FclStack stack, int heapSize, Transcript transcript) { + this.stack = stack; + this.heap = new Object[heapSize]; + this.interOp = new JvmInterOp(stack); + this.transcript = transcript; + initPrimitives(); + } + + private void initPrimitives() { + addPrimitive("+", () -> stack.push((aOp(stack.pop())).add(stack.pop()))); + addPrimitive("-", () -> { + Obj top = stack.pop(); + stack.push(aOp(stack.pop()).sub(top)); + }); + addPrimitive("*", () -> stack.push((aOp(stack.pop())).mul(stack.pop()))); + addPrimitive("/", () -> { + Obj top = stack.pop(); + stack.push((aOp(stack.pop())).div(top)); + }); + addPrimitive("/mod", () -> { + Num b = stack.pop().asNum(); + Num a = stack.pop().asNum(); + stack.push(a.mod(b)); + stack.push(a.intDiv(b)); + }); + addPrimitive("pow", () -> { + Num exponent = stack.pop().asNum(); + Num base = stack.pop().asNum(); + stack.push(base.power(exponent)); + }); + addPrimitive("and", () -> stack.push(lOp(stack.pop()).and(stack.pop()))); + addPrimitive("or", () -> stack.push((lOp(stack.pop())).or(stack.pop()))); + addPrimitive("not", () -> stack.push((lOp(stack.pop())).not())); + addPrimitive("drop", stack::pop); + addPrimitive("dup", () -> stack.push(stack.peek())); + addPrimitive("swap", () -> { + Obj a = stack.pop(); + Obj b = stack.pop(); + stack.push(a); + stack.push(b); + }); + addPrimitive("rswap", () -> { + Obj a = rstack.pop(); + Obj b = rstack.pop(); + rstack.push(a); + rstack.push(b); + }); + addPrimitive(EXIT, () -> {}); + addPrimitive("clean", stack::clean); + addPrimitive("depth", () -> stack.push(new Num(stack.size()))); + addPrimitive("=", () -> stack.push(stack.pop().equals(stack.pop()) ? Bool.TRUE : Bool.FALSE)); + addPrimitive("<", () -> stack.push(stack.pop().asNum().greater(stack.pop().asNum()))); + addPrimitive("true", () -> stack.push(Bool.TRUE)); + addPrimitive("false", () -> stack.push(Bool.FALSE)); + addPrimitive("nil", () -> stack.push(Nil.INSTANCE)); + addPrimitive("here", () -> stack.push(new Num(dp))); + addPrimitive("interpret", () -> mode = Mode.INTERPRET); + addPrimitive("lit", () -> stack.push((Obj)heap[ip++])); + addPrimitive(">r", () -> rstack.push(stack.pop())); + addPrimitive("r>", () -> stack.push(rstack.pop())); + addPrimitive("i", () -> stack.push(rstack.peek())); + addPrimitive("j", () -> stack.push(rstack.at(1))); + addPrimitive(",", () -> heap[dp++] = stack.pop()); + addPrimitive("!", () -> heap[stack.pop().intValue()] = stack.pop()); + addPrimitive("@", () -> stack.push((Obj) heap[stack.pop().intValue()])); + addPrimitive("[']", () -> stack.push((Word)heap[ip++])); + addPrimitive("`", () -> { Word word = dict.at(word()); stack.push(word == null ? Nil.INSTANCE : word); }); + addPrimitive("immediate", () -> dict.makeImmediate(lastWord)); + addPrimitive(".", () -> show(stack.pop())); + addPrimitive("jvm-call-static", interOp::jvmCallStatic); + addPrimitive("jvm-call-method", interOp::jvmCallMethod); + addPrimitive("jvm-static-var", interOp::jvmStaticVar); + addPrimitive("jvm-null", () -> stack.push(null)); + addPrimitive("asc*", this::sortAsc); + addPrimitive("dsc*", this::sortDsc); + addPrimitive("rev*", this::reverse); + addPrimitive("key", () -> stack.push(new Num(key()))); + addPrimitive("word", () -> stack.push(new Str(word()))); + addPrimitive("override", () -> lastWord.visible(false)); + addPrimitive("reveal", () -> lastWord.visible(true)); + addPrimitive("delword", () -> dict.remove((String)stack.pop().value())); + addPrimitive("jmp#f", () -> ip += stack.pop().boolValue() ? 1 : ((Num) heap[ip]).longValue()); + addPrimitive("jmp", () -> ip += ((Num) heap[ip]).longValue()); + addPrimitive("allot", () -> { int p = dp; dp += stack.pop().longValue(); stack.push(new Num(p)); }); + addPrimitive("freemem", () -> stack.push(new Num(heap.length - dp))); + addPrimitive("var:", () -> { String name = word(); dict.add(new Var(dp, name)); dp++; }); + addPrimitive("val:", () -> { String name = word(); dict.add(new Val(name, stack.pop())); }); + addPrimitive("abort", () -> { throw new Aborted(stack.pop().asStr().value()); }); + addPrimitive("exec", () -> { + rstack.push(new Num(ip)); + innerLoop(pop().intValue()); + ip = rstack.pop().intValue(); + }); + addPrimitive("create", () -> dict.add(new ColonDef(dp, (String)stack.pop().value()))); + addPrimitive("dasm", this::disassemble); + addPrimitive(":", () -> { + lastWord = new ColonDef(dp, word()); + dict.add(lastWord); + mode = Mode.COMPILE; + }); + addPrimitive(";", () -> { + heap[dp++] = dict.at(EXIT); + heap[dp++] = Nil.INSTANCE; + mode = Mode.INTERPRET; + lastWord.visible(true); + }); + } + + private LogicOperand lOp(Obj obj) { + try { + return (LogicOperand) obj; + } catch (ClassCastException e) { + throw new TypeMismatched(obj + " cannot do logic operators on " + obj); + } + } + + private ArithmeticOperand aOp(Obj obj) { + try { + return (ArithmeticOperand) obj; + } catch (ClassCastException e) { + throw new TypeMismatched(obj + " cannot do arithmetic"); + } + } + + private void show(Obj pop) { + transcript.show(pop.asStr().value()); + transcript.cr(); + } + + private void disassemble() { + String name = (String) stack.pop().value(); + Word word = dict.at(name); + if (word instanceof ColonDef) { + int address = ((ColonDef)word).address; + while (true) { + transcript.show(String.format("[%08X] %s", address, heap[address])); + transcript.cr(); + if (heap[address] instanceof Word && ((Word)heap[address]).name().equals(EXIT) + && heap[address+1] == Nil.INSTANCE) { + break; + } + address++; + } + } else { + System.err.println("Not colon def: " + word); + } + } + + private void sortDsc() { + stack.sortDsc(); + } + + private void sortAsc() { + stack.sortAsc(); + } + + private void reverse() { + stack.reverse(); + } + + private void addPrimitive(String name, Runnable code) { + dict.add(new Primitive(name, code)); + } + + public void eval(String source) { + eval(new StringReader(source)); + } + + public void eval(Reader reader) { + this.reader = reader; + String token = word(); + while (!token.isEmpty()) { + onTokenFound(token); + token = word(); + } + } + + /** + * Compile a temporary word into the scratch area and call it. + * This is useful for evaluating words without interpretation semantics. + * Like: if else then, loops, quotations + */ + public void compileTmpAndEval(String script) { + int savedDp = dp; + Mode savedMode = mode; + try { + dp = heap.length - SCRATCH_SIZE; + mode = Mode.COMPILE; + eval(script); + eval(";"); + mode = Mode.INTERPRET; + innerLoop(heap.length - SCRATCH_SIZE); + } finally { + dp = savedDp; + mode = savedMode; + } + } + + private String word() { + StringBuilder token = new StringBuilder(); + int key = key(); + while (key != -1) { + char chr = (char) key; + if (Character.isWhitespace(chr)) { + if (token.length() > 0) + return token.toString(); + token.setLength(0); + } else { + token.append(chr); + } + key = key(); + } + return token.toString(); + } + + private int key() { + try { + return reader.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void onTokenFound(String name) { + Word word = dict.at(name); + switch (mode) { + case INTERPRET: + if (word != null) + word.enter(); + else + stack.push(recognize(name)); + break; + case COMPILE: + if (word != null) { + if (dict.isImmediate(name)) + word.enter(); + else + heap[dp++] = word; + } else { + heap[dp++] = dict.at("lit"); + heap[dp++] = recognize(name); + } + break; + } + } + + private Obj recognize(String token) { + Obj str = recognizeStr(token); + if (str != null) return str; + return Num.parse(token); + } + + private Obj recognizeStr(String firstToken) { + if (!firstToken.startsWith("'")) return null; + StringBuilder str = new StringBuilder(firstToken.substring(1)); + if (firstToken.endsWith("'") && firstToken.length() > 1) { + str.setLength(str.length() - 1); + } else { + str.append(" "); + int k = key(); + while (k != -1 && (char) k != '\'') { + str.append((char) k); + k = key(); + } + } + return new Str(str.toString()); + } + + private void innerLoop(int address) { + ip = address; + Word word = (Word) heap[ip++]; + while (!EXIT.equals(word.name())) { + word.enter(); + word = (Word) heap[ip++]; + } + } + + public Word get(String name) { + return dict.at(name); + } + + public Obj pop() { + return stack.pop(); + } + + public int stackSize() { + return stack.size(); + } + + public int rStackSize() { + return rstack.size(); + } + + public void switchStack(FclStack stack) { + this.stack.switchStack(stack); + } + + public void reset() { + mode = Mode.INTERPRET; + stack.clean(); + rstack.clean(); + } + + public Set wordList() { + return dict.wordList(); + } +} \ No newline at end of file diff --git a/FclStack.java b/FclStack.java new file mode 100644 index 0000000..9a8b6c0 --- /dev/null +++ b/FclStack.java @@ -0,0 +1,108 @@ +package com.vectron.fcl; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.vectron.forthcalc.domain.ports.FileStore; +import com.vectron.fcl.types.Obj; +import com.vectron.forthcalc.support.FclTypeAdapter; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +public class FclStack { + private static final Gson gson; + private final Stack stack = new Stack<>(); + + static { + FclTypeAdapter typeAdapter = new FclTypeAdapter(); + gson = new GsonBuilder() + .registerTypeAdapter(Obj.class, typeAdapter) + .setLenient() + .serializeSpecialFloatingPointValues() + .create(); + typeAdapter.setGSon(gson); + } + + public void push(Obj obj) { + stack.push(obj); + } + + public Obj pop() { + return stack.pop(); + } + + public Obj peek() { + return stack.peek(); + } + + public boolean empty() { + return stack.empty(); + } + + public void clean() { + stack.clear(); + } + + public int size() { + return stack.size(); + } + + public Obj at(int index) { + return stack.get(stack.size() - index -1); + } + + public void switchStack(FclStack other) { + List copy = new ArrayList<>(other.stack); + other.stack.clear(); + other.stack.addAll(this.stack); + this.stack.clear(); + this.stack.addAll(copy); + } + + public void sortDsc() { + Collections.sort(stack, (o1, o2) -> o1.compareTo(o2)); + } + + public void sortAsc() { + Collections.sort(stack, (o1, o2) -> o2.compareTo(o1)); + } + + public void reverse() { + Collections.reverse(stack); + } + + public void load(FileStore fileStore, String id) { + FileInputStream stream = null; + try { + stream = fileStore.open(fileName(id)); + Obj[] loaded = gson.fromJson(new BufferedReader(new InputStreamReader(stream)), Obj[].class); + stack.clear(); + for (Obj each : loaded) + stack.add(each); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + public void save(FileStore fileStore, String id) { + fileStore.save(gson.toJson(stack.toArray(new Obj[0])).getBytes(), fileName(id)); + } + + private String fileName(String id) { + return String.format("stack%s.json", id); + } +} diff --git a/RamTranscript.java b/RamTranscript.java new file mode 100644 index 0000000..330053e --- /dev/null +++ b/RamTranscript.java @@ -0,0 +1,19 @@ +package com.vectron.fcl; + +public class RamTranscript implements Transcript { + private final StringBuilder content = new StringBuilder(); + + @Override + public void show(String str) { + content.append(str); + } + + @Override + public void cr() { + content.append("\n"); + } + + public String content() { + return content.toString(); + } +} diff --git a/Transcript.java b/Transcript.java new file mode 100644 index 0000000..f4d9bda --- /dev/null +++ b/Transcript.java @@ -0,0 +1,17 @@ +package com.vectron.fcl; + +public interface Transcript { + void show(String str); + void cr(); + + Transcript STDOUT = new Transcript() { + public void show(String str) { + System.out.print(str); + } + + @Override + public void cr() { + System.out.println(""); + } + }; +} diff --git a/exceptions/Aborted.java b/exceptions/Aborted.java new file mode 100644 index 0000000..e0d9be8 --- /dev/null +++ b/exceptions/Aborted.java @@ -0,0 +1,7 @@ +package com.vectron.fcl.exceptions; + +public class Aborted extends FclException { + public Aborted(String str) { + super(str); + } +} diff --git a/exceptions/FclException.java b/exceptions/FclException.java new file mode 100644 index 0000000..fac912d --- /dev/null +++ b/exceptions/FclException.java @@ -0,0 +1,11 @@ +package com.vectron.fcl.exceptions; + +public class FclException extends RuntimeException { + public FclException(String message) { + super(message); + } + + public FclException(Throwable cause) { + super(cause); + } +} diff --git a/exceptions/InterOpFailed.java b/exceptions/InterOpFailed.java new file mode 100644 index 0000000..d7da7f0 --- /dev/null +++ b/exceptions/InterOpFailed.java @@ -0,0 +1,11 @@ +package com.vectron.fcl.exceptions; + +public class InterOpFailed extends FclException { + public InterOpFailed(Exception e) { + super(e); + } + + public InterOpFailed(String message) { + super(message); + } +} diff --git a/exceptions/NotUnderstood.java b/exceptions/NotUnderstood.java new file mode 100644 index 0000000..04859aa --- /dev/null +++ b/exceptions/NotUnderstood.java @@ -0,0 +1,7 @@ +package com.vectron.fcl.exceptions; + +public class NotUnderstood extends FclException { + public NotUnderstood(String message) { + super(message); + } +} diff --git a/exceptions/TypeMismatched.java b/exceptions/TypeMismatched.java new file mode 100644 index 0000000..d52a144 --- /dev/null +++ b/exceptions/TypeMismatched.java @@ -0,0 +1,21 @@ +package com.vectron.fcl.exceptions; + +import com.vectron.fcl.types.Obj; + +public class TypeMismatched extends FclException { + public TypeMismatched(String operator, Obj a, Obj b) { + super(String.format("Unsupported types for %s: %s and %s", operator, a, b)); + } + + public TypeMismatched(String operator, Obj a) { + super(String.format("Unsupported types for %s: %s", operator, a)); + } + + public TypeMismatched(Obj obj, String type) { + super(obj + " (" + obj.getClass().getSimpleName() + ") is not convertible to " + type); + } + + public TypeMismatched(String message) { + super(message); + } +} diff --git a/interop/JvmInterOp.java b/interop/JvmInterOp.java new file mode 100644 index 0000000..c9dc318 --- /dev/null +++ b/interop/JvmInterOp.java @@ -0,0 +1,50 @@ +package com.vectron.fcl.interop; + +import com.vectron.fcl.FclStack; +import com.vectron.fcl.exceptions.InterOpFailed; +import com.vectron.fcl.types.JvmObj; +import com.vectron.fcl.types.Obj; + +import java.util.Random; + +import static com.vectron.fcl.interop.MethodSpec.processResult; + +public class JvmInterOp { + private static final Random RND = new Random(); + private final FclStack stack; + + public JvmInterOp(FclStack stack) { + this.stack = stack; + } + + public void jvmCallStatic() { + MethodSpec spec = MethodSpec.parseStatic(stack.pop().asStr().value()); + spec.invoke(stack); + } + + public void jvmCallMethod() { + String methodName = stack.pop().asStr().value(); + Obj receiver = stack.pop(); + MethodSpec spec = MethodSpec.parseDynamic( + methodName, + receiver instanceof JvmObj ? ((JvmObj) receiver).value() : receiver); + spec.invoke(stack); + } + + public void jvmStaticVar() { + String spec = stack.pop().asStr().value(); + String[] parts = spec.split("/"); + String className = parts[0]; + String varName = parts[1]; + try { + Class clazz = Class.forName(className); + processResult(clazz.getDeclaredField(varName).get(null), stack); + } catch (ReflectiveOperationException e) { + throw new InterOpFailed(e); + } + } + + public static double random() { + return RND.nextDouble(); + } +} diff --git a/interop/MethodSpec.java b/interop/MethodSpec.java new file mode 100644 index 0000000..00c8295 --- /dev/null +++ b/interop/MethodSpec.java @@ -0,0 +1,123 @@ +package com.vectron.fcl.interop; + +import com.vectron.fcl.FclStack; +import com.vectron.fcl.exceptions.InterOpFailed; +import com.vectron.fcl.types.Bool; +import com.vectron.fcl.types.JvmObj; +import com.vectron.fcl.types.Nil; +import com.vectron.fcl.types.Num; +import com.vectron.fcl.types.Obj; +import com.vectron.fcl.types.Str; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +class MethodSpec { + private final Class clazz; + private final Object receiver; + private final String methodName; + private final int arity; + private final String typeSpec; + + public static MethodSpec parseStatic(String spec) { + String[] parts = spec.split("/"); + try { + return new MethodSpec(Class.forName(parts[0]), null, parts[1], typeSpec(parts, 2)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static MethodSpec parseDynamic(String spec, Object receiver) { + String[] parts = spec.split("/"); + return new MethodSpec(receiver.getClass(), receiver, parts[0], typeSpec(parts, 1)); + } + + private static String typeSpec(String[] parts, int index) { + String types = ""; + if (parts.length > index) { + types = parts[index]; + } + return types; + } + + private MethodSpec(Class clazz, Object receiver, String methodName, String typeSpec) { + this.clazz = clazz; + this.receiver = receiver; + this.methodName = methodName; + this.arity = typeSpec.length(); + this.typeSpec = typeSpec; + } + + public void invoke(FclStack stack) { + List params = new ArrayList<>(); + List> types = new ArrayList<>(); + for (int i = 0; i < arity; i++) { + Obj value = stack.pop(); + Class clazz = typeOf(typeSpec.charAt(i)); + addParam(params, value, clazz); + types.add(clazz); + } + try { + Method method = clazz.getMethod(methodName, types.toArray(new Class[0])); + method.setAccessible(true); + Object result = method.invoke(receiver, params.toArray(new Object[0])); + if (!method.getReturnType().getSimpleName().equals("void")) + processResult(result, stack); + } catch (ReflectiveOperationException e) { + throw new InterOpFailed(e); + } + } + + public static void processResult(Object result, FclStack stack) { + if (result != null) { + if (result instanceof Number) + stack.push(new Num((Number) result)); + else if (result instanceof String) + stack.push(new Str((String) result)); + else if (result instanceof Boolean) + stack.push((boolean)result ? Bool.TRUE : Bool.FALSE); + else if (result instanceof Obj) + stack.push((Obj)result); + else + stack.push(new JvmObj(result)); + } else { + stack.push(Nil.INSTANCE); + } + } + + private void addParam(List params, Obj value, Class clazz) { + if (clazz == Integer.TYPE) + params.add(value.intValue()); + else if (clazz == Long.TYPE) + params.add(value.longValue()); + else if (clazz == Double.TYPE) + params.add(value.doubleValue()); + else if (clazz == String.class) + params.add((String)value.value()); + else if (clazz == Map.class) + params.add((Map)value.value()); + else if (clazz == List.class) + params.add((List)value.value()); + else if (clazz == Obj.class) + params.add(value); + else + throw new InterOpFailed("Unsupported inter-op type: " + clazz); + } + + private Class typeOf(Character type) { + switch (type) { + case 'i': return Integer.TYPE; + case 'd': return Double.TYPE; + case 'l': return Long.TYPE; + case 's': return String.class; + case 'm': return Map.class; + case 't': return List.class; + case 'O': return Obj.class; + default: + throw new InterOpFailed("Invalid type spec: " + type); + } + } +} diff --git a/types/ArithmeticOperand.java b/types/ArithmeticOperand.java new file mode 100644 index 0000000..3c01656 --- /dev/null +++ b/types/ArithmeticOperand.java @@ -0,0 +1,8 @@ +package com.vectron.fcl.types; + +public interface ArithmeticOperand { + Obj add(Obj other); + Obj sub(Obj other); + Obj mul(Obj other); + Obj div(Obj other); +} diff --git a/types/Bool.java b/types/Bool.java new file mode 100644 index 0000000..83dc4c9 --- /dev/null +++ b/types/Bool.java @@ -0,0 +1,94 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.util.Objects; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Bool implements Obj, LogicOperand { + public static final Bool TRUE = new Bool(true); + public static final Bool FALSE = new Bool(false); + private final boolean value; + + private Bool(boolean value) { + this.value = value; + } + + public Bool and(Obj other) { + return new Bool(value && other.boolValue()); + } + + public Bool or(Obj other) { + return new Bool(value || other.boolValue()); + } + + public Bool not() { + return new Bool(!value); + } + + @Override + public boolean boolValue() { + return value; + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return value ? Num.ONE : Num.ZERO; + } + + @Override + public Object value() { + return value; + } + + @Override + public long longValue() { + if (STRICT) throw new TypeMismatched(this, "long"); + return value ? 1l : 0l; + } + + @Override + public int intValue() { + if (STRICT) throw new TypeMismatched(this, "int"); + return value ? 1 : 0; + } + + @Override + public double doubleValue() { + if (STRICT) throw new TypeMismatched(this, "double"); + return value ? 1.0 : 0.0; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public String toString() { + return value ? "true" : "false"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Bool bool = (Bool) o; + return value == bool.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public int compareTo(Obj o) { + if (o instanceof Bool) + return Boolean.compare(value, o.boolValue()); + else + return -1; + } +} diff --git a/types/Dic.java b/types/Dic.java new file mode 100644 index 0000000..174cc3b --- /dev/null +++ b/types/Dic.java @@ -0,0 +1,124 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Dic implements Obj { + private final Map value = new LinkedHashMap<>(); + + public static Dic empty() { + return new Dic(); + } + + @Override + public long longValue() { + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return value; + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + public Iterator iterator() { + return new Iterator() { + private Iterator> it = value.entrySet().iterator(); + @Override + public boolean hasNext() { + return it.hasNext(); + } + @Override + public Lst next() { + Map.Entry next = it.next(); + Lst result = Lst.empty(); + result.append(next.getKey()); + result.append(next.getValue()); + return result; + } + }; + } + + public int size() { + return value.size(); + } + + public void put(Obj key, Obj value) { + this.value.put(key, value); + } + + public Obj at(Obj key) { + return value.get(key); + } + + public void remove(Obj item) { + value.remove(item); + } + + public Lst keys() { + Lst result = Lst.empty(); + for (Obj each : value.keySet()) + result.append(each); + return result; + } + + public Lst values() { + Lst result = Lst.empty(); + for (Obj each : value.values()) + result.append(each); + return result; + } + + public void clear() { + value.clear(); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder("#[ "); + int i = 0; + for (Map.Entry each : value.entrySet()) { + result.append(each.getKey()); + result.append(" "); + result.append(each.getValue()); + if (i < value.size() -1) result.append(" "); i++; + } + result.append(" ]#"); + return result.toString(); + } +} diff --git a/types/JvmObj.java b/types/JvmObj.java new file mode 100644 index 0000000..d0c3d3a --- /dev/null +++ b/types/JvmObj.java @@ -0,0 +1,69 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import static com.vectron.fcl.Fcl.STRICT; + +public class JvmObj implements Obj { + private final Object object; + + public JvmObj(Object object) { + this.object = object; + } + + @Override + public long longValue() { + if (object instanceof Number) + return ((Number)object).longValue(); + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + if (object instanceof Number) + return ((Number)object).intValue(); + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + if (object instanceof Number) + return ((Number)object).doubleValue(); + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + if (object instanceof Boolean) + return (boolean) object; + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (object instanceof Number) + return new Num((Number)object); + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return object; + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + @Override + public String toString() { + return "JvmObj:" + object; + } +} diff --git a/types/LogicOperand.java b/types/LogicOperand.java new file mode 100644 index 0000000..1bc0b1e --- /dev/null +++ b/types/LogicOperand.java @@ -0,0 +1,7 @@ +package com.vectron.fcl.types; + +public interface LogicOperand { + Obj and(Obj other); + Obj or(Obj other); + Obj not(); +} diff --git a/types/Lst.java b/types/Lst.java new file mode 100644 index 0000000..3b7d015 --- /dev/null +++ b/types/Lst.java @@ -0,0 +1,174 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Lst implements Obj, ArithmeticOperand { + private final List value = new ArrayList<>(); + + public static Lst empty() { + return new Lst(); + } + + @Override + public long longValue() { + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Object value() { + return value; + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + public int size() { + return value.size(); + } + + public void append(Obj value) { + this.value.add(value); + } + + public void prep(Obj value) { + this.value.add(0, value); + } + + public Obj at(Obj index) { + return value.get(index.intValue()); + } + + public int indexOf(Obj item) { + return value.indexOf(item); + } + + public Lst concat(Obj other) { + Lst result = Lst.empty(); + result.value.addAll(value); + result.value.addAll((List)other.value()); + return result; + } + + public Lst subList(int start, int stop) { + Lst result = Lst.empty(); + result.value.addAll(value.subList(start, stop)); + return result; + } + + public void remove(Obj item) { + value.remove(item); + } + + public void removeAt(int index) { + value.remove(index); + } + + public void clear() { + value.clear(); + } + + public Iterator iterator() { + return value.iterator(); + } + + public Lst reverse() { + Lst result = Lst.empty(); + for (int i = value.size() - 1; i >= 0; i--) + result.append(value.get(i)); + return result; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder("[ "); + for (int i = 0; i < value.size(); i++) { + Obj each = value.get(i); + result.append(each); + if (i < value.size() -1) result.append(" "); + } + result.append(" ]"); + return result.toString(); + } + + @Override + public Lst add(Obj other) { + if (other instanceof Num) { + Lst result = Lst.empty(); + for (Obj each : value) + result.append(each.asNum().add(other)); + return result; + } else { + throw new TypeMismatched("+", this, other); + } + } + + @Override + public Lst sub(Obj other) { + if (other instanceof Num) { + Lst result = Lst.empty(); + for (Obj each : value) + result.append(each.asNum().sub(other)); + return result; + } else { + throw new TypeMismatched("-", this, other); + } + } + + @Override + public Lst mul(Obj other) { + if (other instanceof Num) { + Lst result = Lst.empty(); + for (Obj each : value) + result.append(each.asNum().mul(other)); + return result; + } else { + throw new TypeMismatched("*", this, other); + } + } + + @Override + public Lst div(Obj other) { + if (other instanceof Num) { + Lst result = Lst.empty(); + for (Obj each : value) + result.append(each.asNum().div(other)); + return result; + } else { + throw new TypeMismatched("/", this, other); + } + } +} diff --git a/types/Nil.java b/types/Nil.java new file mode 100644 index 0000000..373b0db --- /dev/null +++ b/types/Nil.java @@ -0,0 +1,61 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Nil implements Obj { + public static final Nil INSTANCE = new Nil(); + + private Nil() {} + + @Override + public long longValue() { + if (STRICT) throw new TypeMismatched(this, "long"); + return 0l; + } + + @Override + public int intValue() { + if (STRICT) throw new TypeMismatched(this, "int"); + return 0; + } + + @Override + public double doubleValue() { + if (STRICT) throw new TypeMismatched(this, "double"); + return 0.0; + } + + @Override + public boolean boolValue() { + if (STRICT) throw new TypeMismatched(this, "bool"); + return false; + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.ZERO; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return null; + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + @Override + public String toString() { + return "nil"; + } +} diff --git a/types/Num.java b/types/Num.java new file mode 100644 index 0000000..89e2c52 --- /dev/null +++ b/types/Num.java @@ -0,0 +1,260 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.NotUnderstood; +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.text.NumberFormat; +import java.util.Objects; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Num implements Obj, LogicOperand, ArithmeticOperand { + public static final Num ZERO = new Num(0); + public static final Num ONE = new Num(1); + public static final Num NAN = new Num(Double.NaN); + private static final NumberFormat format = NumberFormat.getNumberInstance(); + private final Number value; + + static { + format.setMaximumFractionDigits(4); + format.setGroupingUsed(false); + format.setParseIntegerOnly(false); + } + + public Num(Number value) { + if (value instanceof Integer) + this.value = ((Integer)value).longValue(); + else + this.value = value; + } + + public static Num parse(String str) { + try { + return new Num(Long.parseLong(str)); + } catch (NumberFormatException e1) { + try { + return new Num(Double.parseDouble(str)); + } catch (NumberFormatException e2) { + throw new NotUnderstood("Undefined word: " + str); + } + } + } + + @Override + public String toString() { + if (value instanceof Long) { + return Long.toString((Long) value); + } else if (value instanceof Double) { + return format.format(value); + } + return value.toString(); + } + + @Override + public Obj add(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num((Long) value + (Long)other.value()); + else if (value instanceof Long && other.value() instanceof Double) + return new Num((Long) value + (Double)other.value()); + else if (value instanceof Double && other.value() instanceof Long) + return new Num((Double) value + (Long)other.value()); + else if (value instanceof Double && other.value() instanceof Double) + return new Num((Double) value + (Double) other.value()); + else if (other instanceof Lst) + return ((Lst) other).add(this); + else if (STRICT) + throw new TypeMismatched("+", this, other); + return Num.NAN; + } + + @Override + public Obj sub(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num((Long) value - (Long)other.value()); + else if (value instanceof Long && other.value() instanceof Double) + return new Num((Long) value - (Double)other.value()); + else if (value instanceof Double && other.value() instanceof Long) + return new Num((Double) value - (Long)other.value()); + else if (value instanceof Double && other.value() instanceof Double) + return new Num((Double) value - (Double) other.value()); + else if (STRICT) + throw new TypeMismatched("-", this, other); + return Num.NAN; + } + + @Override + public Obj mul(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num((Long) value * (Long)other.value()); + else if (value instanceof Long && other.value() instanceof Double) + return new Num((Long) value * (Double)other.value()); + else if (value instanceof Double && other.value() instanceof Long) + return new Num((Double) value * (Long)other.value()); + else if (value instanceof Double && other.value() instanceof Double) + return new Num((Double) value * (Double) other.value()); + else if (other instanceof Lst) + return ((Lst) other).mul(this); + else if (other instanceof Str) + return ((Str) other).mul(this); + else if (STRICT) + throw new TypeMismatched("*", this, other); + return Num.NAN; + } + + @Override + public Obj div(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num(((Long) value).doubleValue() / (Long) other.value()); + else if (value instanceof Long && other.value() instanceof Double) + return new Num((Long) value / (Double)other.value()); + else if (value instanceof Double && other.value() instanceof Long) + return new Num((Double) value / (Long)other.value()); + else if (value instanceof Double && other.value() instanceof Double) + return new Num((Double) value / (Double) other.value()); + else if (STRICT) + throw new TypeMismatched("/", this, other); + return Num.NAN; + } + + public Num power(Num exponent) { + if (value instanceof Long && exponent.value instanceof Long) + return new Num(Math.pow(((Long) value).doubleValue(), ((Long) exponent.value).doubleValue())); + else if (value instanceof Long && exponent.value instanceof Double) + return new Num(Math.pow(((Long) value).doubleValue(), exponent.doubleValue())); + else if (value instanceof Double && exponent.value instanceof Long) + return new Num(Math.pow((Double)value, ((Long) exponent.value).doubleValue())); + else if (value instanceof Double && exponent.value instanceof Double) + return new Num(Math.pow((Double)value, exponent.doubleValue())); + else if (STRICT) + throw new TypeMismatched("POW", this, exponent); + return Num.NAN; + } + + public Num mod(Num other) { + try { + return new Num(this.longValue() % other.longValue()); + } catch (TypeMismatched e) { + if (STRICT) throw e; + return Num.NAN; + } + } + + public Num intDiv(Num other) { + try { + return new Num(this.longValue() / other.longValue()); + } catch (TypeMismatched e) { + if (STRICT) throw e; + return Num.NAN; + } + } + + public Num round() { + if (value instanceof Long) + return this; + else if (value instanceof Double) + return new Num(Math.round(doubleValue())); + else if (STRICT) + throw new TypeMismatched("ROUND", this); + return Num.NAN; + } + + @Override + public Num and(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num((Long)value & other.longValue()); + else if (STRICT) + throw new TypeMismatched("AND", this, other); + return Num.NAN; + } + + @Override + public Num or(Obj other) { + if (value instanceof Long && other.value() instanceof Long) + return new Num((Long)value | other.longValue()); + else if (STRICT) + throw new TypeMismatched("OR", this, other); + return Num.NAN; + } + + @Override + public Num not() { + if (value instanceof Long) + return new Num(~(Long)value ); + else if (STRICT) + throw new TypeMismatched("Unsupported types for NOT operator: " + value.getClass()); + return Num.NAN; + } + + public Bool greater(Num other) { + if (value instanceof Long && other.value instanceof Long) + return (Long) value > (Long)other.value ? Bool.TRUE : Bool.FALSE; + else if (value instanceof Long && other.value instanceof Double) + return (Long) value > (Double)other.value ? Bool.TRUE : Bool.FALSE; + else if (value instanceof Double && other.value instanceof Long) + return (Double) value > (Long)other.value ? Bool.TRUE : Bool.FALSE; + else if (value instanceof Double && other.value instanceof Double) + return (Double) value > (Double) other.value ? Bool.TRUE : Bool.FALSE; + else + throw new TypeMismatched("<", this, other); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Num num = (Num) o; + return value.equals(num.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public long longValue() { + if (value instanceof Long) + return (Long) value; + else if (value instanceof Double) + return Math.round((Double) value); + else + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + return ((Number)value).intValue(); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + return this; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public double doubleValue() { + return ((Number)value).doubleValue(); + } + + @Override + public Object value() { + return value; + } + + @Override + public int compareTo(Obj other) { + return other instanceof Num + ? Double.compare(doubleValue(), ((Num) other).doubleValue()) + : -1; + } +} diff --git a/types/Obj.java b/types/Obj.java new file mode 100644 index 0000000..a483ef8 --- /dev/null +++ b/types/Obj.java @@ -0,0 +1,11 @@ +package com.vectron.fcl.types; + +public interface Obj extends Comparable { + long longValue(); + int intValue(); + double doubleValue(); + boolean boolValue(); + Num asNum(); + Str asStr(); + Object value(); +} \ No newline at end of file diff --git a/types/Primitive.java b/types/Primitive.java new file mode 100644 index 0000000..3fac79d --- /dev/null +++ b/types/Primitive.java @@ -0,0 +1,84 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Primitive implements Word { + private final Runnable code; + private final String name; + private boolean visible = true; + + public Primitive(String name, Runnable code) { + this.code = code; + this.name = name; + } + + @Override + public void visible(boolean isVisible) { + this.visible = isVisible; + } + + @Override + public boolean visible() { + return visible; + } + + @Override + public void enter() { + code.run(); + } + + @Override + public String name() { + return name; + } + + @Override + public long longValue() { + throw new TypeMismatched(this,"long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this,"int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public Object value() { + return code; + } + + @Override + public String toString() { + return "xt_" + name; + } + + @Override + public int compareTo(Obj other) { + return other instanceof Primitive + ? name.compareTo(((Primitive) other).name) + : -1; + } +} diff --git a/types/Quot.java b/types/Quot.java new file mode 100644 index 0000000..3b9b5f6 --- /dev/null +++ b/types/Quot.java @@ -0,0 +1,73 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Quot implements Obj { + private final int address; + private final int stackFrame; + + public static Quot create(int stackFrame, int address) { + return new Quot(stackFrame, address); + } + + public Quot(int stackFrame, int address) { + this.address = address; + this.stackFrame = stackFrame; + } + + public Num address() { + return new Num(address); + } + + public Num stackFrame() { + return new Num(stackFrame); + } + + @Override + public long longValue() { + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Object value() { + throw new TypeMismatched(this, "value"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + @Override + public String toString() { + return "Quotation: " + address + ", " + stackFrame; + } +} diff --git a/types/Range.java b/types/Range.java new file mode 100644 index 0000000..0c888d7 --- /dev/null +++ b/types/Range.java @@ -0,0 +1,97 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.InterOpFailed; +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.util.Iterator; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Range implements Obj { + private RangeIterator iterator; + private final int from; + private final int to; + private final int by; + private int current; + + public static Range create(int by, int to, int from) { + return new Range(from, to, by); + } + + private Range(int from, int to, int by) { + if (by == 0) + throw new InterOpFailed("Invalid increment for range: " + by); + this.from = from; + this.to = to; + this.by = by; + this.current = from; + } + + @Override + public long longValue() { + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.NAN; + } + + @Override + public Str asStr() { + return new Str(toString()); + } + + public Iterator iterator() { + if (iterator == null) + iterator = new RangeIterator(); + return iterator; + } + + @Override + public String toString() { + return by == 1 + ? String.format("%d..%d (%d)", from, to, current) + : String.format("%d...%d (%d) by %d", from, to, current, by); + } + + @Override + public Object value() { + return iterator; + } + + @Override + public int compareTo(Obj o) { + return -1; + } + + public class RangeIterator implements Iterator { + @Override + public boolean hasNext() { + return by > 0 ? current <= to : current >= to; + } + + @Override + public Obj next() { + Num result = new Num(current); + current += by; + return result; + } + } +} diff --git a/types/Str.java b/types/Str.java new file mode 100644 index 0000000..e9bae85 --- /dev/null +++ b/types/Str.java @@ -0,0 +1,175 @@ +package com.vectron.fcl.types; + +import com.vectron.fcl.exceptions.TypeMismatched; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import static com.vectron.fcl.Fcl.STRICT; + +public class Str implements Obj, ArithmeticOperand { + private final String value; + + public Str(String value) { + this.value = value; + } + + @Override + public long longValue() { + throw new TypeMismatched(this, "long"); + } + + @Override + public int intValue() { + throw new TypeMismatched(this, "int"); + } + + @Override + public double doubleValue() { + throw new TypeMismatched(this, "double"); + } + + @Override + public boolean boolValue() { + throw new TypeMismatched(this, "bool"); + } + + @Override + public String value() { + return value; + } + + @Override + public Num asNum() { + if (STRICT) throw new TypeMismatched(this, "num"); + return Num.parse(value); + } + + @Override + public Str asStr() { + return this; + } + + @Override + public String toString() { + return "'" + value + "'"; + } + + public Str substr(int from, int to) { + return new Str(value.substring(from, to)); + } + + public int size() { + return value.length(); + } + + public Str at(Obj index) { + return new Str(Character.toString(value.charAt(index.intValue()))); + } + + public Lst split(String delimiter) { + Lst result = Lst.empty(); + for (String each : value.split(delimiter)) + result.append(new Str(each)); + return result; + } + + public Str upper() { + return new Str(value.toUpperCase()); + } + + public Str lower() { + return new Str(value.toLowerCase()); + } + + public Str trim() { + return new Str(value.trim()); + } + + public int indexOf(Obj s) { + return value.indexOf((String)s.value()); + } + + public Str replace(String olds, String news) { + return new Str(value.replaceAll(olds, news)); + } + + public Str concat(Obj str) { + return new Str(value + (String)str.value()); + } + + public Str reverse() { + return new Str(new StringBuilder(value).reverse().toString()); + } + + public Iterator iterator() { + return new Iterator() { + private int index = 0; + @Override + public boolean hasNext() { + return index < value.length(); + } + + @Override + public Str next() { + return new Str(Character.toString(value.charAt(index++))); + } + }; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Str str = (Str) o; + return value.equals(str.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public int compareTo(Obj other) { + return other instanceof Str + ? value.compareTo(((Str) other).value) + : -1; + } + + public Str format(List params) { + Object[] a = new Object[params.size()]; + for (int i = 0; i < params.size(); i++) { + a[i] = params.get(i).value(); + } + return new Str(String.format(value, a)); + } + + @Override + public Obj add(Obj other) { + throw new TypeMismatched("+", this, other); + } + + @Override + public Obj sub(Obj other) { + throw new TypeMismatched("+", this, other); + } + + @Override + public Obj mul(Obj other) { + if (other instanceof Num) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < other.intValue(); i++) { + result.append(value); + } + return new Str(result.toString()); + } + throw new TypeMismatched("*", this, other); + } + + @Override + public Obj div(Obj other) { + throw new TypeMismatched("+", this, other); + } +} diff --git a/types/Word.java b/types/Word.java new file mode 100644 index 0000000..4c388c1 --- /dev/null +++ b/types/Word.java @@ -0,0 +1,8 @@ +package com.vectron.fcl.types; + +public interface Word extends Obj { + void enter(); + String name(); + void visible(boolean isVisible); + boolean visible(); +}