diff --git a/.gitignore b/.gitignore index a1c2a23..7372d5e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,8 @@ *.tar.gz *.rar +#eclipse .project and .classpath +.project +.classpath # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/README.md b/README.md index acd2875..0481f29 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,14 @@ Yes, I know what you're thinking. Java is so much NOT the right tool for the job So, Java it is. I haven't used lex/yacc, let alone better parser tools, in ages. Also this is the very first time I'm writing an interpreter. My point is: the code is crap and I never intended otherwise. It is also probably buggy and I think it doesn't follow the full specs. I wrote this in one single evening, probably no more than 4 or 5 hours in total. This is absolutely NOT intended for professional use. But the language creator clearly specified that if you write even a single line of Rockstar then you are a full-fledged Rockstar developer. And this crappy interpreter can be used to write a (probably faulty) Hello world. YOU CAN BE A ROCKSTAR DEVELOPER TOO! JUST USE THIS INTERPRETER! BESTELLEN SIE JETZT! Also, I can't promise that I will keep this implementation up to date, but if you really want it, as long as you ask nicely and I have the free time (this might be tough depending on the time of the year), I don't mind spending some time maintaining the project :). -I will probably provide further code examples. Despite my dislike for dynamically typed languages, I have to admit that writing snippets of code in Rockstar is fun, at least for the few first times, if only because you can get very creative and lyrical. It's paradoxical: the natural obfuscation of the language says "only for especially nerdy programmers", but the room for imagination says "people with pure humanities background are very welcome". I guess that Rockstar can be an inclusive language: Bonnie Tyler, Nathan East or Freddie Mercury are just as welcome as Rudolf Schenker. There is room for everyone in the land of rock! \ No newline at end of file +I will probably provide further code examples. Despite my dislike for dynamically typed languages, I have to admit that writing snippets of code in Rockstar is fun, at least for the few first times, if only because you can get very creative and lyrical. It's paradoxical: the natural obfuscation of the language says "only for especially nerdy programmers", but the room for imagination says "people with pure humanities background are very welcome". I guess that Rockstar can be an inclusive language: Bonnie Tyler, Nathan East or Freddie Mercury are just as welcome as Rudolf Schenker. There is room for everyone in the land of rock! + +07/08/2018 Added functionality to work with the example provided with the Rockstar specs. +*"LOWER OR EQUAL" and "HIGHER OR EQUAL" comparisons. +*Add, Substract, Multiply and Divide instructions added. +*NoOp instrucion added for comments. +*Added support to complex assignments (ie: x = y + z). +*Fixed Until and If blocks (and While too probably). +*Added "debug" flag and toString() methods to all instructions. +*Added auto conversion of inputs to numbers instead of only strings. +*Added pretty-print for integers. diff --git a/src/main/java/com/rockstar/Parser.java b/src/main/java/com/rockstar/Parser.java index ca0db02..9c79607 100644 --- a/src/main/java/com/rockstar/Parser.java +++ b/src/main/java/com/rockstar/Parser.java @@ -18,13 +18,13 @@ import com.rockstar.internal.Function; import com.rockstar.internal.Instruction; import com.rockstar.internal.conditions.CompositeCondition; -import com.rockstar.internal.instructions.Assignment; import com.rockstar.internal.instructions.BlockInstruction; import com.rockstar.internal.instructions.Conditional; import com.rockstar.internal.instructions.Decrement; import com.rockstar.internal.instructions.Increment; import com.rockstar.internal.instructions.Input; import com.rockstar.internal.instructions.Loop; +import com.rockstar.internal.instructions.NoOp; import com.rockstar.internal.instructions.Output; import com.rockstar.internal.instructions.SpecialBlockInstruction; import com.rockstar.internal.instructions.SubtractInstruction; @@ -34,6 +34,7 @@ public class Parser { public final static List COMMON_VARIABLE_NAMES=Arrays.asList("the ","my ","your ","The ","My ","Your "); + public static boolean debug; public static String parseAsCommonVariableName(String name) { for (String prefix:COMMON_VARIABLE_NAMES) { @@ -67,9 +68,9 @@ public static Program parse(String fileName) throws IOException { public static Program parse(PeekingIterator lines) { Map functions=new HashMap<>(); List instructions=new ArrayList<>(); - while (lines.hasNext()) { + while (lines.hasNext()){ String line=lines.next().trim(); - if (line.isEmpty()) continue; + if (line.isEmpty() ) continue; FunctionHeaderMatcher functionMatcher=new FunctionHeaderMatcher(line); if (functionMatcher.isFunction()) { Function fun=parseFunction(functionMatcher,lines); @@ -88,13 +89,16 @@ private static Function parseFunction(FunctionHeaderMatcher header,PeekingIterat if (line.startsWith("Give back ")) { String rhs=line.substring(10); // "Give back ".length()=10. return new Function(header.getArgNames(),rhs,instructions); - } else instructions.add(parseInstruction(line,lines)); + }else instructions.add(parseInstruction(line,lines)); } } private static Instruction parseInstruction(String line,PeekingIterator lines) { // This requires the iterator because maybe it's a block instruction. - if (line.startsWith("Put ")) return parseAsPutInto(line); + if (line.startsWith("(") && line.endsWith(")")) return new NoOp(); + else if (line.equals("Continue")||line.equalsIgnoreCase("take it to the top")) return SpecialBlockInstruction.CONTINUE; + else if (line.equals("Break")||line.equalsIgnoreCase("break it down!")) return SpecialBlockInstruction.BREAK; + else if (line.startsWith("Put ")) return parseAsPutInto(line); else if (line.startsWith("Take ")) return parseAsTakeFrom(line); else if (line.startsWith("Build ")) return parseAsBuildUp(line); else if (line.startsWith("Knock ")) return parseAsKnockDown(line); @@ -103,30 +107,31 @@ private static Instruction parseInstruction(String line,PeekingIterator else if (line.startsWith("Whisper ")) return new Output(line.substring(8)); else if (line.startsWith("Scream ")) return new Output(line.substring(7)); else if (line.startsWith("Listen to ")) return new Input(parseVariableName(line.substring(10))); - else if (line.equals("Continue")||line.equalsIgnoreCase("take it to the top")) return SpecialBlockInstruction.CONTINUE; - else if (line.equals("Break")||line.equalsIgnoreCase("break it down!")) return SpecialBlockInstruction.BREAK; else if (line.startsWith("If ")) return parseIf(line,lines); else if (line.startsWith("While ")) return parseWhile(line,lines); else if (line.startsWith("Until ")) return parseUntil(line,lines); - AssignmentParser assignment=new AssignmentParser(line); - if (assignment.isAssignment()) return assignment.createInstruction(); - throw new RockstarException("Unknown sentence: "+line+"."); + return parseAssignment(line); } - private static Instruction parseIf(String line,PeekingIterator lines) { - List instructions=new ArrayList<>(); - Condition cond=parseCondition(line.substring(3)); - instructions.add(parseInstruction(lines.next(),lines)); - for (;;) { - String newLine=lines.peek().trim(); - if (newLine.startsWith("And ")) { - instructions.add(parseInstruction(newLine.substring(4),lines)); - lines.next(); // To advance the line we just read. - } - else break; - } - return new Conditional(cond,new BlockInstruction(instructions)); + private static Instruction parseAssignment(String line) { + AssignmentParser assignment = new AssignmentParser(line); + if (assignment.isAssignment()) { + return assignment.createInstruction(); + } + throw new RockstarException("Unknown sentence: " + line + "."); + } + + private static Instruction parseIf(String line, PeekingIterator lines) { + List instructions = new ArrayList<>(); + Condition cond = parseCondition(line.substring(3)); + while (lines.hasNext()&&!lines.peek().trim().isEmpty()) { + instructions.add(parseInstruction(lines.next(), lines)); } + if(lines.hasNext()){ + lines.next(); // skip blank line + } + return new Conditional(cond, new BlockInstruction(instructions)); + } private static Instruction parseWhile(String line,PeekingIterator lines) { Condition condition=parseCondition(line.substring(6)); @@ -163,27 +168,28 @@ private static Condition parseSimpleCondition(String cond) { return ConditionParser.parseCondition(cond); } - private static BlockInstruction parseLoop(PeekingIterator lines) { - List instructions=new ArrayList<>(); - for (;;) { - String line=lines.next().trim(); - if (line.isEmpty()) continue; - if (line.equals("End")||line.equals("And around we go")) return new BlockInstruction(instructions); - else instructions.add(parseInstruction(line,lines)); - } - + private static BlockInstruction parseLoop(PeekingIterator lines) { + List instructions = new ArrayList<>(); + while (lines.hasNext() + && !(lines.peek().isEmpty() || lines.peek().equals("End") || lines.peek().equals("And around we go"))) { + instructions.add(parseInstruction(lines.next(), lines)); + } + if(lines.hasNext()){ + lines.next(); //skip blank } + return new BlockInstruction(instructions); + + } private static Instruction parseAsPutInto(String line) { String[] split=line.substring(4).split(" into "); // The 4 is because we remove "put ". - if (split.length!=2) throw new RockstarException("Malformed assignment instruction."); - String varName=parseVariableName(split[1]); - return new Assignment(varName,split[0]); + if (split.length!=2) throw new RockstarException("Malformed put assignment instruction: " + line); + return parseAssignment(split[1] + " is " + split[0]); } private static Instruction parseAsTakeFrom(String line) { String[] split=line.substring(5).split(" from "); // The 5 is because we remove "take ". - if (split.length!=2) throw new RockstarException("Malformed assignment instruction."); + if (split.length!=2) throw new RockstarException("Malformed take assignment instruction: " + line); String varName=parseVariableName(split[1]); return new SubtractInstruction(varName,split[0]); } @@ -216,7 +222,8 @@ public static void main(String[] args) { } try { Program program=parse(args[0]); - program.run(); + debug = args.length>1; + program.run(debug); } catch (IOException exc) { System.out.println("Error reading the script: "+exc.getMessage()+"."); } catch (RockstarException exc) { diff --git a/src/main/java/com/rockstar/Program.java b/src/main/java/com/rockstar/Program.java index 012a02a..b5d968c 100644 --- a/src/main/java/com/rockstar/Program.java +++ b/src/main/java/com/rockstar/Program.java @@ -134,7 +134,12 @@ private Optional getAsFunctionCall(String rhs) { return Optional.of(function.call(arguments, functions)); } - public void run() { - for (Instruction instr:instructions) instr.run(this); + public void run(boolean debug) { + for (Instruction instr : instructions) { + if(debug){ + System.out.println(instr.toString()); + } + instr.run(this); } + } } diff --git a/src/main/java/com/rockstar/internal/Comparison.java b/src/main/java/com/rockstar/internal/Comparison.java index 9d4081d..aa042de 100644 --- a/src/main/java/com/rockstar/internal/Comparison.java +++ b/src/main/java/com/rockstar/internal/Comparison.java @@ -22,6 +22,20 @@ public boolean compare(Value lhs, Value rhs) { double r=rhs.getValue(Double.class); return l>r; } + },HIGHER_OR_EQUAL { + @Override + public boolean compare(Value lhs, Value rhs) { + double l=lhs.getValue(Double.class); + double r=rhs.getValue(Double.class); + return l>=r; + } + },LOWER_OR_EQUAL { + @Override + public boolean compare(Value lhs, Value rhs) { + double l=lhs.getValue(Double.class); + double r=rhs.getValue(Double.class); + return l<=r; + } }; public final boolean compare(Program state,String lhs,String rhs) { diff --git a/src/main/java/com/rockstar/internal/Function.java b/src/main/java/com/rockstar/internal/Function.java index b741144..882569c 100644 --- a/src/main/java/com/rockstar/internal/Function.java +++ b/src/main/java/com/rockstar/internal/Function.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; +import com.rockstar.Parser; import com.rockstar.Program; import com.rockstar.RockstarException; @@ -22,7 +23,7 @@ public Value call(List arguments,Map functions) { int N=argumentNames.size(); if (N!=arguments.size()) throw new RockstarException("Expected "+N+" arguments, found "+arguments.size()+"."); for (int i=0;i T getValue(Class theClass) { try { diff --git a/src/main/java/com/rockstar/internal/conditions/ComparisonCondition.java b/src/main/java/com/rockstar/internal/conditions/ComparisonCondition.java index 24c9d7c..a5508bb 100644 --- a/src/main/java/com/rockstar/internal/conditions/ComparisonCondition.java +++ b/src/main/java/com/rockstar/internal/conditions/ComparisonCondition.java @@ -19,4 +19,21 @@ public ComparisonCondition(String lhs,String rhs,Comparison comp) { public boolean evaluate(Program state) { return comp.compare(state,lhs,rhs); } + + @Override + public String toString(){ + switch(comp){ + case EQUAL: + return "Comparison " + lhs + " EQUAL " + rhs; + case LOWER: + return "Comparison " + lhs + " LOWER THAN " + rhs; + case HIGHER: + return "Comparison " + lhs + " HIGHER THAN " + rhs; + case HIGHER_OR_EQUAL: + return "Comparison " + lhs + " HIGHER OR EQUAL THAN " + rhs; + case LOWER_OR_EQUAL: + return "Comparison " + lhs + " LOWER OR EQUAL THAN " + rhs; + } + return "Comparison Undefined?"; + } } diff --git a/src/main/java/com/rockstar/internal/conditions/CompositeCondition.java b/src/main/java/com/rockstar/internal/conditions/CompositeCondition.java index 9d541e2..70ad706 100644 --- a/src/main/java/com/rockstar/internal/conditions/CompositeCondition.java +++ b/src/main/java/com/rockstar/internal/conditions/CompositeCondition.java @@ -47,4 +47,20 @@ public boolean evaluate(Program state) { Collection conditionals=Collections2.transform(simpleConditions, (Condition cond)->cond.evaluate(state)); return condType.isTrue(conditionals); } + + @Override + public String toString() { + String rta = simpleConditions.get(0).toString(); + for (int i = 1; i ALIASES=Arrays.asList(" is "," was "," were "," says "); - private String lhs; + private String varName; private String rhs; private boolean isCorrect; @@ -18,7 +24,7 @@ public AssignmentParser(String line) { for (String alias:ALIASES) { String[] split=line.split(alias); if (split.length!=2) continue; - lhs=split[0]; + varName = Parser.parseVariableName(split[0]); rhs=split[1]; isCorrect=true; return; @@ -30,8 +36,44 @@ public boolean isAssignment() { return isCorrect; } + //FIXME refactor... this is awfull public Instruction createInstruction() { - String varName=Parser.parseVariableName(lhs); - return new Assignment(varName,rhs); + if(rhs.contains(" plus ") || rhs.contains(" with ")){ + List instructions = new ArrayList(); + String part=rhs.contains(" plus ")?rhs.split(" plus ")[0]:rhs.split(" with ")[0]; + instructions.add(new Assignment(varName, part)); + part=rhs.contains(" plus ")?rhs.split(" plus ")[1]:rhs.split(" with ")[1]; + instructions.add(new AddInstruction(varName, part)); + return new BlockInstruction(instructions); + } else if(rhs.contains(" minus ") || rhs.contains(" without ")){ + List instructions = new ArrayList(); + String part=rhs.contains(" minus ")?rhs.split(" minus ")[0]:rhs.split(" without ")[0]; + instructions.add(new Assignment(varName, part)); + part=rhs.contains(" minus ")?rhs.split(" minus ")[1]:rhs.split(" without ")[1]; + instructions.add(new SubtractInstruction(varName, part)); + return new BlockInstruction(instructions); + } else if(rhs.contains(" times ") || rhs.contains(" of ")){ + List instructions = new ArrayList(); + String part=rhs.contains(" times ")?rhs.split(" times ")[0]:rhs.split(" of ")[0]; + instructions.add(new Assignment(varName, part)); + part=rhs.contains(" times ")?rhs.split(" times ")[1]:rhs.split(" of ")[1]; + instructions.add(new MultiplyInstruction(varName, part)); + return new BlockInstruction(instructions); + } else if (rhs.contains(" over ")){ + List instructions = new ArrayList(); + instructions.add(new Assignment(varName, rhs.split(" over ")[0])); + instructions.add(new DivideInstruction(varName, rhs.split(" over ")[1])); + return new BlockInstruction(instructions); + } else{ + return new Assignment(varName,rhs); + } + } + + public String getRHS() { + return rhs; + } + + public String getVarName() { + return varName; } } diff --git a/src/main/java/com/rockstar/parser/ConditionParser.java b/src/main/java/com/rockstar/parser/ConditionParser.java index 0933b06..258910d 100644 --- a/src/main/java/com/rockstar/parser/ConditionParser.java +++ b/src/main/java/com/rockstar/parser/ConditionParser.java @@ -16,7 +16,9 @@ public class ConditionParser { private final static List IS_NOT_IDS=Arrays.asList(" is not "," ain't "); private final static List IS_IDS=Arrays.asList(" is "); private final static Set HIGHER_IDS=ImmutableSet.of("higher","greater","bigger","stronger"); + private final static Set HIGHER_OR_EQUAL_IDS=ImmutableSet.of("as high","as great","as big","as strong"); private final static Set LOWER_IDS=ImmutableSet.of("lower","less","smaller","weaker"); + private final static Set LOWER_OR_EQUAL_IDS=ImmutableSet.of("as low","as little","as small","as weak"); public static Condition parseCondition(String str) { // "Is not" must be tried before "is" because "is not" includes "is". @@ -25,15 +27,29 @@ public static Condition parseCondition(String str) { return new ValueCondition(str); } - public static Condition parseCondition(String str,String id) { - String[] split=str.split(id); - if (split.length!=2) throw new RockstarException("Malformed condition."); - String lhs=split[0]; - if (split[1].contains(" than ")) { - String[] resplit=split[1].split(" than "); - if (HIGHER_IDS.contains(resplit[0])) return new ComparisonCondition(lhs,resplit[1],Comparison.HIGHER); - else if (LOWER_IDS.contains(resplit[0])) return new ComparisonCondition(lhs,resplit[1],Comparison.LOWER); - else throw new RockstarException("Malformed condition."); - } else return new ComparisonCondition(lhs,split[1],Comparison.EQUAL); - } + public static Condition parseCondition(String str, String id) { + String[] split = str.split(id); + if (split.length != 2) + throw new RockstarException("Malformed condition."); + String lhs = split[0]; + if (split[1].contains(" than ")) { + String[] resplit = split[1].split(" than "); + if (HIGHER_IDS.contains(resplit[0])) + return new ComparisonCondition(lhs, resplit[1], Comparison.HIGHER); + else if (LOWER_IDS.contains(resplit[0])) + return new ComparisonCondition(lhs, resplit[1], Comparison.LOWER); + else + throw new RockstarException("Malformed condition."); + } else if(split[1].contains(" as ")){ + String[] resplit = split[1].split(" as "); + if (HIGHER_OR_EQUAL_IDS.contains(resplit[0])) + return new ComparisonCondition(lhs, resplit[1], Comparison.HIGHER_OR_EQUAL); + else if (LOWER_OR_EQUAL_IDS.contains(resplit[0])) + return new ComparisonCondition(lhs, resplit[1], Comparison.LOWER_OR_EQUAL); + else + throw new RockstarException("Malformed condition."); + + } else + return new ComparisonCondition(lhs, split[1], Comparison.EQUAL); + } }