-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2839739
Showing
23 changed files
with
1,708 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.idea | ||
javadoc/ | ||
out/ | ||
*.iml | ||
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 Johan Sannemo | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
![Spoooky!](https://github.com/jsannemo/spooky-vm/blob/master/spook.png?raw=true) | ||
|
||
So spooky. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>se.jsannemo</groupId> | ||
<artifactId>spooky-vm</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<maven.compiler.source>13</maven.compiler.source> | ||
<maven.compiler.target>${maven.compiler.source}</maven.compiler.target> | ||
</properties> | ||
<dependencies> | ||
<dependency> | ||
<groupId>com.google.truth</groupId> | ||
<artifactId>truth</artifactId> | ||
<version>1.0</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter</artifactId> | ||
<version>5.5.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.auto.value</groupId> | ||
<artifactId>auto-value-annotations</artifactId> | ||
<version>1.6.6</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>28.1-jre</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.8.1</version> | ||
<configuration> | ||
<compilerArgs> | ||
<arg>-XDcompilePolicy=simple</arg> | ||
<arg>-Xplugin:ErrorProne</arg> | ||
</compilerArgs> | ||
<annotationProcessors> | ||
<annotationProcessor>com.google.auto.value.processor.AutoValueProcessor</annotationProcessor> | ||
<annotationProcessor>com.google.auto.value.processor.AutoOneOfProcessor</annotationProcessor> | ||
</annotationProcessors> | ||
<annotationProcessorPaths> | ||
<path> | ||
<groupId>com.google.errorprone</groupId> | ||
<artifactId>error_prone_core</artifactId> | ||
<version>2.3.3</version> | ||
</path> | ||
<path> | ||
<groupId>com.google.auto.value</groupId> | ||
<artifactId>auto-value</artifactId> | ||
<version>1.6.6</version> | ||
</path> | ||
</annotationProcessorPaths> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>com.github.spotbugs</groupId> | ||
<artifactId>spotbugs-maven-plugin</artifactId> | ||
<version>3.1.12.2</version> | ||
<dependencies> | ||
<dependency> | ||
<groupId>com.github.spotbugs</groupId> | ||
<artifactId>spotbugs</artifactId> | ||
<version>4.0.0-beta4</version> | ||
</dependency> | ||
</dependencies> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.22.2</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-failsafe-plugin</artifactId> | ||
<version>2.22.2</version> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package se.jsannemo.spooky.compiler; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import se.jsannemo.spooky.vm.code.ExecutableParser; | ||
import se.jsannemo.spooky.vm.code.Instructions.Instruction; | ||
|
||
/** Assembler for VM instructions into binary code. */ | ||
public final class Assembler { | ||
|
||
/** | ||
* Assembles {@code instructions} into a binary format that can be parsed by {@link | ||
* ExecutableParser#fromBinary(byte[])}. | ||
*/ | ||
public static byte[] assemble(ImmutableList<Instruction> instructions) { | ||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
try { | ||
for (Instruction inst : instructions) { | ||
inst.writeBinary(bos); | ||
} | ||
} catch (IOException ioe) { | ||
throw new AssertionError("ByteArrayOutputStream shouldn't throw IOException"); | ||
} | ||
return bos.toByteArray(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package se.jsannemo.spooky.vm; | ||
|
||
/** The signature of an external call that should be supported in the VM. */ | ||
@FunctionalInterface | ||
public interface ExternCall { | ||
void call(SpookyVm vm) throws VmException; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package se.jsannemo.spooky.vm; | ||
|
||
import static com.google.common.base.Preconditions.checkState; | ||
|
||
import com.google.common.collect.ImmutableMap; | ||
import se.jsannemo.spooky.vm.code.Executable; | ||
import se.jsannemo.spooky.vm.code.Instructions.Add; | ||
import se.jsannemo.spooky.vm.code.Instructions.Const; | ||
import se.jsannemo.spooky.vm.code.Instructions.Div; | ||
import se.jsannemo.spooky.vm.code.Instructions.Extern; | ||
import se.jsannemo.spooky.vm.code.Instructions.Instruction; | ||
import se.jsannemo.spooky.vm.code.Instructions.Jump; | ||
import se.jsannemo.spooky.vm.code.Instructions.LessThan; | ||
import se.jsannemo.spooky.vm.code.Instructions.Move; | ||
import se.jsannemo.spooky.vm.code.Instructions.Mul; | ||
import se.jsannemo.spooky.vm.code.Instructions.Sub; | ||
|
||
/** | ||
* A virtual machine, executing parsed Spooky code. | ||
* | ||
* <p>The Spooky code is given to the VM in the form of a {@link Executable}. It will start executing | ||
* the first instruction in the text segment. | ||
*/ | ||
public final class SpookyVm { | ||
|
||
private final ImmutableMap<String, ExternCall> externs; | ||
private final int[] memory; | ||
/** | ||
* The value of the instruction pointer, with the index of the text instructions in the current | ||
* executable that should be executed. | ||
*/ | ||
private int ip; | ||
/** The executable that we are currently executing instructions in. */ | ||
private Executable curExecutable; | ||
|
||
private SpookyVm(Executable executable, ImmutableMap<String, ExternCall> externs, int memoryCells) { | ||
this.externs = externs; | ||
this.curExecutable = executable; | ||
this.ip = 0; | ||
this.memory = new int[memoryCells]; | ||
} | ||
|
||
/** | ||
* Executes the current instruction of the VM, advancing the instruction pointer afterwards. | ||
* | ||
* <p>If the instruction pointer points to an invalid instruction (i.e. one that is smaller or | ||
* larger than the amount of instructions in the current executable), nothing happens. | ||
* | ||
* @throws VmException if the instruction caused a run-time fault in the VM. | ||
*/ | ||
public void executeInstruction() throws VmException { | ||
// Halt VM in case if an out-of-bounds instruction. | ||
if (ip < 0 || ip >= curExecutable.text().size()) { | ||
throw new VmException("Instruction pointer out-of-bounds"); | ||
} | ||
Instruction ins = curExecutable.text().get(ip++); | ||
checkState(ins.isExecutable()); | ||
if (ins instanceof Move) { | ||
Move mov = (Move) ins; | ||
setM(getM(mov.target()), getM(getM(mov.source()))); | ||
} | ||
if (ins instanceof Const) { | ||
Const cnst = (Const) ins; | ||
setM(cnst.target(), cnst.value()); | ||
} | ||
if (ins instanceof Add) { | ||
Add add = (Add) ins; | ||
setM(add.target(), getM(add.op1()) + getM(add.op2())); | ||
} | ||
if (ins instanceof Sub) { | ||
Sub sub = (Sub) ins; | ||
setM(sub.target(), getM(sub.op1()) - getM(sub.op2())); | ||
} | ||
if (ins instanceof Mul) { | ||
Mul mul = (Mul) ins; | ||
setM(mul.target(), getM(mul.op1()) * getM(mul.op2())); | ||
} | ||
if (ins instanceof Div) { | ||
Div div = (Div) ins; | ||
setM(div.target(), getM(div.op1()) / getM(div.op2())); | ||
} | ||
if (ins instanceof LessThan) { | ||
LessThan lt = (LessThan) ins; | ||
setM(lt.target(), getM(lt.op1()) < getM(lt.op2()) ? 1 : 0); | ||
} | ||
if (ins instanceof Jump) { | ||
Jump jmp = (Jump) ins; | ||
if (getM(jmp.flag()) == 0) { | ||
ip = getM(jmp.addr()); | ||
} | ||
} | ||
if (ins instanceof Extern) { | ||
Extern ext = (Extern) ins; | ||
callExtern(ext.name()); | ||
} | ||
} | ||
|
||
private void callExtern(String extern) throws VmException { | ||
if (!externs.containsKey(extern)) { | ||
throw new VmException("Attempted to call non-existent extern " + extern); | ||
} | ||
externs.get(extern).call(this); | ||
} | ||
|
||
/** | ||
* Returns the memory at position {@code pos}. | ||
* | ||
* <p>If {@code pos < 0}, the index {@code -(pos + 1)} of the data segment is returned. Otherwise, | ||
* the position {@code pos} from the main memory is returned. | ||
* | ||
* @throws VmException if {@code pos} is invalid. | ||
*/ | ||
public int getM(int pos) throws VmException { | ||
if (0 <= pos && pos < memory.length) { | ||
return memory[pos]; | ||
} | ||
if (-this.curExecutable.data().size() <= pos && pos < 0) { | ||
return this.curExecutable.data().get(-(pos + 1)); | ||
} | ||
throw new VmException("Memory position " + pos + " is out of bounds"); | ||
} | ||
|
||
/** | ||
* Sets the memory at position {@code pos} in the main memory. | ||
* | ||
* @throws VmException if {@code pos} is invalid. | ||
*/ | ||
public void setM(int pos, int value) throws VmException { | ||
if (pos < 0 || pos >= memory.length) { | ||
throw new VmException("Memory position " + pos + " is out of bounds"); | ||
} | ||
memory[pos] = value; | ||
} | ||
|
||
/** Returns a new builder for {@link SpookyVm} instances. */ | ||
public static Builder newBuilder(Executable executable) { | ||
return new Builder(executable); | ||
} | ||
|
||
/** A builder for {@link SpookyVm} instances. */ | ||
public static class Builder { | ||
private final Executable executable; | ||
private final ImmutableMap.Builder<String, ExternCall> externBuilder = ImmutableMap.builder(); | ||
private int memoryCells; | ||
|
||
private Builder(Executable executable) { | ||
this.executable = executable; | ||
memoryCells = 0; | ||
} | ||
|
||
/** Make available an external call named {@code name} invoking {@code callback} when called. */ | ||
public Builder addExtern(String name, ExternCall callback) { | ||
externBuilder.put(name, callback); | ||
return this; | ||
} | ||
|
||
/** Add the external calls that the standard library provides. */ | ||
public Builder addStdLib() { | ||
externBuilder.put("random", StdLib::random); | ||
externBuilder.put("print", StdLib::print); | ||
return this; | ||
} | ||
|
||
/** Set the memory size in (integer-sized) cells. */ | ||
public Builder setMemorySize(int memoryCells) { | ||
this.memoryCells = memoryCells; | ||
return this; | ||
} | ||
|
||
public SpookyVm build() { | ||
return new SpookyVm(executable, externBuilder.build(), memoryCells); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package se.jsannemo.spooky.vm; | ||
|
||
import java.util.Random; | ||
|
||
/** | ||
* {@link StdLib} provides utility methods to implement extern functions with the same calling | ||
* convention as the Spooky standard library. | ||
*/ | ||
public final class StdLib { | ||
|
||
private static final int STACK_PTR = 0; | ||
private static final Random RANDOM = new Random(); | ||
|
||
private StdLib() {} | ||
|
||
/** | ||
* Pops the last argument for the function from the stack and returns the value. | ||
* | ||
* @throws VmException if the pop causes the stack to underflow. | ||
*/ | ||
public static int popArg(SpookyVm vm) throws VmException { | ||
int sp = vm.getM(STACK_PTR); | ||
int val = vm.getM(sp - 1); | ||
vm.setM(STACK_PTR, sp - 1); | ||
return val; | ||
} | ||
|
||
/** | ||
* Pushes a return value for a function to the stack. | ||
* | ||
* @throws VmException if the push causes the stack to overflow. | ||
*/ | ||
public static void pushArg(SpookyVm vm, int val) throws VmException { | ||
int sp = vm.getM(STACK_PTR); | ||
vm.setM(sp, val); | ||
vm.setM(STACK_PTR, sp + 1); | ||
} | ||
|
||
static void random(SpookyVm vm) throws VmException { | ||
pushArg(vm, RANDOM.nextInt()); | ||
} | ||
|
||
static void print(SpookyVm vm) throws VmException { | ||
System.out.print((char) popArg(vm)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package se.jsannemo.spooky.vm; | ||
|
||
/** Exception signifying a run-time error in the program executing in the VM. */ | ||
public final class VmException extends Throwable { | ||
VmException(String s) { | ||
super(s); | ||
} | ||
} |
Oops, something went wrong.