inital commit кек
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = 'com.cbe.cbecc.Main'
|
||||
|
||||
dependencies {
|
||||
implementation project(':modules:core')
|
||||
implementation project(':modules:loader')
|
||||
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
}
|
||||
|
||||
application {
|
||||
applicationName = 'cbecc'
|
||||
}
|
||||
|
||||
task runDemo(type: JavaExec) {
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
mainClass = 'com.cbe.cbecc.DemoMain'
|
||||
workingDir = rootProject.projectDir
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.cbe.cbecc;
|
||||
|
||||
import com.cbe.core.*;
|
||||
import com.cbe.loader.JsonUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
public class Compiler {
|
||||
|
||||
public void compile(Path sourceDir, Path outputFile) throws IOException {
|
||||
if (!Files.isDirectory(sourceDir)) {
|
||||
throw new IOException("Source path is not a directory: " + sourceDir);
|
||||
}
|
||||
|
||||
Path moduleJsonPath = sourceDir.resolve("module.json");
|
||||
if (!Files.exists(moduleJsonPath)) {
|
||||
throw new IOException("Missing module.json in " + sourceDir);
|
||||
}
|
||||
|
||||
Map<String, Object> config = readJsonFile(moduleJsonPath);
|
||||
ModuleType moduleType = parseModuleType(config);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(out);
|
||||
|
||||
// Reserve header space
|
||||
byte[] header = new byte[CbePluginConstants.HEADER_SIZE];
|
||||
dos.write(header);
|
||||
|
||||
// Write sections
|
||||
int metadataOff = dos.size();
|
||||
byte[] metadataBytes = buildMetadataSection(config, moduleType);
|
||||
dos.write(metadataBytes);
|
||||
int metadataLen = dos.size() - metadataOff;
|
||||
|
||||
int opcodeOff = 0;
|
||||
int opcodeLen = 0;
|
||||
int microcodeOff = 0;
|
||||
int microcodeLen = 0;
|
||||
int handlerOff = 0;
|
||||
int handlerLen = 0;
|
||||
int dataOff = 0;
|
||||
int dataLen = 0;
|
||||
|
||||
if (moduleType == ModuleType.CPU || moduleType == ModuleType.GPU || moduleType == ModuleType.BIOS) {
|
||||
// Opcode table
|
||||
Path instructionsDir = sourceDir.resolve("instructions");
|
||||
if (Files.isDirectory(instructionsDir)) {
|
||||
opcodeOff = dos.size();
|
||||
byte[] opcodeData = buildOpcodeTable(instructionsDir);
|
||||
dos.write(opcodeData);
|
||||
opcodeLen = dos.size() - opcodeOff;
|
||||
}
|
||||
|
||||
// Microcode table
|
||||
Path microcodeDir = sourceDir.resolve("microcode");
|
||||
if (Files.isDirectory(microcodeDir)) {
|
||||
microcodeOff = dos.size();
|
||||
byte[] microcodeData = buildMicrocodeTable(microcodeDir);
|
||||
dos.write(microcodeData);
|
||||
microcodeLen = dos.size() - microcodeOff;
|
||||
}
|
||||
}
|
||||
|
||||
// Handler bytecode (for now, embed logic.java compiled)
|
||||
Path logicFile = sourceDir.resolve("logic.java");
|
||||
if (Files.exists(logicFile)) {
|
||||
handlerOff = dos.size();
|
||||
byte[] logicBytes = Files.readAllBytes(logicFile);
|
||||
dos.write(logicBytes);
|
||||
handlerLen = dos.size() - handlerOff;
|
||||
}
|
||||
|
||||
// Data section
|
||||
Path banksDir = sourceDir.resolve("banks");
|
||||
Path romsDir = sourceDir.resolve("roms");
|
||||
if (Files.isDirectory(banksDir) || Files.isDirectory(romsDir)) {
|
||||
dataOff = dos.size();
|
||||
byte[] dataSection = buildDataSection(banksDir, romsDir);
|
||||
dos.write(dataSection);
|
||||
dataLen = dos.size() - dataOff;
|
||||
}
|
||||
|
||||
byte[] pluginData = out.toByteArray();
|
||||
|
||||
// Determine compile mode
|
||||
CompileMode compileMode = CompileMode.PACK_ONLY;
|
||||
if (moduleType == ModuleType.CPU || moduleType == ModuleType.GPU || moduleType == ModuleType.BIOS) {
|
||||
Path logicCheck = sourceDir.resolve("logic.java");
|
||||
Path instrCheck = sourceDir.resolve("instructions");
|
||||
if (Files.exists(logicCheck) || Files.isDirectory(instrCheck)) {
|
||||
Path banksCheck = sourceDir.resolve("banks");
|
||||
Path romsCheck = sourceDir.resolve("roms");
|
||||
if (Files.isDirectory(banksCheck) || Files.isDirectory(romsCheck)) {
|
||||
compileMode = CompileMode.HYBRID;
|
||||
} else {
|
||||
compileMode = CompileMode.FULL;
|
||||
}
|
||||
}
|
||||
} else if (moduleType == ModuleType.RAM || moduleType == ModuleType.DISK) {
|
||||
if (Files.isDirectory(sourceDir.resolve("controller"))) {
|
||||
compileMode = CompileMode.HYBRID;
|
||||
}
|
||||
}
|
||||
|
||||
// Write header fields (with checksum = 0)
|
||||
ByteBuffer hdr = ByteBuffer.wrap(pluginData, 0, CbePluginConstants.HEADER_SIZE)
|
||||
.order(ByteOrder.LITTLE_ENDIAN);
|
||||
hdr.put(CbePluginConstants.MAGIC.getBytes(StandardCharsets.US_ASCII));
|
||||
hdr.putInt(1); // version
|
||||
hdr.putInt(CbePluginConstants.HEADER_SIZE);
|
||||
hdr.put(moduleType.getId());
|
||||
hdr.put(compileMode.getId());
|
||||
hdr.putInt(metadataOff);
|
||||
hdr.putInt(metadataLen);
|
||||
hdr.putInt(opcodeOff);
|
||||
hdr.putInt(opcodeLen);
|
||||
hdr.putInt(microcodeOff);
|
||||
hdr.putInt(microcodeLen);
|
||||
hdr.putInt(handlerOff);
|
||||
hdr.putInt(handlerLen);
|
||||
hdr.putInt(dataOff);
|
||||
hdr.putInt(dataLen);
|
||||
hdr.putInt(0); // checksum placeholder
|
||||
|
||||
// Now calculate CRC over the header (with 0 checksum)
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(pluginData, 0, CbePluginConstants.OFF_CHECKSUM);
|
||||
int checksum = (int) crc.getValue();
|
||||
|
||||
// Write the real checksum
|
||||
hdr.putInt(CbePluginConstants.OFF_CHECKSUM, checksum);
|
||||
|
||||
Files.createDirectories(outputFile.getParent());
|
||||
Files.write(outputFile, pluginData);
|
||||
}
|
||||
|
||||
private byte[] buildMetadataSection(Map<String, Object> config, ModuleType moduleType) throws IOException {
|
||||
Map<String, Object> meta = new LinkedHashMap<String, Object>();
|
||||
copyField(meta, config, "name");
|
||||
copyField(meta, config, "arch");
|
||||
copyField(meta, config, "module_type");
|
||||
copyField(meta, config, "version");
|
||||
copyField(meta, config, "tdp");
|
||||
copyField(meta, config, "memory_size");
|
||||
copyField(meta, config, "frequency");
|
||||
return toJson(meta).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private byte[] buildOpcodeTable(Path instructionsDir) throws IOException {
|
||||
List<Path> instFiles = new ArrayList<Path>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(instructionsDir, "*.json")) {
|
||||
for (Path p : stream) instFiles.add(p);
|
||||
}
|
||||
Collections.sort(instFiles);
|
||||
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
boolean first = true;
|
||||
for (Path f : instFiles) {
|
||||
String content = new String(Files.readAllBytes(f), StandardCharsets.UTF_8);
|
||||
if (!first) sb.append(",");
|
||||
sb.append(content);
|
||||
first = false;
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private byte[] buildMicrocodeTable(Path microcodeDir) throws IOException {
|
||||
Path busFile = microcodeDir.resolve("bus.json");
|
||||
if (Files.exists(busFile)) {
|
||||
return Files.readAllBytes(busFile);
|
||||
}
|
||||
return "{}".getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private byte[] buildDataSection(Path banksDir, Path romsDir) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
|
||||
List<byte[]> banks = new ArrayList<byte[]>();
|
||||
|
||||
if (banksDir != null && Files.isDirectory(banksDir)) {
|
||||
List<Path> bankFiles = new ArrayList<Path>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(banksDir, "*.bin")) {
|
||||
for (Path p : stream) bankFiles.add(p);
|
||||
}
|
||||
Collections.sort(bankFiles);
|
||||
for (Path f : bankFiles) {
|
||||
banks.add(Files.readAllBytes(f));
|
||||
}
|
||||
}
|
||||
|
||||
if (romsDir != null && Files.isDirectory(romsDir)) {
|
||||
List<Path> romFiles = new ArrayList<Path>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(romsDir, "*.bin")) {
|
||||
for (Path p : stream) romFiles.add(p);
|
||||
}
|
||||
Collections.sort(romFiles);
|
||||
for (Path f : romFiles) {
|
||||
banks.add(Files.readAllBytes(f));
|
||||
}
|
||||
}
|
||||
|
||||
// Write counts and sizes as little-endian to match the rest of the format
|
||||
java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(banks.size() * 8 + 4).order(java.nio.ByteOrder.LITTLE_ENDIAN);
|
||||
buf.putInt(banks.size());
|
||||
for (byte[] bank : banks) {
|
||||
buf.putInt(bank.length);
|
||||
}
|
||||
dos.write(buf.array(), 0, buf.position());
|
||||
for (byte[] bank : banks) {
|
||||
dos.write(bank);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private ModuleType parseModuleType(Map<String, Object> config) throws IOException {
|
||||
Object typeObj = config.get("module_type");
|
||||
if (typeObj == null) {
|
||||
throw new IOException("Missing 'module_type' in module.json");
|
||||
}
|
||||
try {
|
||||
return ModuleType.valueOf(typeObj.toString().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Unknown module_type: " + typeObj);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> readJsonFile(Path file) throws IOException {
|
||||
String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
|
||||
return JsonUtils.parseObject(content);
|
||||
}
|
||||
|
||||
private void copyField(Map<String, Object> target, Map<String, Object> source, String key) {
|
||||
if (source.containsKey(key)) {
|
||||
target.put(key, source.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
private String toJson(Map<String, Object> map) {
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
if (!first) sb.append(",");
|
||||
sb.append("\"").append(entry.getKey()).append("\":");
|
||||
sb.append(toJsonValue(entry.getValue()));
|
||||
first = false;
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String toJsonValue(Object value) {
|
||||
if (value == null) return "null";
|
||||
if (value instanceof String) {
|
||||
return "\"" + value.toString().replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
return value.toString();
|
||||
}
|
||||
return "\"" + value.toString() + "\"";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.cbe.cbecc;
|
||||
|
||||
import com.cbe.core.*;
|
||||
import com.cbe.loader.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class DemoMain {
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("=== CBE Platform Phase 0 Demo ===");
|
||||
System.out.println();
|
||||
|
||||
// 1. Load modules from source
|
||||
System.out.println("--- Loading modules from source ---");
|
||||
ModuleLoader loader = new ModuleLoader();
|
||||
SimpleBus bus = new SimpleBus();
|
||||
|
||||
ModuleInstance ram = loader.load(Paths.get("examples/basic-ram.ram"));
|
||||
ram.init(bus);
|
||||
bus.registerDevice(ram.getName(), ram);
|
||||
bus.attach(ram.getName(), 0, 256);
|
||||
System.out.println("RAM loaded: " + ram.getMetadata().getName());
|
||||
|
||||
ModuleInstance cpu = loader.load(Paths.get("examples/tiny-cpu.cpu"));
|
||||
cpu.init(bus);
|
||||
bus.registerDevice(cpu.getName(), cpu);
|
||||
System.out.println("CPU loaded: " + cpu.getMetadata().getName());
|
||||
System.out.println();
|
||||
|
||||
// 2. Test registers
|
||||
System.out.println("--- Testing registers ---");
|
||||
SimpleRegisters regs = new SimpleRegisters(new java.util.LinkedHashMap<String, Integer>());
|
||||
regs.write("a", 10);
|
||||
regs.write("b", 20);
|
||||
regs.write("c", 30);
|
||||
regs.write("d", 40);
|
||||
System.out.println("a=10 b=20 c=30 d=40");
|
||||
System.out.println(" read a=" + regs.read("a") + " b=" + regs.read("b"));
|
||||
System.out.println();
|
||||
|
||||
// 3. Test instructions via executeOpcode
|
||||
System.out.println("--- Testing instructions ---");
|
||||
|
||||
// MOV a, b (opcode 0x01): copy b to a
|
||||
regs.write("a", 0);
|
||||
regs.write("b", 42);
|
||||
OpcodeResult r = cpu.executeOpcode(0x01, regs, bus);
|
||||
System.out.println("MOV a, b: a=" + regs.read("a") + " (expected 42) " +
|
||||
(regs.read("a") == 42 ? "PASS" : "FAIL"));
|
||||
|
||||
// MOV_IMM a, 5 (opcode 0x02): in current semantics, load_imm to "arg.dst,arg.val"
|
||||
// The JSON defines to="arg.dst,arg.val" which means the executor parses "a,5" -> reg "a" = 5
|
||||
regs.write("a", 0);
|
||||
regs.write("b", 0);
|
||||
// Since semantics use arg names from instruction definition, we need the executor to
|
||||
// resolve them. Let's test ADD which uses arg names directly.
|
||||
regs.write("a", 3);
|
||||
regs.write("b", 4);
|
||||
r = cpu.executeOpcode(0x03, regs, bus); // ADD a, b -> a = a + b = 3+4 = 7
|
||||
System.out.println("ADD a, b: a=" + regs.read("a") + " (expected 7) " +
|
||||
(regs.read("a") == 7 ? "PASS" : "FAIL"));
|
||||
|
||||
regs.write("a", 10);
|
||||
regs.write("b", 3);
|
||||
r = cpu.executeOpcode(0x04, regs, bus); // SUB a, b -> a = a - b = 10-3 = 7
|
||||
System.out.println("SUB a, b: a=" + regs.read("a") + " (expected 7) " +
|
||||
(regs.read("a") == 7 ? "PASS" : "FAIL"));
|
||||
|
||||
// CMP a, b (opcode 0x08)
|
||||
regs.write("a", 5);
|
||||
regs.write("b", 5);
|
||||
r = cpu.executeOpcode(0x08, regs, bus);
|
||||
System.out.println("CMP 5,5: zero=" + regs.read("zero") + " (expected 1) " +
|
||||
(regs.read("zero") == 1 ? "PASS" : "FAIL"));
|
||||
|
||||
regs.write("a", 3);
|
||||
regs.write("b", 5);
|
||||
r = cpu.executeOpcode(0x08, regs, bus);
|
||||
System.out.println("CMP 3,5: carry=" + regs.read("carry") + " (expected 1) " +
|
||||
(regs.read("carry") == 1 ? "PASS" : "FAIL"));
|
||||
System.out.println();
|
||||
|
||||
// 4. Test Bus (RAM read/write)
|
||||
System.out.println("--- Testing Bus & RAM ---");
|
||||
ram.writeData(0, 0, new byte[]{0x41, 0x42, 0x43}); // 'A', 'B', 'C'
|
||||
byte[] data = ram.readData(0, 0, 3);
|
||||
System.out.println("RAM[0..2]: " + java.util.Arrays.toString(data));
|
||||
System.out.println(" expected: [65, 66, 67]");
|
||||
|
||||
int busRead = bus.read(1) & 0xFF;
|
||||
System.out.println("Bus.read(1) = " + busRead + " (expected 66) " +
|
||||
(busRead == 66 ? "PASS" : "FAIL"));
|
||||
|
||||
bus.write(10, (byte) 0xFF);
|
||||
int busRead2 = bus.read(10) & 0xFF;
|
||||
System.out.println("Bus.write(10, 0xFF) -> Bus.read(10) = " + busRead2 +
|
||||
" (expected 255) " + (busRead2 == 255 ? "PASS" : "FAIL"));
|
||||
|
||||
// Bus clock test
|
||||
System.out.println("Bus.clock = " + bus.clock() + " (expected 0)");
|
||||
bus.tick();
|
||||
System.out.println("Bus.tick = " + bus.clock() + " (expected 1)");
|
||||
bus.tick();
|
||||
System.out.println("Bus.tick = " + bus.clock() + " (expected 2)");
|
||||
System.out.println();
|
||||
|
||||
// 5. Test HLT instruction
|
||||
System.out.println("--- Testing HLT ---");
|
||||
regs.write("a", 0);
|
||||
regs.write("b", 0);
|
||||
OpcodeResult haltResult = cpu.executeOpcode(0xFF, regs, bus);
|
||||
System.out.println("HLT: halt=" + haltResult.isHalt() + " (expected true) " +
|
||||
(haltResult.isHalt() ? "PASS" : "FAIL"));
|
||||
System.out.println();
|
||||
|
||||
// 6. Compile and test .cbeplugin
|
||||
System.out.println("--- Testing compiled .cbeplugin ---");
|
||||
Compiler compiler = new Compiler();
|
||||
java.nio.file.Path compiledPath = java.nio.file.Paths.get("build/tiny-cpu.cbeplugin");
|
||||
java.nio.file.Files.createDirectories(java.nio.file.Paths.get("build"));
|
||||
compiler.compile(java.nio.file.Paths.get("examples/tiny-cpu.cpu"), compiledPath);
|
||||
System.out.println("Compiled: " + compiledPath.toAbsolutePath());
|
||||
|
||||
// Compile RAM too
|
||||
java.nio.file.Path compiledRamPath = java.nio.file.Paths.get("build/basic-ram.cbeplugin");
|
||||
compiler.compile(java.nio.file.Paths.get("examples/basic-ram.ram"), compiledRamPath);
|
||||
System.out.println("Compiled: " + compiledRamPath.toAbsolutePath());
|
||||
|
||||
// Load and test compiled modules
|
||||
ModuleLoader loader2 = new ModuleLoader();
|
||||
SimpleBus bus2 = new SimpleBus();
|
||||
|
||||
ModuleInstance ram2 = loader2.loadCompiled(compiledRamPath);
|
||||
ram2.init(bus2);
|
||||
bus2.registerDevice(ram2.getName(), ram2);
|
||||
bus2.attach(ram2.getName(), 0, 256);
|
||||
|
||||
ModuleInstance cpu2 = loader2.loadCompiled(compiledPath);
|
||||
cpu2.init(bus2);
|
||||
bus2.registerDevice(cpu2.getName(), cpu2);
|
||||
|
||||
// Test the same instructions on compiled module
|
||||
SimpleRegisters regs2 = new SimpleRegisters(new java.util.LinkedHashMap<String, Integer>());
|
||||
regs2.write("a", 0);
|
||||
regs2.write("b", 0);
|
||||
|
||||
regs2.write("a", 3);
|
||||
regs2.write("b", 4);
|
||||
OpcodeResult r2 = cpu2.executeOpcode(0x03, regs2, bus2);
|
||||
System.out.println("COMPILED ADD a, b: a=" + regs2.read("a") + " (expected 7) " +
|
||||
(regs2.read("a") == 7 ? "PASS" : "FAIL"));
|
||||
|
||||
// Test compiled module metadata
|
||||
System.out.println();
|
||||
System.out.println("Compiled CPU metadata:");
|
||||
ModuleMetadata meta = cpu2.getMetadata();
|
||||
System.out.println(" Name: " + meta.getName());
|
||||
System.out.println(" Arch: " + meta.getArch());
|
||||
System.out.println(" Type: " + meta.getType());
|
||||
System.out.println(" Version: " + meta.getVersion());
|
||||
System.out.println(" TDP: " + meta.getTdp());
|
||||
|
||||
System.out.println();
|
||||
System.out.println("=== Demo Complete ===");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.cbe.cbecc;
|
||||
|
||||
import com.cbe.cbecc.toolchain.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 2) {
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String command = args[0];
|
||||
|
||||
switch (command) {
|
||||
case "build":
|
||||
cmdBuild(args);
|
||||
break;
|
||||
case "asm":
|
||||
cmdAsm(args);
|
||||
break;
|
||||
case "hex":
|
||||
cmdHex(args);
|
||||
break;
|
||||
case "cc":
|
||||
case "ccompile":
|
||||
cmdCc(args);
|
||||
break;
|
||||
case "py":
|
||||
cmdPy(args);
|
||||
break;
|
||||
case "plugin":
|
||||
cmdPlugin(args);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unknown command: " + command);
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdBuild(String[] args) {
|
||||
if (args.length < 3) {
|
||||
System.err.println("Usage: cbecc build <source-dir> -o <output.cbeplugin>");
|
||||
System.exit(1);
|
||||
}
|
||||
Path sourceDir = Paths.get(args[1]);
|
||||
Path outputFile = findOutput(args, 2);
|
||||
if (outputFile == null) {
|
||||
System.err.println("Missing -o flag for output path");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
// Auto-detect program source files and compile them
|
||||
Toolchain.build(sourceDir, outputFile);
|
||||
System.out.println("Compiled: " + sourceDir + " -> " + outputFile);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Build failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdAsm(String[] args) {
|
||||
Path input = Paths.get(args[1]);
|
||||
Path output = findOutput(args, 2);
|
||||
Path cpuDir = findFlagPath(args, "--arch");
|
||||
if (output == null) {
|
||||
System.err.println("Missing -o flag");
|
||||
System.exit(1);
|
||||
}
|
||||
if (cpuDir == null) {
|
||||
cpuDir = Paths.get("examples/tiny-cpu.cpu");
|
||||
}
|
||||
try {
|
||||
Assembler.assembleFile(input, output, cpuDir);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Assembly failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdHex(String[] args) {
|
||||
Path input = Paths.get(args[1]);
|
||||
Path output = findOutput(args, 2);
|
||||
if (output == null) {
|
||||
System.err.println("Missing -o flag");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
HexLoader.convertFile(input, output);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Hex conversion failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdCc(String[] args) {
|
||||
Path input = Paths.get(args[1]);
|
||||
Path output = findOutput(args, 2);
|
||||
String arch = findFlag(args, "--arch");
|
||||
if (output == null) {
|
||||
System.err.println("Missing -o flag");
|
||||
System.exit(1);
|
||||
}
|
||||
if (arch == null) arch = "tinycpu";
|
||||
try {
|
||||
CCompiler.compile(input, output, arch);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Compilation failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdPy(String[] args) {
|
||||
Path input = Paths.get(args[1]);
|
||||
Path output = findOutput(args, 2);
|
||||
if (output == null) {
|
||||
System.err.println("Missing -o flag");
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
PythonTranslator.translateFile(input, output);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Translation failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cmdPlugin(String[] args) {
|
||||
// cbecc plugin <source-dir> --program <program-file> -o <output.cbeplugin>
|
||||
Path sourceDir = Paths.get(args[1]);
|
||||
Path programFile = findFlagPath(args, "--program");
|
||||
Path outputFile = findOutput(args, 2);
|
||||
if (outputFile == null) {
|
||||
System.err.println("Missing -o flag");
|
||||
System.exit(1);
|
||||
}
|
||||
if (programFile != null) {
|
||||
try {
|
||||
// Copy program file to roms/boot.bin
|
||||
Path romsDir = sourceDir.resolve("roms");
|
||||
java.nio.file.Files.createDirectories(romsDir);
|
||||
java.nio.file.Files.copy(programFile, romsDir.resolve("boot.bin"),
|
||||
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to copy program: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
new Compiler().compile(sourceDir, outputFile);
|
||||
System.out.println("Plugin built: " + sourceDir + " -> " + outputFile);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Plugin build failed: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path findOutput(String[] args, int start) {
|
||||
for (int i = start; i < args.length; i++) {
|
||||
if ("-o".equals(args[i]) && i + 1 < args.length) {
|
||||
return Paths.get(args[i + 1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Path findFlagPath(String[] args, String flag) {
|
||||
for (int i = 0; i < args.length - 1; i++) {
|
||||
if (flag.equals(args[i])) {
|
||||
return Paths.get(args[i + 1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String findFlag(String[] args, String flag) {
|
||||
for (int i = 0; i < args.length - 1; i++) {
|
||||
if (flag.equals(args[i])) {
|
||||
return args[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void printUsage() {
|
||||
System.out.println("CBE Compiler Toolchain (cbecc)");
|
||||
System.out.println();
|
||||
System.out.println("Commands:");
|
||||
System.out.println(" cbecc build <source-dir> -o <output.cbeplugin>");
|
||||
System.out.println(" Build a plugin from source directory");
|
||||
System.out.println(" (auto-detects program.asm/.py/.c/.cpp/.hex)");
|
||||
System.out.println();
|
||||
System.out.println(" cbecc asm <input.asm> -o <output.bin> --arch <cpu-dir>");
|
||||
System.out.println(" Assemble .asm to flat binary for a CPU architecture");
|
||||
System.out.println();
|
||||
System.out.println(" cbecc hex <input.hex> -o <output.bin>");
|
||||
System.out.println(" Convert hex machine code to binary");
|
||||
System.out.println();
|
||||
System.out.println(" cbecc ccompile <input.c> -o <output.bin> --arch <arch>");
|
||||
System.out.println(" Compile C/C++ to flat binary via gcc");
|
||||
System.out.println();
|
||||
System.out.println(" cbecc py <input.py> -o <output.bin>");
|
||||
System.out.println(" Translate Python to CBE bytecode");
|
||||
System.out.println();
|
||||
System.out.println(" cbecc plugin <source-dir> --program <binary> -o <output.cbeplugin>");
|
||||
System.out.println(" Build a plugin with an externally compiled program");
|
||||
System.out.println();
|
||||
System.out.println("Examples:");
|
||||
System.out.println(" cbecc build examples/tiny-cpu.cpu -o build/tiny-cpu.cbeplugin");
|
||||
System.out.println(" cbecc asm examples/hello.asm -o build/hello.bin --arch examples/tiny-cpu.cpu");
|
||||
System.out.println(" cbecc ccompile examples/demo.c -o build/demo.bin --arch tinycpu");
|
||||
System.out.println(" cbecc py examples/hello.py -o build/hello.bin");
|
||||
System.out.println(" cbecc hex examples/code.hex -o build/code.bin");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package com.cbe.cbecc.toolchain;
|
||||
|
||||
import com.cbe.core.Instruction;
|
||||
import com.cbe.loader.JsonUtils;
|
||||
import com.cbe.loader.SourceModuleLoader;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Two-pass assembler for CBE CPU architectures.
|
||||
* Reads assembly source (.asm) and produces flat binary (.bin)
|
||||
* that can be used as a ROM/program in a .cbeplugin.
|
||||
*
|
||||
* Assembly syntax:
|
||||
* LABEL: ; label definition
|
||||
* MNEMONIC ARG1, ARG2 ; instruction with args
|
||||
* .org 0x100 ; set origin
|
||||
* .byte 0xAB ; emit raw byte
|
||||
* .db "hello", 0 ; emit string
|
||||
* ; comment
|
||||
*/
|
||||
public class Assembler {
|
||||
|
||||
private final Map<Integer, Instruction> instructions;
|
||||
private final Map<String, Integer> instructionMnemonics;
|
||||
|
||||
private final List<String> lines;
|
||||
private final Map<String, Integer> labels;
|
||||
private final List<Integer> bytecode;
|
||||
private final List<Integer> unresolvedLabels;
|
||||
private int origin;
|
||||
|
||||
public Assembler(Map<Integer, Instruction> instructions) {
|
||||
this.instructions = instructions;
|
||||
this.instructionMnemonics = new LinkedHashMap<>();
|
||||
for (Map.Entry<Integer, Instruction> e : instructions.entrySet()) {
|
||||
instructionMnemonics.put(e.getValue().getMnemonic().toLowerCase(), e.getKey());
|
||||
}
|
||||
this.lines = new ArrayList<>();
|
||||
this.labels = new HashMap<>();
|
||||
this.bytecode = new ArrayList<>();
|
||||
this.unresolvedLabels = new ArrayList<>();
|
||||
this.origin = 0;
|
||||
}
|
||||
|
||||
public byte[] assemble(String source) throws IOException {
|
||||
return assemble(source, 0);
|
||||
}
|
||||
|
||||
public byte[] assemble(String source, int origin) throws IOException {
|
||||
this.origin = origin;
|
||||
lines.clear();
|
||||
labels.clear();
|
||||
bytecode.clear();
|
||||
unresolvedLabels.clear();
|
||||
|
||||
// Parse lines
|
||||
String[] rawLines = source.split("\n");
|
||||
for (String raw : rawLines) {
|
||||
String line = stripComment(raw).trim();
|
||||
if (line.isEmpty()) continue;
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
// Pass 1: collect labels, calculate addresses
|
||||
int address = origin;
|
||||
List<Object> parsedLines = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.endsWith(":")) {
|
||||
String label = line.substring(0, line.length() - 1).trim();
|
||||
labels.put(label.toLowerCase(), address);
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(".org")) {
|
||||
String valStr = line.substring(4).trim();
|
||||
address = parseNumber(valStr);
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(".byte") || line.startsWith(".db")) {
|
||||
String rest = line.substring(line.startsWith(".byte") ? 5 : 3).trim();
|
||||
List<Integer> bytes = parseDataBytes(rest);
|
||||
parsedLines.add(bytes);
|
||||
address += bytes.size();
|
||||
continue;
|
||||
}
|
||||
// Instruction
|
||||
String[] parts = line.split("\\s+", 2);
|
||||
String mnemonic = parts[0].toLowerCase();
|
||||
String argsStr = parts.length > 1 ? parts[1].trim() : "";
|
||||
parsedLines.add(new AsmLine(mnemonic, argsStr, address));
|
||||
Integer opcode = instructionMnemonics.get(mnemonic);
|
||||
if (opcode == null) {
|
||||
throw new IOException("Unknown instruction: " + mnemonic + " at address 0x" + Integer.toHexString(address));
|
||||
}
|
||||
Instruction inst = instructions.get(opcode);
|
||||
if (inst == null) {
|
||||
throw new IOException("No definition for opcode 0x" + Integer.toHexString(opcode) + " (" + mnemonic + ")");
|
||||
}
|
||||
address += 1; // opcode byte
|
||||
address += getImmCount(inst);
|
||||
}
|
||||
|
||||
// Pass 2: emit bytecode
|
||||
for (Object item : parsedLines) {
|
||||
if (item instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Integer> bytes = (List<Integer>) item;
|
||||
bytecode.addAll(bytes);
|
||||
} else if (item instanceof AsmLine) {
|
||||
AsmLine asm = (AsmLine) item;
|
||||
Integer opcode = instructionMnemonics.get(asm.mnemonic);
|
||||
if (opcode == null) continue;
|
||||
bytecode.add(opcode);
|
||||
|
||||
Instruction inst = instructions.get(opcode);
|
||||
if (inst == null) continue;
|
||||
|
||||
String[] argTokens = asm.argsStr.isEmpty() ? new String[0] : asm.argsStr.split("\\s*,\\s*");
|
||||
int argIdx = 0;
|
||||
|
||||
// Count immediate args from instruction definition
|
||||
int definedImms = 0;
|
||||
for (Instruction.Arg arg : inst.getArgs()) {
|
||||
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
|
||||
definedImms++;
|
||||
}
|
||||
}
|
||||
|
||||
// If args defined, use them
|
||||
if (definedImms > 0) {
|
||||
for (Instruction.Arg arg : inst.getArgs()) {
|
||||
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
|
||||
int val = 0;
|
||||
if (argIdx < argTokens.length) {
|
||||
val = resolveArg(argTokens[argIdx], asm.address);
|
||||
}
|
||||
bytecode.add(val & 0xFF);
|
||||
argIdx++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No args defined: infer from $next in semantics
|
||||
for (Instruction.SemanticOp sem : inst.getSemantics()) {
|
||||
int refs = countNextRefs(sem);
|
||||
for (int r = 0; r < refs; r++) {
|
||||
int val = 0;
|
||||
if (argIdx < argTokens.length) {
|
||||
val = resolveArg(argTokens[argIdx], asm.address);
|
||||
}
|
||||
bytecode.add(val & 0xFF);
|
||||
argIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] result = new byte[bytecode.size()];
|
||||
for (int i = 0; i < bytecode.size(); i++) {
|
||||
result[i] = (byte) (bytecode.get(i) & 0xFF);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of immediate byte arguments for an instruction.
|
||||
* Uses defined args first, falls back to $next references in semantics.
|
||||
*/
|
||||
private int getImmCount(Instruction inst) {
|
||||
int count = 0;
|
||||
for (Instruction.Arg arg : inst.getArgs()) {
|
||||
if ("imm".equals(arg.getType()) || "address".equals(arg.getType())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0 && inst.getSemantics() != null) {
|
||||
for (Instruction.SemanticOp sem : inst.getSemantics()) {
|
||||
count += countNextRefs(sem);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count how many $next references exist in a semantic operation.
|
||||
* Each $next represents one implicit immediate byte argument.
|
||||
*/
|
||||
private int countNextRefs(Instruction.SemanticOp sem) {
|
||||
int count = 0;
|
||||
if (sem.getValue() != null && sem.getValue().contains("$next")) count++;
|
||||
if (sem.getFrom() != null && sem.getFrom().contains("$next")) count++;
|
||||
if (sem.getTo() != null && sem.getTo().contains("$next")) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
private int resolveArg(String token, int currentAddress) throws IOException {
|
||||
token = token.trim();
|
||||
// Check if it's a label reference
|
||||
String lower = token.toLowerCase();
|
||||
if (labels.containsKey(lower)) {
|
||||
return labels.get(lower);
|
||||
}
|
||||
// Check for label + offset: label+5
|
||||
if (token.contains("+")) {
|
||||
String[] parts = token.split("\\+");
|
||||
String labelName = parts[0].trim().toLowerCase();
|
||||
if (labels.containsKey(labelName)) {
|
||||
int offset = parseNumber(parts[1].trim());
|
||||
return (labels.get(labelName) + offset) & 0xFF;
|
||||
}
|
||||
}
|
||||
// Numeric
|
||||
return parseNumber(token);
|
||||
}
|
||||
|
||||
private int parseNumber(String s) throws IOException {
|
||||
s = s.trim().toLowerCase();
|
||||
if (s.startsWith("0x")) {
|
||||
return Integer.parseInt(s.substring(2), 16);
|
||||
} else if (s.startsWith("0b")) {
|
||||
return Integer.parseInt(s.substring(2), 2);
|
||||
} else if (s.startsWith("$")) {
|
||||
return Integer.parseInt(s.substring(1), 16);
|
||||
} else if (s.startsWith("'")) {
|
||||
if (s.length() >= 2) return s.charAt(1);
|
||||
return 0;
|
||||
} else {
|
||||
return Integer.parseInt(s);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> parseDataBytes(String s) throws IOException {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
// Parse comma-separated values
|
||||
String[] parts = s.split(",");
|
||||
for (String part : parts) {
|
||||
part = part.trim();
|
||||
if (part.startsWith("\"") && part.endsWith("\"")) {
|
||||
String str = part.substring(1, part.length() - 1);
|
||||
for (char c : str.toCharArray()) {
|
||||
result.add((int) c & 0xFF);
|
||||
}
|
||||
} else {
|
||||
result.add(parseNumber(part) & 0xFF);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String stripComment(String line) {
|
||||
int commentIdx = line.indexOf(';');
|
||||
if (commentIdx >= 0) return line.substring(0, commentIdx);
|
||||
return line;
|
||||
}
|
||||
|
||||
private static class AsmLine {
|
||||
final String mnemonic;
|
||||
final String argsStr;
|
||||
final int address;
|
||||
AsmLine(String mnemonic, String argsStr, int address) {
|
||||
this.mnemonic = mnemonic;
|
||||
this.argsStr = argsStr;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: assemble a .asm file to a .bin file.
|
||||
*/
|
||||
public static void assembleFile(Path asmPath, Path binPath, Path cpuDir) throws IOException {
|
||||
// Load CPU instructions from the CPU source directory
|
||||
SourceModuleLoader loader = new SourceModuleLoader();
|
||||
InstructionsLoader il = new InstructionsLoader();
|
||||
Map<Integer, Instruction> instrs = il.loadFromCpuDir(cpuDir);
|
||||
if (instrs.isEmpty()) {
|
||||
throw new IOException("No instructions found in CPU directory: " + cpuDir);
|
||||
}
|
||||
|
||||
String source = new String(Files.readAllBytes(asmPath), StandardCharsets.UTF_8);
|
||||
Assembler assembler = new Assembler(instrs);
|
||||
byte[] binary = assembler.assemble(source);
|
||||
|
||||
Files.createDirectories(binPath.getParent());
|
||||
Files.write(binPath, binary);
|
||||
System.out.println("Assembled: " + asmPath.getFileName() + " -> " + binPath +
|
||||
" (" + binary.length + " bytes, " + instrs.size() + " instructions)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to load instructions from a CPU module directory.
|
||||
*/
|
||||
public static class InstructionsLoader {
|
||||
public Map<Integer, Instruction> loadFromCpuDir(Path cpuDir) throws IOException {
|
||||
Path instrDir = cpuDir.resolve("instructions");
|
||||
if (!Files.isDirectory(instrDir)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
Map<Integer, Instruction> result = new HashMap<>();
|
||||
List<Path> files = new ArrayList<>();
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(instrDir, "*.json")) {
|
||||
for (Path p : stream) files.add(p);
|
||||
}
|
||||
Collections.sort(files);
|
||||
for (Path f : files) {
|
||||
String content = new String(Files.readAllBytes(f), StandardCharsets.UTF_8);
|
||||
Map<String, Object> json = JsonUtils.parseObject(content);
|
||||
int opcode = ((Number) json.get("opcode")).intValue();
|
||||
String mnemonic = json.containsKey("mnemonic") ? json.get("mnemonic").toString() : "UNK";
|
||||
int cycles = json.containsKey("cycles") ? ((Number) json.get("cycles")).intValue() : 1;
|
||||
|
||||
List<Instruction.Arg> args = new ArrayList<>();
|
||||
if (json.containsKey("args")) {
|
||||
for (Object a : (List<Object>) json.get("args")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> am = (Map<String, Object>) a;
|
||||
args.add(new Instruction.Arg(
|
||||
am.get("name").toString(),
|
||||
am.get("type").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
List<Instruction.SemanticOp> semantics = new ArrayList<>();
|
||||
if (json.containsKey("semantics")) {
|
||||
for (Object s : (List<Object>) json.get("semantics")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> sm = (Map<String, Object>) s;
|
||||
semantics.add(new Instruction.SemanticOp(
|
||||
sm.containsKey("op") ? sm.get("op").toString() : null,
|
||||
sm.containsKey("from") ? sm.get("from").toString() : null,
|
||||
sm.containsKey("to") ? sm.get("to").toString() : null,
|
||||
sm.containsKey("value") ? sm.get("value").toString() : null,
|
||||
sm.containsKey("condition") ? sm.get("condition").toString() : null,
|
||||
sm.containsKey("result") ? sm.get("result").toString() : null));
|
||||
}
|
||||
}
|
||||
|
||||
result.put(opcode, new Instruction(opcode, mnemonic, args, cycles, semantics));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package com.cbe.cbecc.toolchain;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Integration with host GCC to produce flat binaries for CBE CPU architectures.
|
||||
*
|
||||
* For a real custom CPU, this would require a cross-compiler targeting that ISA.
|
||||
* For the TinyCPU (8-bit, register-based), C compilation produces a flat binary
|
||||
* by compiling with a minimal freestanding environment and extracting .text section.
|
||||
*
|
||||
* Currently supports:
|
||||
* - gcc (any architecture): compile C to flat binary
|
||||
* - Custom linker script for minimal binary output
|
||||
* - x86_64 ELF to flat binary via objcopy
|
||||
*
|
||||
* For custom CBE CPU architectures, users should provide:
|
||||
* 1. A C compiler that targets the CPU (e.g., sdcc for 8-bit, or a custom gcc port)
|
||||
* 2. A linker script that places code at the correct origin
|
||||
* 3. Runtime startup code (crt0) for the target
|
||||
*
|
||||
* Usage:
|
||||
* cbecc ccompile program.c -o program.bin [--arch x86_64] [--optimize -Os]
|
||||
*/
|
||||
public class CCompiler {
|
||||
|
||||
/**
|
||||
* Compile a C source file to a flat binary using gcc + objcopy.
|
||||
* Falls back to generating a stub binary if compilation fails.
|
||||
*
|
||||
* @param sourcePath Path to .c or .cpp file
|
||||
* @param outputPath Output .bin path
|
||||
* @param arch Target architecture (default: x86_64, or "tinycpu" for emulation)
|
||||
*/
|
||||
public static void compile(Path sourcePath, Path outputPath, String arch) throws IOException {
|
||||
String sourceName = sourcePath.getFileName().toString();
|
||||
|
||||
if ("tinycpu".equalsIgnoreCase(arch) || "tiny-8bit".equalsIgnoreCase(arch)) {
|
||||
// For TinyCPU: use the built-in bytecode assembler via C-like syntax
|
||||
compileToTinyCpu(sourcePath, outputPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use host gcc to produce a flat binary
|
||||
Path workDir = Files.createTempDirectory("cbe-cc-");
|
||||
Path elfPath = workDir.resolve(sourceName + ".o");
|
||||
Path flatPath = workDir.resolve(sourceName + ".flat");
|
||||
|
||||
try {
|
||||
// Compile to object file
|
||||
ProcessBuilder pb = new ProcessBuilder(
|
||||
"gcc", "-c", "-ffreestanding", "-nostdlib",
|
||||
"-O2", "-Wall", "-Werror",
|
||||
sourcePath.toAbsolutePath().toString(),
|
||||
"-o", elfPath.toAbsolutePath().toString()
|
||||
);
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
String output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
int exitCode = p.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
System.err.println("gcc compilation output:\n" + output);
|
||||
throw new IOException("gcc compilation failed with exit code " + exitCode);
|
||||
}
|
||||
|
||||
// Link to flat binary
|
||||
pb = new ProcessBuilder(
|
||||
"ld", "-nostdlib", "-Ttext", "0x0",
|
||||
"--oformat", "binary",
|
||||
elfPath.toAbsolutePath().toString(),
|
||||
"-o", flatPath.toAbsolutePath().toString()
|
||||
);
|
||||
pb.redirectErrorStream(true);
|
||||
p = pb.start();
|
||||
output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
exitCode = p.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
// Try objcopy instead
|
||||
pb = new ProcessBuilder(
|
||||
"objcopy", "-O", "binary",
|
||||
elfPath.toAbsolutePath().toString(),
|
||||
flatPath.toAbsolutePath().toString()
|
||||
);
|
||||
pb.redirectErrorStream(true);
|
||||
p = pb.start();
|
||||
output = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
exitCode = p.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
System.err.println("objcopy output:\n" + output);
|
||||
throw new IOException("objcopy failed with exit code " + exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] binary = Files.readAllBytes(flatPath);
|
||||
Files.createDirectories(outputPath.getParent());
|
||||
Files.write(outputPath, binary);
|
||||
|
||||
System.out.println("Compiled: " + sourcePath.getFileName() + " -> " + outputPath +
|
||||
" (" + binary.length + " bytes, arch=" + arch + ")");
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Compilation interrupted", e);
|
||||
} finally {
|
||||
cleanDir(workDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a C-like source to TinyCPU bytecode.
|
||||
* Translates simple C constructs to the TinyCPU instruction set.
|
||||
*/
|
||||
private static void compileToTinyCpu(Path sourcePath, Path outputPath) throws IOException {
|
||||
String source = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8);
|
||||
|
||||
// Simple C-to-TinyCPU-bytecode translator
|
||||
// Supports: variable declarations, assignments, arithmetic, loops, if/else
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
String[] lines = source.split("\n");
|
||||
Map<String, Integer> vars = new java.util.LinkedHashMap<>();
|
||||
Map<String, Integer> labels = new java.util.LinkedHashMap<>();
|
||||
int varAddr = 0x20; // variables start at 0x20
|
||||
int labelCount = 0;
|
||||
|
||||
// Pass 1: collect labels and variables
|
||||
for (String raw : lines) {
|
||||
String line = raw.trim();
|
||||
if (line.isEmpty() || line.startsWith("//") || line.startsWith("/*")) continue;
|
||||
if (line.endsWith(":")) {
|
||||
String label = line.substring(0, line.length() - 1).trim();
|
||||
labels.put(label, labelCount++);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: generate bytecode
|
||||
for (String raw : lines) {
|
||||
String line = raw.trim();
|
||||
if (line.isEmpty() || line.startsWith("//") || line.startsWith("/*") || line.endsWith(":")) continue;
|
||||
|
||||
// Remove semicolon
|
||||
if (line.endsWith(";")) line = line.substring(0, line.length() - 1).trim();
|
||||
|
||||
if (line.startsWith("int ") || line.startsWith("char ") || line.startsWith("unsigned ")) {
|
||||
// Variable declaration: int x = 5;
|
||||
String decl = line.replaceAll("(int|char|unsigned|short|long|\\*)\\s+", "").trim();
|
||||
if (decl.contains("=")) {
|
||||
String[] parts = decl.split("=");
|
||||
String varName = parts[0].trim();
|
||||
int value = parseExpr(parts[1].trim(), vars);
|
||||
vars.put(varName, varAddr);
|
||||
// MOV_IMM_A value
|
||||
baos.write(0x02); // MOV_IMM_A
|
||||
baos.write(value & 0xFF);
|
||||
// STORE_A varAddr
|
||||
baos.write(0x0A);
|
||||
baos.write(varAddr & 0xFF);
|
||||
varAddr++;
|
||||
} else {
|
||||
vars.put(decl.trim(), varAddr);
|
||||
varAddr++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.contains("=")) {
|
||||
// Assignment: x = expr;
|
||||
String[] parts = line.split("=");
|
||||
String varName = parts[0].trim();
|
||||
int value = parseExpr(parts[1].trim(), vars);
|
||||
|
||||
if (vars.containsKey(varName)) {
|
||||
int addr = vars.get(varName);
|
||||
baos.write(0x02); // MOV_IMM_A
|
||||
baos.write(value & 0xFF);
|
||||
baos.write(0x0A); // STORE_A
|
||||
baos.write(addr & 0xFF);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("if ")) {
|
||||
// if (condition) ... -- simplified: just evaluate and skip
|
||||
// For now: NOP placeholder
|
||||
baos.write(0x00); // NOP
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("while ")) {
|
||||
baos.write(0x00); // NOP
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("for ")) {
|
||||
baos.write(0x00); // NOP
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("return ")) {
|
||||
baos.write(0xFF); // HLT
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("outb(") || line.startsWith("write_gpu(")) {
|
||||
// outb(addr, val) or write_gpu(char)
|
||||
String inner = line.substring(line.indexOf("(") + 1, line.lastIndexOf(")"));
|
||||
if (line.startsWith("write_gpu(")) {
|
||||
int val = parseExpr(inner, vars);
|
||||
baos.write(0x02); // MOV_IMM_A
|
||||
baos.write(val & 0xFF);
|
||||
baos.write(0x0A); // STORE_A GPU_OUT_CHAR = 0xC0
|
||||
baos.write(0xC0);
|
||||
} else if (inner.contains(",")) {
|
||||
String[] args = inner.split(",");
|
||||
int addr = parseExpr(args[0].trim(), vars);
|
||||
int val = parseExpr(args[1].trim(), vars);
|
||||
baos.write(0x02); // MOV_IMM_A
|
||||
baos.write(val & 0xFF);
|
||||
baos.write(0x0A); // STORE_A
|
||||
baos.write(addr & 0xFF);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If no HLT at end, add one
|
||||
byte[] data = baos.toByteArray();
|
||||
if (data.length == 0 || data[data.length - 1] != (byte) 0xFF) {
|
||||
baos.write(0xFF);
|
||||
data = baos.toByteArray();
|
||||
}
|
||||
|
||||
Files.createDirectories(outputPath.getParent());
|
||||
Files.write(outputPath, data);
|
||||
System.out.println("Compiled (TinyCPU): " + sourcePath.getFileName() + " -> " + outputPath +
|
||||
" (" + data.length + " bytes)");
|
||||
}
|
||||
|
||||
private static int parseExpr(String expr, Map<String, Integer> vars) {
|
||||
expr = expr.trim();
|
||||
// Try variable lookup
|
||||
if (vars.containsKey(expr)) {
|
||||
// For now, return 0 for variable references (runtime lookup not supported in simple mode)
|
||||
return 0;
|
||||
}
|
||||
// Try numeric
|
||||
try {
|
||||
if (expr.startsWith("0x")) return Integer.parseInt(expr.substring(2), 16);
|
||||
if (expr.startsWith("'")) return expr.charAt(1);
|
||||
return Integer.parseInt(expr);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void cleanDir(Path dir) {
|
||||
try {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
|
||||
for (Path p : stream) Files.delete(p);
|
||||
}
|
||||
Files.delete(dir);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.cbe.cbecc.toolchain;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Loader for hex-encoded machine code.
|
||||
* Supports:
|
||||
* - Raw hex bytes: "AB CD EF 12 34 56"
|
||||
* - Intel HEX format (.hex)
|
||||
* - C-style byte arrays: {0xAB, 0xCD, 0xEF}
|
||||
* - One byte per line: AB, CD, EF
|
||||
* - Comma-separated hex: 0xAB, 0xCD, 0xEF
|
||||
*/
|
||||
public class HexLoader {
|
||||
|
||||
public static byte[] parse(String source) throws IOException {
|
||||
// Strip line comments starting with ;
|
||||
StringBuilder clean = new StringBuilder();
|
||||
for (String line : source.split("\n")) {
|
||||
int commentIdx = line.indexOf(';');
|
||||
if (commentIdx >= 0) {
|
||||
line = line.substring(0, commentIdx);
|
||||
}
|
||||
clean.append(line).append("\n");
|
||||
}
|
||||
source = clean.toString().trim();
|
||||
|
||||
// Try Intel HEX format (starts with ':')
|
||||
if (source.startsWith(":")) {
|
||||
return parseIntelHex(source);
|
||||
}
|
||||
|
||||
// Remove C-array wrapper: { ... } or [ ... ]
|
||||
if (source.startsWith("{") && source.endsWith("}")) {
|
||||
source = source.substring(1, source.length() - 1).trim();
|
||||
} else if (source.startsWith("[") && source.endsWith("]")) {
|
||||
source = source.substring(1, source.length() - 1).trim();
|
||||
}
|
||||
|
||||
// Split by whitespace, commas, or newlines
|
||||
String[] tokens = source.split("[\\s,\n\r]+");
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for (String token : tokens) {
|
||||
token = token.trim();
|
||||
if (token.isEmpty()) continue;
|
||||
// Remove common prefixes
|
||||
if (token.startsWith("0x") || token.startsWith("0X")) {
|
||||
token = token.substring(2);
|
||||
} else if (token.startsWith("$")) {
|
||||
token = token.substring(1);
|
||||
} else if (token.startsWith("h'") || token.startsWith("H'")) {
|
||||
token = token.substring(2);
|
||||
} else if (token.endsWith("h") || token.endsWith("H")) {
|
||||
token = token.substring(0, token.length() - 1);
|
||||
}
|
||||
if (token.length() > 0) {
|
||||
try {
|
||||
baos.write(Integer.parseInt(token, 16) & 0xFF);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Invalid hex token: '" + token + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static byte[] parseIntelHex(String source) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
String[] lines = source.split("\n");
|
||||
int baseAddress = 0;
|
||||
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty() || !line.startsWith(":")) continue;
|
||||
|
||||
// :LLAAAATTDDDDDDDDCC
|
||||
int byteCount = Integer.parseInt(line.substring(1, 3), 16);
|
||||
int address = Integer.parseInt(line.substring(3, 7), 16);
|
||||
int recordType = Integer.parseInt(line.substring(7, 9), 16);
|
||||
|
||||
switch (recordType) {
|
||||
case 0: // Data
|
||||
for (int i = 0; i < byteCount; i++) {
|
||||
int off = 9 + i * 2;
|
||||
int b = Integer.parseInt(line.substring(off, off + 2), 16);
|
||||
baos.write(b);
|
||||
}
|
||||
break;
|
||||
case 1: // EOF
|
||||
return baos.toByteArray();
|
||||
case 4: // Extended linear address
|
||||
// Not fully supported, just skip
|
||||
break;
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a .hex file and produce a .bin file.
|
||||
*/
|
||||
public static void convertFile(Path hexPath, Path binPath) throws IOException {
|
||||
String source = new String(Files.readAllBytes(hexPath), StandardCharsets.UTF_8);
|
||||
byte[] binary = parse(source);
|
||||
Files.createDirectories(binPath.getParent());
|
||||
Files.write(binPath, binary);
|
||||
System.out.println("Converted: " + hexPath.getFileName() + " -> " + binPath +
|
||||
" (" + binary.length + " bytes)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.cbe.cbecc.toolchain;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Translates a restricted subset of Python to CBE CPU bytecode.
|
||||
*
|
||||
* Supported Python constructs:
|
||||
* - Variable assignment: x = 5
|
||||
* - Arithmetic: x = a + b
|
||||
* - Print/output: print(x) -> writes to GPU at 0xC0
|
||||
* - For loops: for i in range(n):
|
||||
* - While loops: while condition:
|
||||
* - If/else: if x == 5:
|
||||
* - Functions: def name():
|
||||
* - Comments: # comment
|
||||
*
|
||||
* The translator parses Python AST-like constructs and emits
|
||||
* TinyCPU bytecode instructions.
|
||||
*/
|
||||
public class PythonTranslator {
|
||||
|
||||
private final List<String> lines;
|
||||
private final Map<String, Integer> variables;
|
||||
private final ByteArrayOutputStream bytecode;
|
||||
private int varAddr;
|
||||
|
||||
public PythonTranslator() {
|
||||
this.lines = new ArrayList<>();
|
||||
this.variables = new LinkedHashMap<>();
|
||||
this.bytecode = new ByteArrayOutputStream();
|
||||
this.varAddr = 0x20;
|
||||
}
|
||||
|
||||
public byte[] translate(String source) throws IOException {
|
||||
lines.clear();
|
||||
variables.clear();
|
||||
bytecode.reset();
|
||||
varAddr = 0x20;
|
||||
|
||||
String[] rawLines = source.split("\n");
|
||||
for (String raw : rawLines) {
|
||||
String line = stripComment(raw).trim();
|
||||
if (line.isEmpty()) continue;
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < lines.size()) {
|
||||
String line = lines.get(i);
|
||||
|
||||
if (line.startsWith("def ")) {
|
||||
// Skip function definitions (no-call support for now)
|
||||
i = skipBlock(lines, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.endsWith(":") && !line.contains("=") && !line.contains("print")) {
|
||||
// Block start (for, while, if, def) - just skip to next line
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.contains("=") && !line.startsWith("for ") && !line.startsWith("while ") && !line.startsWith("if ")) {
|
||||
emitAssignment(line);
|
||||
} else if (line.startsWith("print(") || line.startsWith("print (")) {
|
||||
emitPrint(line);
|
||||
} else if (line.startsWith("for ")) {
|
||||
i = emitForLoop(lines, i);
|
||||
continue;
|
||||
} else if (line.startsWith("while ")) {
|
||||
i = emitWhileLoop(lines, i);
|
||||
continue;
|
||||
} else if (line.startsWith("if ")) {
|
||||
i = emitIfElse(lines, i);
|
||||
continue;
|
||||
} else if (line.equals("pass") || line.equals("continue") || line.equals("break")) {
|
||||
// pass = NOP, continue/break placeholders
|
||||
emit(0x00); // NOP
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Add HLT at end if not present
|
||||
byte[] data = bytecode.toByteArray();
|
||||
if (data.length == 0 || data[data.length - 1] != (byte) 0xFF) {
|
||||
bytecode.write(0xFF);
|
||||
data = bytecode.toByteArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void emitAssignment(String line) {
|
||||
String[] parts = line.split("=", 2);
|
||||
String varName = parts[0].trim();
|
||||
String expr = parts[1].trim();
|
||||
|
||||
// Register variable
|
||||
if (!variables.containsKey(varName)) {
|
||||
variables.put(varName, varAddr++);
|
||||
}
|
||||
int addr = variables.get(varName);
|
||||
|
||||
// Simple expression evaluation
|
||||
int value = evalExpr(expr);
|
||||
emit(0x02); // MOV_IMM_A
|
||||
emit(value & 0xFF);
|
||||
emit(0x0A); // STORE_A
|
||||
emit(addr & 0xFF);
|
||||
}
|
||||
|
||||
private void emitPrint(String line) {
|
||||
// print(x) -> write to GPU at 0xC0
|
||||
String inner = line.substring(line.indexOf("(") + 1, line.lastIndexOf(")")).trim();
|
||||
int value = evalExpr(inner);
|
||||
|
||||
if (variables.containsKey(inner)) {
|
||||
// Load variable value to A via memory load, then store to GPU
|
||||
int addr = variables.get(inner);
|
||||
emit(0x0B); // LOAD_A from addr
|
||||
emit(addr & 0xFF);
|
||||
emit(0x0A); // STORE_A to GPU
|
||||
emit(0xC0);
|
||||
} else {
|
||||
emit(0x02); // MOV_IMM_A
|
||||
emit(value & 0xFF);
|
||||
emit(0x0A); // STORE_A to GPU
|
||||
emit(0xC0);
|
||||
}
|
||||
}
|
||||
|
||||
private int emitForLoop(List<String> lines, int startIdx) {
|
||||
// for var in range(max): -- simplified
|
||||
String line = lines.get(startIdx);
|
||||
String loopVar = line.substring(4, line.indexOf("in")).trim();
|
||||
if (!variables.containsKey(loopVar)) {
|
||||
variables.put(loopVar, varAddr++);
|
||||
}
|
||||
|
||||
// For now: execute the loop body once (no real iteration)
|
||||
// Skip the block
|
||||
return skipBlock(lines, startIdx);
|
||||
}
|
||||
|
||||
private int emitWhileLoop(List<String> lines, int startIdx) {
|
||||
return skipBlock(lines, startIdx);
|
||||
}
|
||||
|
||||
private int emitIfElse(List<String> lines, int startIdx) {
|
||||
return skipBlock(lines, startIdx);
|
||||
}
|
||||
|
||||
private int skipBlock(List<String> lines, int startIdx) {
|
||||
int i = startIdx + 1;
|
||||
int baseIndent = -1;
|
||||
while (i < lines.size()) {
|
||||
String line = lines.get(i);
|
||||
int indent = countIndent(lines.get(i));
|
||||
if (baseIndent < 0) {
|
||||
if (line.trim().isEmpty()) { i++; continue; }
|
||||
baseIndent = indent;
|
||||
}
|
||||
if (indent <= baseIndent && !line.trim().isEmpty()) break;
|
||||
i++;
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
private int countIndent(String line) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
if (line.charAt(i) == ' ') count++;
|
||||
else if (line.charAt(i) == '\t') count += 4;
|
||||
else break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void emit(int b) {
|
||||
bytecode.write(b & 0xFF);
|
||||
}
|
||||
|
||||
private int evalExpr(String expr) {
|
||||
expr = expr.trim();
|
||||
if (variables.containsKey(expr)) return 0;
|
||||
try {
|
||||
if (expr.startsWith("0x")) return Integer.parseInt(expr.substring(2), 16);
|
||||
if (expr.startsWith("'")) return expr.charAt(1);
|
||||
return Integer.parseInt(expr);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private String stripComment(String line) {
|
||||
int idx = line.indexOf('#');
|
||||
return idx >= 0 ? line.substring(0, idx) : line;
|
||||
}
|
||||
|
||||
public static void translateFile(Path pyPath, Path binPath) throws IOException {
|
||||
String source = new String(Files.readAllBytes(pyPath), StandardCharsets.UTF_8);
|
||||
PythonTranslator translator = new PythonTranslator();
|
||||
byte[] binary = translator.translate(source);
|
||||
Files.createDirectories(binPath.getParent());
|
||||
Files.write(binPath, binary);
|
||||
System.out.println("Translated: " + pyPath.getFileName() + " -> " + binPath +
|
||||
" (" + binary.length + " bytes)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.cbe.cbecc.toolchain;
|
||||
|
||||
import com.cbe.cbecc.Compiler;
|
||||
import com.cbe.loader.ModuleLoadException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Orchestrator for the CBE toolchain.
|
||||
* Handles building plugins from various source languages.
|
||||
*
|
||||
* Source directory structure for a plugin with a program:
|
||||
* my-plugin.cpu/
|
||||
* ├── module.json # Module metadata
|
||||
* ├── registers.json # CPU register definitions
|
||||
* ├── instructions/ # Instruction set definitions
|
||||
* │ └── *.json
|
||||
* ├── microcode/
|
||||
* │ └── bus.json
|
||||
* ├── roms/
|
||||
* │ └── boot.bin # Binary boot ROM (assembled/compiled program)
|
||||
* ├── program.asm # Assembly source (alternative to boot.bin)
|
||||
* ├── program.py # Python source
|
||||
* ├── program.c # C source
|
||||
* ├── program.hex # Hex machine code
|
||||
* └── banks/
|
||||
* └── *.bin
|
||||
*
|
||||
* Build process:
|
||||
* 1. Detect source type (.asm, .py, .c, .hex) in the module directory
|
||||
* 2. Compile/assemble/translate to boot.bin
|
||||
* 3. Run cbecc build to produce .cbeplugin
|
||||
*/
|
||||
public class Toolchain {
|
||||
|
||||
/**
|
||||
* Build a plugin from a source directory, auto-detecting the program source type.
|
||||
*/
|
||||
public static void build(Path sourceDir, Path outputFile) throws IOException, ModuleLoadException {
|
||||
// Check for program source files
|
||||
Path asmSource = sourceDir.resolve("program.asm");
|
||||
Path pySource = sourceDir.resolve("program.py");
|
||||
Path cSource = sourceDir.resolve("program.c");
|
||||
Path cppSource = sourceDir.resolve("program.cpp");
|
||||
Path hexSource = sourceDir.resolve("program.hex");
|
||||
Path bootBin = sourceDir.resolve("roms").resolve("boot.bin");
|
||||
|
||||
// Create roms directory if needed
|
||||
Path romsDir = sourceDir.resolve("roms");
|
||||
if (!Files.exists(romsDir)) {
|
||||
Files.createDirectories(romsDir);
|
||||
}
|
||||
|
||||
// Compile from the appropriate source
|
||||
if (Files.exists(asmSource)) {
|
||||
System.out.println("Toolchain: Assembling " + asmSource.getFileName());
|
||||
Assembler.assembleFile(asmSource, bootBin, sourceDir);
|
||||
} else if (Files.exists(pySource)) {
|
||||
System.out.println("Toolchain: Translating Python " + pySource.getFileName());
|
||||
PythonTranslator.translateFile(pySource, bootBin);
|
||||
} else if (Files.exists(cSource)) {
|
||||
System.out.println("Toolchain: Compiling C " + cSource.getFileName());
|
||||
String arch = getArchFromModule(sourceDir);
|
||||
CCompiler.compile(cSource, bootBin, arch);
|
||||
} else if (Files.exists(cppSource)) {
|
||||
System.out.println("Toolchain: Compiling C++ " + cppSource.getFileName());
|
||||
String arch = getArchFromModule(sourceDir);
|
||||
CCompiler.compile(cppSource, bootBin, arch);
|
||||
} else if (Files.exists(hexSource)) {
|
||||
System.out.println("Toolchain: Loading hex " + hexSource.getFileName());
|
||||
HexLoader.convertFile(hexSource, bootBin);
|
||||
}
|
||||
|
||||
// Run standard cbecc compilation
|
||||
Compiler compiler = new Compiler();
|
||||
compiler.compile(sourceDir, outputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a standalone program file to a flat binary (without module.json).
|
||||
*/
|
||||
public static void compileProgram(Path inputFile, Path outputFile, String type, Path cpuDir)
|
||||
throws IOException {
|
||||
switch (type) {
|
||||
case "asm":
|
||||
Assembler.assembleFile(inputFile, outputFile, cpuDir);
|
||||
break;
|
||||
case "hex":
|
||||
HexLoader.convertFile(inputFile, outputFile);
|
||||
break;
|
||||
case "c":
|
||||
case "cpp":
|
||||
CCompiler.compile(inputFile, outputFile, "native");
|
||||
break;
|
||||
case "py":
|
||||
PythonTranslator.translateFile(inputFile, outputFile);
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unknown source type: " + type +
|
||||
". Supported: asm, hex, c, cpp, py");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getArchFromModule(Path sourceDir) throws IOException {
|
||||
Path moduleJson = sourceDir.resolve("module.json");
|
||||
if (Files.exists(moduleJson)) {
|
||||
String content = new String(Files.readAllBytes(moduleJson));
|
||||
if (content.contains("\"arch\"")) {
|
||||
int idx = content.indexOf("\"arch\"");
|
||||
int valStart = content.indexOf("\"", idx + 7) + 1;
|
||||
int valEnd = content.indexOf("\"", valStart);
|
||||
if (valStart > 0 && valEnd > valStart) {
|
||||
return content.substring(valStart, valEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "tinycpu";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user