Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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*
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
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.
81 changes: 44 additions & 37 deletions src/main/java/com/rockstar/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,6 +34,7 @@

public class Parser {
public final static List<String> 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) {
Expand Down Expand Up @@ -67,9 +68,9 @@ public static Program parse(String fileName) throws IOException {
public static Program parse(PeekingIterator<String> lines) {
Map<String,Function> functions=new HashMap<>();
List<Instruction> 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);
Expand All @@ -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<String> 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);
Expand All @@ -103,30 +107,31 @@ private static Instruction parseInstruction(String line,PeekingIterator<String>
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<String> lines) {
List<Instruction> 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<String> lines) {
List<Instruction> 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<String> lines) {
Condition condition=parseCondition(line.substring(6));
Expand Down Expand Up @@ -163,27 +168,28 @@ private static Condition parseSimpleCondition(String cond) {
return ConditionParser.parseCondition(cond);
}

private static BlockInstruction parseLoop(PeekingIterator<String> lines) {
List<Instruction> 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<String> lines) {
List<Instruction> 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]);
}
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/com/rockstar/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ private Optional<Value> 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);
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/rockstar/internal/Comparison.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/rockstar/internal/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.Map;

import com.rockstar.Parser;
import com.rockstar.Program;
import com.rockstar.RockstarException;

Expand All @@ -22,7 +23,7 @@ public Value call(List<Value> arguments,Map<String,Function> functions) {
int N=argumentNames.size();
if (N!=arguments.size()) throw new RockstarException("Expected "+N+" arguments, found "+arguments.size()+".");
for (int i=0;i<N;++i) program.assignVariable(argumentNames.get(i),arguments.get(i));
program.run();
program.run(Parser.debug);
return program.evaluate(returnValueExpr);
}
}
28 changes: 15 additions & 13 deletions src/main/java/com/rockstar/internal/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,22 @@ public Content getContentType() {
}

@Override
public String toString() {
switch (contentType) {
case NUMBER:
case STRING:
case BOOLEAN:
return value.toString();
case NULL_TYPE:
return "null";
case MYSTERIOUS:
return "mysterious";
default:
return "";
}
public String toString() {
switch (contentType) {
case NUMBER:
if(value.toString().endsWith(".0"))
return value.toString().substring(0, value.toString().length()-2);
case STRING:
case BOOLEAN:
return value.toString();
case NULL_TYPE:
return "null";
case MYSTERIOUS:
return "mysterious";
default:
return "";
}
}

public <T> T getValue(Class<T> theClass) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,20 @@ public boolean evaluate(Program state) {
Collection<Boolean> 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 <simpleConditions.size(); i++) {
switch (condType) {
case AND:
rta += " AND " + simpleConditions.get(i).toString();
break;
case OR:
rta += " OR " + simpleConditions.get(i).toString();
break;
}
}
return rta;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public InvertedCondition(Condition baseCondition) {
public boolean evaluate(Program state) {
return !baseCondition.evaluate(state);
}

@Override
public String toString(){
return "NOT " + baseCondition.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ public boolean evaluate(Program state) {
return false;
}
}

@Override
public String toString(){
return "ValueCondition " + expression;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.rockstar.internal.instructions;

import com.rockstar.Program;
import com.rockstar.internal.Instruction;
import com.rockstar.internal.Value;

public class AddInstruction implements Instruction {
private final String variableName;
private final String rhs;

public AddInstruction(String variableName, String rhs){
this.variableName = variableName;
this.rhs = rhs;
}

@Override
public void run(Program state) {
double number1=state.evaluate(rhs).getValue(Double.class);
double number2=state.getVariable(variableName).getValue(Double.class);
state.assignVariable(variableName,Value.createNumber(number1 + number2));
}

@Override
public String toString(){
return variableName + " + " + rhs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public void run(Program state) {
Value result=state.evaluate(rhs);
state.assignVariable(variableName,result);
}

@Override
public String toString(){
return "Assignment " + variableName + " = " + rhs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ public BlockInstruction(List<Instruction> instructions) {
public void run(Program state) {
for (Instruction instr:instructions) instr.run(state);
}

@Override
public String toString(){
String rta = "Block START\n";
for(Instruction i : instructions){
rta += i.toString()+"\n";
}
return rta+"Block END";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public Conditional(Condition condition,BlockInstruction block) {
public void run(Program state) {
if (condition.evaluate(state)) block.run(state);
}

@Override
public String toString(){
return "Condition " + condition + " BLOCK \n" + block;
}
}
Loading