Browse Source

Basic compiler. More error detection/handling needed

master
Stephen 3 months ago
parent
commit
05078c2de1
13 changed files with 473 additions and 58 deletions
  1. +1
    -0
      .gitignore
  2. +25
    -25
      example.es
  3. +284
    -0
      src/compiler.rs
  4. +75
    -0
      src/electro_graph.rs
  5. +2
    -2
      src/electro_program.rs
  6. +4
    -0
      src/error_handler.rs
  7. +1
    -0
      src/external_module.rs
  8. +3
    -3
      src/internal_module.rs
  9. +9
    -1
      src/main.rs
  10. +16
    -7
      src/module_param.rs
  11. +6
    -4
      src/parser.rs
  12. +40
    -16
      src/stmt.rs
  13. +7
    -0
      src/visitor.rs

+ 1
- 0
.gitignore View File

@ -1 +1,2 @@
/target
graph.*

+ 25
- 25
example.es View File

@ -23,41 +23,41 @@ module full_adder(input a, input b, input carryIn, output sum, output carryOut)
# a byte full adder
# takes in busses(arrays of signals)
module byte_adder(input A[0:7], input B[0:7], input carryIn, output Sum[0:7], output carryOut)
module byte_adder(input A[0:8], input B[0:8], input carryIn, output Sum[0:8], output carryOut)
{
signal Carries[0:6];
full_adder(A[0], B[0], carryIn, Sum[0], carry[0]);
full_adder(A[1], B[1], carry[0], Sum[1], carry[1]);
full_adder(A[2], B[2], carry[1], Sum[2], carry[2]);
full_adder(A[3], B[3], carry[2], Sum[3], carry[3]);
full_adder(A[4], B[4], carry[3], Sum[4], carry[4]);
full_adder(A[5], B[5], carry[4], Sum[5], carry[5]);
full_adder(A[6], B[6], carry[5], Sum[6], carry[6]);
full_adder(A[7], B[7], carry[6], Sum[7], carryOut);
signal Carries[0:7];
full_adder(A[0], B[0], carryIn, Sum[0], Carries[0]);
full_adder(A[1], B[1], Carries[0], Sum[1], Carries[1]);
full_adder(A[2], B[2], Carries[1], Sum[2], Carries[2]);
full_adder(A[3], B[3], Carries[2], Sum[3], Carries[3]);
full_adder(A[4], B[4], Carries[3], Sum[4], Carries[4]);
full_adder(A[5], B[5], Carries[4], Sum[5], Carries[5]);
full_adder(A[6], B[6], Carries[5], Sum[6], Carries[6]);
full_adder(A[7], B[7], Carries[6], Sum[7], carryOut);
}
# this is what gets converted into a schematic
# it's just a re-export of the byte adder.
module main(input A[0:7], input B[0:7], input cIn, output Sum[0:7], output cOut)
module main(input A[0:8], input B[0:8], input cIn, output Sum[0:8], output cOut)
{
byte_adder(A, B, cIn, Sum, cOut);
}
# tests
# tests (TODO)
# we need to define how and, or, xor work
# compiler will look at the signals to figure out what are inputs and what are outputs
expect and(0, 0, 0);
expect and(0, 1, 0);
expect and(1, 0, 0);
expect and(1, 1, 1); # can also write `expect and(HIGH, HIGH, HIGH)` - HIGH = 1 and LOW = 0; The compiler doesn't care
## expect and(0, 0, 0);
## expect and(0, 1, 0);
## expect and(1, 0, 0);
## expect and(1, 1, 1); # can also write `expect and(HIGH, HIGH, HIGH)` - HIGH = 1 and LOW = 0; The compiler doesn't care
test "adder works" # start with "test", then the name of the test case
{
signal Sum[0:7];
signal cOut;
# numbers are automatically converted into bus values
byte_adder(2, 3, LOW, Sum, cOut);
assert_equal(5, Sum);
assert_equal(LOW, cOut);
}
## test "adder works" # start with "test", then the name of the test case
## {
## signal Sum[0:7];
## signal cOut;
## # numbers are automatically converted into bus values
## byte_adder(2, 3, LOW, Sum, cOut);
## assert_equal(5, Sum);
## assert_equal(LOW, cOut);
## }

+ 284
- 0
src/compiler.rs View File

@ -0,0 +1,284 @@
use crate::electro_graph::ElectroGraph;
use crate::electro_program::ElectroProgram;
use crate::error_handler::ErrorHandler;
use crate::internal_module::InternalModule;
use crate::module_param::{BusMetadata, ModuleParam, ModuleParamType};
use crate::stmt::Connection;
use crate::visitor::StmtVisitor;
use std::collections::HashMap;
use std::mem;
// Turns an AST(ElectroProgram) into a schematic graph(ElectroGraph)
pub struct Compiler {
program: ElectroProgram,
error_handler: ErrorHandler,
signals: HashMap<String, usize>, // keeps track of signals that are in scope
busses: HashMap<String, (BusMetadata, Vec<usize>)>, // a bus is just a vec of signals
graph: ElectroGraph,
}
impl Compiler {
pub fn new(program: ElectroProgram) -> Self {
Self {
program,
error_handler: ErrorHandler::new(),
signals: HashMap::new(),
busses: HashMap::new(),
graph: ElectroGraph::new(),
}
}
pub fn compile(mut self) -> Result<ElectroGraph, Vec<String>> {
let main_module = self
.program
.internal_modules
.get("main")
.map(InternalModule::to_owned);
match main_module {
Some(im) => {
let mut main_args = Vec::new();
for param in &im.params {
match &param.param_type {
ModuleParamType::Input | ModuleParamType::Output => {
let signal_id = self.define_signal(param.name.clone());
self.graph.new_main_signal(param.name.clone(), signal_id);
main_args.push(Connection::SignalFromSignal {
signal_name: param.name.clone(),
});
}
ModuleParamType::InputBus(meta) | ModuleParamType::OutputBus(meta) => {
let ids = self.define_bus(param.name.clone(), *meta);
self.graph
.new_main_bus(param.name.clone(), &ids, meta.start_index);
main_args.push(Connection::BusFromBus {
bus_name: param.name.clone(),
start_index: meta.start_index,
end_index: meta.end_index,
});
}
};
}
self.compile_internal_module(&im, &main_args);
Ok(self.graph)
}
None => {
self.error_handler
.lineless_error("Could not find 'main' module.");
Err(self.error_handler.errors)
}
}
}
// Returns a vec of signal ids, corresponding to the params of the internal module
fn compile_internal_module(&mut self, im: &InternalModule, arguments: &[Connection]) {
let mut original_signals = HashMap::new();
mem::swap(&mut original_signals, &mut self.signals);
let mut original_busses = HashMap::new();
mem::swap(&mut original_busses, &mut self.busses);
// Create param signals, connected to the arguments
// We do this by having the params have the same signal ids as the arguments
for (i, conn) in arguments.iter().enumerate() {
match conn {
Connection::SignalFromSignal { signal_name } => match &im.params[i].param_type {
ModuleParamType::Input | ModuleParamType::Output => {
self.new_signal_from_signal(signal_name, &im.params[i], &original_signals);
}
ModuleParamType::InputBus(meta) | ModuleParamType::OutputBus(meta) => {
self.new_bus_from_bus(
signal_name,
meta.start_index,
meta.end_index,
&im.params[i],
&mut original_busses,
);
}
},
Connection::SignalFromBus { bus_name, index } => {
let (bus_stmt, signal_ids) = match original_busses.get(bus_name) {
Some(x) => x,
None => todo!("{} {:?}", bus_name, original_busses), // TODO throw error here
};
// First, make sure that the index is actually valid
if *index < bus_stmt.start_index || *index >= bus_stmt.end_index {
todo!(
"Bus index is too high or too low ({} vs {:?})",
index,
bus_stmt
);
}
let vec_idx = index - bus_stmt.start_index;
self.signals
.insert(im.params[i].name.clone(), signal_ids[vec_idx as usize]);
}
Connection::BusFromBus {
bus_name,
start_index,
end_index,
} => self.new_bus_from_bus(
bus_name,
*start_index,
*end_index,
&im.params[i],
&mut original_busses,
),
_ => todo!(),
}
}
for stmt in im.stmts.clone() {
stmt.accept(self);
}
self.signals = original_signals;
self.busses = original_busses;
}
// returns true if success
// returns false if we couldn't find the source signal
fn new_signal_from_signal(
&mut self,
source_name: &String,
param: &ModuleParam,
original_signals: &HashMap<String, usize>,
) {
// TODO assert this is linking a signal and a signal
// or a bus and a bus
// Get signal id from original_signals
let id = match original_signals.get(source_name) {
Some(signal) => *signal,
None => {
todo!(); // could not find signal
}
};
self.signals.insert(param.name.clone(), id);
}
fn new_bus_from_bus(
&mut self,
source_name: &String,
source_start: i32,
source_end: i32,
param: &ModuleParam,
original_busses: &mut HashMap<String, (BusMetadata, Vec<usize>)>,
) {
let param_metadata = match param.param_type {
ModuleParamType::Input | ModuleParamType::Output => {
todo!("param mismatch!");
} // TODO throw proper error
ModuleParamType::InputBus(meta) | ModuleParamType::OutputBus(meta) => meta,
};
if source_end - source_start != param_metadata.end_index - param_metadata.start_index {
todo!(
"Param length mismatch {} vs {}",
source_end - source_start,
param_metadata.end_index - param_metadata.start_index
); // TODO throw proper error (param mismatch or something)
}
// Get signal id vec from original_busses
let (_, ids) = match original_busses.get(source_name) {
Some(x) => x,
None => todo!(), // TODO throw proper error - couldn't find bus
};
self.busses
.insert(param.name.clone(), (param_metadata, ids.to_owned()));
}
fn define_signal(&mut self, name: String) -> usize {
let id = self.graph.new_signal();
self.signals.insert(name, id);
id
}
fn define_bus(&mut self, name: String, metadata: BusMetadata) -> Vec<usize> {
let mut signal_ids = Vec::new();
for _ in metadata.start_index..metadata.end_index {
signal_ids.push(self.graph.new_signal());
}
self.busses.insert(name, (metadata, signal_ids.clone()));
signal_ids
}
// return signal id from param
fn resolve_signal_param(&self, conn: &Connection) -> usize {
match conn {
Connection::SignalFromSignal { signal_name } => {
match self.signals.get(signal_name) {
Some(sig) => *sig,
None => todo!(), // could not find signal
}
}
Connection::SignalFromBus { bus_name, index } => {
let (bus_stmt, signal_ids) = match self.busses.get(bus_name) {
Some(x) => x,
None => todo!("could not find bus"), // TODO throw error here
};
// First, make sure that the index is actually valid
if *index < bus_stmt.start_index || *index >= bus_stmt.end_index {
todo!("Bus index is too high or too low");
}
let vec_idx = index - bus_stmt.start_index;
signal_ids[vec_idx as usize]
}
Connection::BusFromBus { .. } => {
// throw error
todo!("Expected signal found bus");
}
_ => todo!(),
}
}
}
impl StmtVisitor<()> for Compiler {
fn visit_signal_stmt(&mut self, stmt: &crate::stmt::SignalStmt) -> () {
let signal_id = self.graph.new_signal();
self.signals.insert(stmt.name.clone(), signal_id);
}
fn visit_bus_stmt(&mut self, stmt: &crate::stmt::BusStmt) -> () {
self.define_bus(stmt.name.clone(), stmt.into());
}
fn visit_call_stmt(&mut self, stmt: &crate::stmt::CallStmt) -> () {
if let Some(im) = self
.program
.internal_modules
.get(&stmt.name)
.map(InternalModule::to_owned)
{
self.compile_internal_module(&im, &stmt.args);
} else if let Some(em) = self.program.external_modules.get(&stmt.name) {
if stmt.args.len() != em.params.len() {
todo!("ext module wrong # of args"); // TODO proper error message
}
let arg_ids = stmt
.args
.iter()
.map(|arg| self.resolve_signal_param(arg))
.collect();
self.graph.new_node(em.name.clone(), arg_ids);
} else {
// TODO error handler
todo!();
}
}
}

+ 75
- 0
src/electro_graph.rs View File

@ -0,0 +1,75 @@
use crate::external_module::ExternalModule;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
#[derive(Debug)]
pub struct GraphEdge {
module_id: usize,
signal_id: usize,
}
// Kicad schematic and some other bits.
#[derive(Debug)]
pub struct GraphNode {
name: String,
params: Vec<usize>, // signal ids
}
#[derive(Debug)]
pub struct ElectroGraph {
signal_count: usize, // signals go from 0 to signal_count
nodes: Vec<GraphNode>,
main_signals: HashMap<usize, String>, // Signals that are exposed publically
}
impl ElectroGraph {
pub fn new() -> Self {
Self {
signal_count: 0,
nodes: Vec::new(),
main_signals: HashMap::new(),
}
}
pub fn new_node(&mut self, name: String, params: Vec<usize>) {
self.nodes.push(GraphNode { name, params });
}
pub fn new_signal(&mut self) -> usize {
let id = self.signal_count;
self.signal_count += 1;
id
}
pub fn new_main_signal(&mut self, name: String, id: usize) {
self.main_signals.insert(id, name);
}
pub fn new_main_bus(&mut self, name: String, ids: &[usize], start_index: i32) {
for (i, id) in ids.iter().enumerate() {
self.new_main_signal(format!("{}[{}]", name, i + start_index as usize), *id);
}
}
// for debugging
pub fn save_graphviz(&self) {
let mut output = "graph {".to_string();
for (i, node) in self.nodes.iter().enumerate() {
for (j, param) in node.params.iter().enumerate() {
output += &format!("{} -- {}_{} [label=\"{}\"];\r\n", param, node.name, i, j);
}
}
// main params
for (id, name) in &self.main_signals {
output += &format!("\"{}\" [fillcolor = yellow, style=filled]", name);
output += &format!("\"{}\" -- {}\r\n", name, id);
}
output += "}\r\n";
let mut file = File::create("graph.txt").unwrap();
file.write_all(output.as_bytes()).unwrap();
}
}

+ 2
- 2
src/electro_program.rs View File

@ -4,8 +4,8 @@ use std::collections::HashMap;
// Represents the AST of an electroscript file.
pub struct ElectroProgram {
internal_modules: HashMap<String, InternalModule>,
external_modules: HashMap<String, ExternalModule>,
pub internal_modules: HashMap<String, InternalModule>,
pub external_modules: HashMap<String, ExternalModule>,
}
impl ElectroProgram {


+ 4
- 0
src/error_handler.rs View File

@ -14,6 +14,10 @@ impl ErrorHandler {
.push(format!("Error on line {}: {}", line, message));
}
pub fn lineless_error(&mut self, msg: &str) {
self.errors.push(format!("Error in script: {}", msg));
}
pub fn error_token(&mut self, token: &Token, msg: &str) {
if token.token_type == TokenType::Eof {
self.report(token.line, "at end", msg);


+ 1
- 0
src/external_module.rs View File

@ -1,5 +1,6 @@
use crate::module_param::ModuleParamType;
#[derive(Debug)]
pub struct ExternalModule {
pub name: String,
pub params: Vec<ModuleParamType>,


+ 3
- 3
src/internal_module.rs View File

@ -1,11 +1,11 @@
use crate::module_param::ModuleParam;
use crate::stmt::Stmt;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct InternalModule {
pub name: String,
params: Vec<ModuleParam>,
stmts: Vec<Stmt>,
pub params: Vec<ModuleParam>,
pub stmts: Vec<Stmt>,
}
impl InternalModule {


+ 9
- 1
src/main.rs View File

@ -1,3 +1,5 @@
mod compiler;
mod electro_graph;
mod electro_program;
mod error_handler;
mod external_module;
@ -7,7 +9,9 @@ mod parser;
mod scanner;
mod stmt;
mod token;
mod visitor;
use crate::compiler::Compiler;
use crate::parser::Parser;
use crate::scanner::Scanner;
use std::env;
@ -40,7 +44,11 @@ fn process_file(file_name: &str) -> Result<(), Vec<String>> {
let tokens = scanner.scan_tokens()?;
// println!("{:?}", tokens);
let parser = Parser::new(tokens);
parser.parse();
let program = parser.parse()?;
let graph = Compiler::new(program).compile()?;
println!("{:?}", graph);
graph.save_graphviz();
Ok(())
}

+ 16
- 7
src/module_param.rs View File

@ -1,9 +1,9 @@
use crate::token::{Token, TokenType};
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct BusMetadata {
start_index: i32,
end_index: i32,
pub start_index: i32,
pub end_index: i32,
}
impl BusMetadata {
@ -19,7 +19,16 @@ impl BusMetadata {
}
}
#[derive(Debug)]
impl From<&crate::stmt::BusStmt> for BusMetadata {
fn from(stmt: &crate::stmt::BusStmt) -> Self {
Self {
start_index: stmt.start_index,
end_index: stmt.end_index,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum ModuleParamType {
Input,
Output,
@ -27,10 +36,10 @@ pub enum ModuleParamType {
OutputBus(BusMetadata),
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ModuleParam {
param_type: ModuleParamType,
name: String,
pub param_type: ModuleParamType,
pub name: String,
line: usize,
}


+ 6
- 4
src/parser.rs View File

@ -23,7 +23,7 @@ impl Parser {
}
}
pub fn parse(mut self) -> Option<ElectroProgram> {
pub fn parse(mut self) -> Result<ElectroProgram, Vec<String>> {
let mut ep = ElectroProgram::new();
while !self.is_at_end() {
@ -32,9 +32,11 @@ impl Parser {
}
}
println!("{}", self.error_handler.errors.join("\n"));
Some(ep)
if !self.error_handler.errors.is_empty() {
Err(self.error_handler.errors)
} else {
Ok(ep)
}
}
fn try_parse(&mut self, ep: &mut ElectroProgram) -> Option<()> {


+ 40
- 16
src/stmt.rs View File

@ -1,3 +1,6 @@
use crate::visitor::StmtVisitor;
// This is the parallel of an expression
#[derive(Clone, Debug)]
pub enum Connection {
// Also bus from bus where it's a 1:1 mapping
@ -31,6 +34,7 @@ impl Connection {
}
pub fn new_bus_from_bus(bus_name: String, start_index: i32, end_index: i32) -> Self {
println!("Created bus from bus {}", bus_name);
Self::BusFromBus {
bus_name,
start_index,
@ -40,36 +44,56 @@ impl Connection {
}
#[derive(Clone, Debug)]
pub struct SignalStmt {
pub name: String,
}
#[derive(Clone, Debug)]
pub struct BusStmt {
pub name: String,
pub start_index: i32,
pub end_index: i32,
}
#[derive(Clone, Debug)]
pub struct CallStmt {
pub name: String,
pub args: Vec<Connection>,
}
#[derive(Clone, Debug)]
pub enum Stmt {
Signal {
name: String,
},
Bus {
name: String,
start_index: i32,
end_index: i32,
},
Call {
name: String,
args: Vec<Connection>,
},
Signal(SignalStmt),
Bus(BusStmt),
Call(CallStmt),
}
impl Stmt {
pub fn new_signal(name: String) -> Self {
Stmt::Signal { name }
Stmt::Signal(SignalStmt { name })
}
pub fn new_bus(name: String, start_index: i32, end_index: i32) -> Self {
assert!(start_index <= end_index);
Stmt::Bus {
Stmt::Bus(BusStmt {
name,
start_index,
end_index,
}
})
}
pub fn new_call(name: String, args: Vec<Connection>) -> Self {
Stmt::Call { name, args }
Stmt::Call(CallStmt { name, args })
}
pub fn accept<U, V>(&self, visitor: &mut V) -> U
where
V: StmtVisitor<U>,
{
match self {
Stmt::Signal(x) => visitor.visit_signal_stmt(x),
Stmt::Bus(x) => visitor.visit_bus_stmt(x),
Stmt::Call(x) => visitor.visit_call_stmt(x),
}
}
}

+ 7
- 0
src/visitor.rs View File

@ -0,0 +1,7 @@
use crate::stmt::{BusStmt, CallStmt, SignalStmt};
pub trait StmtVisitor<U> {
fn visit_signal_stmt(&mut self, stmt: &SignalStmt) -> U;
fn visit_bus_stmt(&mut self, stmt: &BusStmt) -> U;
fn visit_call_stmt(&mut self, stmt: &CallStmt) -> U;
}

Loading…
Cancel
Save