Skip to content

Commit

Permalink
[#201] First draft on SSEM ANTLR4 parser
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed May 3, 2021
1 parent 763acfd commit 65191b3
Show file tree
Hide file tree
Showing 11 changed files with 386 additions and 5 deletions.
2 changes: 1 addition & 1 deletion plugins/compiler/as-ssem/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
src/main/java/net/emustudio/plugins/compiler/ssem/LexerImpl.java
*.tokens
40 changes: 37 additions & 3 deletions plugins/compiler/as-ssem/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ import org.apache.tools.ant.filters.ReplaceTokens

plugins {
id 'java'
id 'idea'
id 'org.xbib.gradle.plugin.jflex' version "1.5.0"
id "com.github.andrescv.jcup" version "1.0"
id 'antlr'
}

dependencies {
antlr "org.antlr:antlr4:4.9.2"
compile "org.antlr:antlr4-runtime:4.9.2"

implementation libs.emuLib
implementation libs.slf4JApi
implementation libs.jcipAnnotations
Expand All @@ -37,9 +42,31 @@ dependencies {
testImplementation libs.slf4JSimple
}

sourceSets.main.java.srcDirs = [
"${buildDir}/generated-sources/jflex", "${buildDir}/generated-sources/cup", 'src/main/java'
]
generateGrammarSource {
maxHeapSize = "128m"
arguments += ['-package', 'net.emustudio.plugins.compiler.ssem', '-visitor', '-no-listener']
outputDirectory = file("${buildDir}/generated-src/antlr/main/net/emustudio/plugins/compiler/ssem")
}
compileJava.dependsOn generateGrammarSource

sourceSets {
generated {
java.srcDir "${buildDir}/generated-src/antlr/main"
}
main {
java.srcDirs = [
"${buildDir}/generated-sources/jflex", "${buildDir}/generated-sources/cup",
'src/main/java', "${buildDir}/generated-src/antlr/main"
]
}
}
compileJava.source sourceSets.generated.java, sourceSets.main.java

idea {
module {
sourceDirs += file("build/generated-src/antlr")
}
}

jflex {
no_backup = true
Expand Down Expand Up @@ -82,3 +109,10 @@ copy {
from('src/main/scripts')
into "$buildDir/libs/scripts"
}


test {
testLogging {
events "passed", "skipped", "failed"
}
}
57 changes: 57 additions & 0 deletions plugins/compiler/as-ssem/src/main/antlr/SSEMLexer.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
lexer grammar SSEMLexer;

tokens {
COMMENT,
EOL,
JMP, JRP, JPR, JMR, LDN, STO, SUB, CMP, SKN, STP, HLT,
START, NUM, BNUM,
NUMBER, HEXNUMBER
}

WS : (' ' | '\t') -> skip;
COMMENT: ('//' | '--' | ';' | '#' ) ~[\r\n]*;
EOL: '\r'? '\n';

fragment J: [jJ];
fragment M: [mM];
fragment P: [pP];
fragment R: [rR];
fragment L: [lL];
fragment D: [dD];
fragment N: [nN];
fragment S: [sS];
fragment T: [tT];
fragment O: [oO];
fragment U: [uU];
fragment B: [bB];
fragment C: [cC];
fragment K: [kK];
fragment H: [hH];
fragment A: [aA];
fragment I: [iI];

// reserved
JMP: J M P;
JRP: J R P;
JPR: J P R;
JMR: J M R;
LDN: L D N;
STO: S T O;
SUB: S U B;
CMP: C M P;
SKN: S K N;
STP: S T P;
HLT: H L T;

// preprocessor
START: S T A R T;
NUM: N U M;
BNUM: ((B N U M) | (B I N S)) -> pushMode(BIN);

// literals
NUMBER: [\-]? [0-9]+;
HEXNUMBER: [\-]? ('0x'|'0X') [0-9a-fA-F]+;

mode BIN;
BWS : (' ' | '\t') -> skip;
BinaryNumber: [01]+ -> popMode;
29 changes: 29 additions & 0 deletions plugins/compiler/as-ssem/src/main/antlr/SSEMParser.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
parser grammar SSEMParser;

options {
tokenVocab = SSEMLexer;
}

start:
(line EOL line)* EOF
| line EOF
;

line:
linenumber=(NUMBER|HEXNUMBER) command=statement? comment
| comment
;

comment: COMMENT? ;

statement:
instr=START
| instr=JPR operand=(NUMBER|HEXNUMBER)
| instr=LDN operand=(NUMBER|HEXNUMBER)
| instr=STO operand=(NUMBER|HEXNUMBER)
| instr=SUB operand=(NUMBER|HEXNUMBER)
| instr=CMP
| instr=STP
| instr=NUM operand=(NUMBER|HEXNUMBER)
| instr=BNUM operand=BinaryNumber
;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
package net.emustudio.plugins.compiler.ssem;

public class CompileException extends Exception {
public class CompileException extends RuntimeException {

public CompileException(String message) {
super(message);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.emustudio.plugins.compiler.ssem.ast;

import net.emustudio.emulib.runtime.helpers.NumberUtils;
import net.emustudio.plugins.compiler.ssem.CompileException;
import net.emustudio.plugins.compiler.ssem.SSEMParser;

import java.nio.ByteBuffer;

public class CodeGenerator {
private final ByteBuffer code = ByteBuffer.allocate(33 * 4); // one is for start line

public ByteBuffer generateCode(Program program) {
code.position(0);
code.putInt(program.getStartLine());

program.forEach((line, instruction) -> {
code.position(4 * line);
if (instruction.tokenType == SSEMParser.BNUM || instruction.tokenType == SSEMParser.NUM) {
code.putInt(instruction.operand);
} else {
writeInstruction(instruction.getOpcode(), instruction.operand);
}
});
return code.clear();
}

private void writeInstruction(int opcode, int operand) {
if (operand < 0 || operand > 31) {
throw new CompileException("Operand must be between <0, 31>; it was " + operand);
}

byte address = (byte)(NumberUtils.reverseBits((byte)(operand & 0xFF), 8) & 0xF8);
// 5 bits address + 3 empty bits
code.put(address);
// next: 5 empty bits + 3 bit instruction
code.put((byte)(opcode & 0xFF));
// 16 empty bits
code.put((byte)0);
code.put((byte)0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.emustudio.plugins.compiler.ssem.ast;

import net.emustudio.plugins.compiler.ssem.CompileException;
import net.emustudio.plugins.compiler.ssem.SSEMParser;
import net.jcip.annotations.Immutable;

import java.util.Map;

@Immutable
public class Instruction {
private final static Map<Integer, Byte> OPCODES = Map.of(
SSEMParser.JMP, (byte)0, // 000
SSEMParser.SUB, (byte)1, // 001
SSEMParser.LDN, (byte)2, // 010
SSEMParser.CMP, (byte)3, // 011
SSEMParser.JRP, (byte)4, // 100
SSEMParser.STO, (byte)6, // 110
SSEMParser.STP, (byte)7, // 111,
SSEMParser.BNUM, (byte)0,
SSEMParser.NUM, (byte)0
);

public final int tokenType;
public final int operand;

public Instruction(int tokenType) {
if (!OPCODES.containsKey(tokenType)) {
throw new CompileException("Unknown instruction");
}
this.tokenType = tokenType;
this.operand = 0;
}

public Instruction(int tokenType, int operand) {
if (!OPCODES.containsKey(tokenType)) {
throw new CompileException("Unknown instruction");
}
this.tokenType = tokenType;
this.operand = operand;
}

public int getOpcode() {
return OPCODES.get(tokenType);
}

public String toString() {
return String.format("%02d %02d", getOpcode(), operand);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.emustudio.plugins.compiler.ssem.ast;

import net.emustudio.plugins.compiler.ssem.CompileException;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class Program {
private int startLine;
private boolean startLineDefined;

private final Map<Integer, Instruction> instructions = new HashMap<>();

public void setStartLine(int startLine) {
if (startLineDefined) {
throw new CompileException("Start line is already defined!");
}
this.startLine = startLine;
startLineDefined = true;
}

public int getStartLine() {
return startLine;
}

public void add(int line, Instruction instruction) {
if (instructions.containsKey(line)) {
throw new CompileException("Duplicate line definition: " + line);
}
if (line > 31) {
throw new CompileException("Line number is out of bounds <0;31>: " + line);
}
instructions.put(line, instruction);
}

public void forEach(BiConsumer<Integer, Instruction> processor) {
instructions.forEach(processor);
}

public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(startLine).append(" start\n");
forEach((line, instr) -> {
buffer.append(String.format("%02d %s\n", line, instr));
});
return buffer.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.emustudio.plugins.compiler.ssem.ast;

import net.emustudio.plugins.compiler.ssem.SSEMParser;
import net.emustudio.plugins.compiler.ssem.SSEMParserBaseVisitor;
import org.antlr.v4.runtime.Token;

public class ProgramParser extends SSEMParserBaseVisitor<Program> {
private final Program program = new Program();

@Override
public Program visitLine(SSEMParser.LineContext ctx) {
if (ctx.linenumber != null) {
int line = parseNumber(ctx.linenumber);

if (ctx.command != null) {
int operand = 0;
if (ctx.command.operand != null) {
if (ctx.command.instr.getType() == SSEMParser.BNUM) {
operand = Integer.parseInt(ctx.command.operand.getText(), 2);
} else {
operand = parseNumber(ctx.command.operand);
}
}

int instrType = ctx.command.instr.getType();
if (instrType == SSEMParser.START) {
program.setStartLine(line);
} else {
program.add(line, new Instruction(instrType, operand));
}
}
}
return program;
}

private int parseNumber(Token token) {
if (token.getType() == SSEMParser.HEXNUMBER) {
return Integer.decode(token.getText());
} else {
// Do not use decode because we don't support octal numbers
return Integer.parseUnsignedInt(token.getText());
}
}

public Program getProgram() {
return program;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@

public class ParserTest {

// PASS:
// -- COMMENT
// <EOL>
// 01 STP -- COMMENT

// FAIL:
// STP

// PASS:
// 01

// PASS:
// --

// PASS:
// <EOL>

// FAIL:
// 01 STP 01 STP



private ParserImpl program(String program) {
return new ParserImpl(
new LexerImpl(new StringReader(program)),
Expand Down
Loading

0 comments on commit 65191b3

Please sign in to comment.