diff --git a/ohdlc/.github/workflows/compiler_ci.yml b/ohdlc/.github/workflows/compiler_ci.yml new file mode 100644 index 0000000..8fd4fe7 --- /dev/null +++ b/ohdlc/.github/workflows/compiler_ci.yml @@ -0,0 +1,32 @@ +name: "Compiler CI" + +on: + push: + paths: + - "ohdlc/**" + - ".github/workflows/**" + pull_request: + paths: + - "ohdlc/**" + - ".github/workflows/**" + merge_group: + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + name: "[Compiler] Cargo Build & Test" + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + defaults: + run: + working-directory: ohdlc + steps: + - uses: actions/checkout@v3 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: cargo build --verbose + - run: cargo test --verbose diff --git a/ohdlc/Cargo.lock b/ohdlc/Cargo.lock index 71c6d3d..7d3f974 100644 --- a/ohdlc/Cargo.lock +++ b/ohdlc/Cargo.lock @@ -81,6 +81,18 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -105,6 +117,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "fnv" version = "1.0.7" @@ -132,6 +150,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "insta" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -165,6 +195,12 @@ version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.12" @@ -221,6 +257,7 @@ dependencies = [ "ariadne", "bumpalo", "deref-derive", + "insta", "lasso", "logos", "once_cell", @@ -285,6 +322,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + [[package]] name = "smallvec" version = "1.13.2" diff --git a/ohdlc/Cargo.toml b/ohdlc/Cargo.toml index 956b74d..6d8019b 100644 --- a/ohdlc/Cargo.toml +++ b/ohdlc/Cargo.toml @@ -11,3 +11,6 @@ lasso = { version = "0.7.2", features = ["multi-threaded"] } logos = "0.14.0" once_cell = "1.19.0" surotto = { git = "https://github.com/DasLixou/surotto" } + +[dev-dependencies] +insta = "1.38.0" diff --git a/ohdlc/src/lib.rs b/ohdlc/src/lib.rs new file mode 100644 index 0000000..6314e02 --- /dev/null +++ b/ohdlc/src/lib.rs @@ -0,0 +1,17 @@ +#![deny(rust_2018_idioms)] + +use message::Messages; + +// TODO: can we get unused errors back? :3 +pub mod ast; +pub mod ir; +pub mod lexer; +pub mod message; +pub mod parser; +pub mod span; +pub mod symbol; + +pub static MESSAGES: Messages = Messages::new(); + +#[derive(Clone)] +pub struct Source<'s>(pub String, pub &'s str); diff --git a/ohdlc/src/main.rs b/ohdlc/src/main.rs index d819f92..c75f2e2 100644 --- a/ohdlc/src/main.rs +++ b/ohdlc/src/main.rs @@ -2,10 +2,8 @@ use ariadne::{Label, Report}; use bumpalo::Bump; -use message::Messages; -use parser::Parser; -use crate::{ +use ohdlc::{ ir::{ import_bucket::ImportBucket, name_lookup::NameLookup, @@ -15,21 +13,10 @@ use crate::{ }, }, lexer::Lexer, + parser::Parser, + Source, MESSAGES, }; -mod ast; -mod ir; -mod lexer; -mod message; -mod parser; -mod span; -mod symbol; - -static MESSAGES: Messages = Messages::new(); - -#[derive(Clone)] -pub struct Source<'s>(pub String, pub &'s str); - fn main() -> Result<(), ()> { let source = Source("work.ohd".to_owned(), include_str!("work.ohd")); diff --git a/ohdlc/tests/basic.ohd b/ohdlc/tests/basic.ohd new file mode 100644 index 0000000..b0a2403 --- /dev/null +++ b/ohdlc/tests/basic.ohd @@ -0,0 +1,78 @@ +mod std { + enum Logical { + High, + Low, + } +} +use ::std::Logical; + +mod x { + use y; +} + +mod y { + use x; +} + +record MyRecord { + a: Logical, + b: Logical, +} + +enum MyEnum { + A, + B, + C, + D, + E, +} + +entity HalfAdder { + in a: Logical, + in b: Logical, + out o: Logical, + out c: Logical, +} + +arch RTL for HalfAdder { + o <= a xor b; + c <= a and b; +} + +use a::b::Lol; + +mod a { + use other::b; +} + +mod other { + mod b { + entity Lol { } + } +} + +entity FullAdder { + in a: Logical, + in b: Logical, + in ci: Logical, + out o: Logical, + out co: Logical, +} + +arch RTL for FullAdder { + HalfAdder(RTL) { + a <= a, + b <= b, + o => signal s, + c => signal cb, + } + + HalfAdder(RTL) { + a <= s, + b <= ci, + o => o, + c => signal cc, + } + + co <= cb or cc; +} \ No newline at end of file diff --git a/ohdlc/tests/basic.rs b/ohdlc/tests/basic.rs new file mode 100644 index 0000000..164ad40 --- /dev/null +++ b/ohdlc/tests/basic.rs @@ -0,0 +1,109 @@ +use ariadne::{Label, Report}; +use bumpalo::Bump; +use insta::assert_debug_snapshot; +use ohdlc::{ + ir::{ + import_bucket::ImportBucket, + name_lookup::NameLookup, + registry::Registry, + stages::{ + flatten_lookup::FlattenLookupStage, refine_types::RefineTypesStage, rough::RoughStage, + }, + }, + lexer::Lexer, + parser::Parser, + Source, MESSAGES, +}; + +#[test] +fn main() { + let source = Source("work.ohd".to_owned(), include_str!("basic.ohd")); + + println!("[STAGE] Lexer"); + + let lexer = Lexer::new(&source.1); + report_messages(&source); + let lexer = lexer.unwrap(); + + println!("[STAGE] Parser"); + + let parser_arena = Bump::new(); + + let mut parser = Parser::new(&parser_arena, source.clone(), lexer); + + let root = parser.parse(); + let root = match root { + Ok(tree) => tree, + Err(messages) => { + MESSAGES.extend(messages); + report_messages(&source); + panic!(); + } + }; + + assert_debug_snapshot!(root); + + let ir_arena = Bump::new(); + let mut registry = Registry::default(); + let mut name_lookup = NameLookup::new(); + let mut import_bucket = ImportBucket::new(); + + { + let rough = RoughStage { + arena: &ir_arena, + registry: &mut registry, + name_lookup: &mut name_lookup, + import_bucket: &mut import_bucket, + root: &root, + }; + rough.lower(); + report_messages(&source); + } + + let name_lookup = { + let resolve = FlattenLookupStage { + registry: ®istry, + name_lookup, + import_bucket, + resolvables: Vec::new(), + }; + let lookup = resolve.lower(); + report_messages(&source); + lookup + }; + let name_lookup = name_lookup.unwrap(); + + let refined_types = { + let refine_types = RefineTypesStage { + arena: &ir_arena, + name_lookup: &name_lookup, + module_registry: ®istry.modules, + }; + let refined_types = refine_types.lower(registry.types); + report_messages(&source); + Registry { + modules: registry.modules, + types: refined_types, + } + }; + + assert_debug_snapshot!(refined_types); +} + +fn report_messages(source: &Source<'_>) { + MESSAGES.drain(|msg| { + let filename = source.0.as_str(); + + let report = + Report::build(msg.kind, filename, msg.location.0) + .with_message(msg.message) + .with_labels(msg.labels.into_iter().map(|label| { + Label::new((filename, label.span.into())).with_message(label.message) + })) + .finish(); + + report + .eprint((filename, ariadne::Source::from(source.1))) + .unwrap(); + }); +} diff --git a/ohdlc/tests/snapshots/basic__main-2.snap b/ohdlc/tests/snapshots/basic__main-2.snap new file mode 100644 index 0000000..5b06fef --- /dev/null +++ b/ohdlc/tests/snapshots/basic__main-2.snap @@ -0,0 +1,247 @@ +--- +source: tests/basic.rs +expression: refined_types +--- +Registry { + modules: { + ModuleId( + 0, + ): Module { + name: `std`:4..7, + scope: ScopeId( + 1, + ), + }, + ModuleId( + 1, + ): Module { + name: `x`:97..98, + scope: ScopeId( + 2, + ), + }, + ModuleId( + 2, + ): Module { + name: `y`:123..124, + scope: ScopeId( + 3, + ), + }, + ModuleId( + 3, + ): Module { + name: `a`:463..464, + scope: ScopeId( + 4, + ), + }, + ModuleId( + 4, + ): Module { + name: `other`:496..501, + scope: ScopeId( + 5, + ), + }, + ModuleId( + 5, + ): Module { + name: `b`:513..514, + scope: ScopeId( + 6, + ), + }, + }, + types: { + TypeId( + 0, + ): Enum( + Enum { + type_id: TypeId( + 0, + ), + name: `Logical`:20..27, + variants: [ + Variant { + ident: `High`:39..43, + }, + Variant { + ident: `Low`:54..57, + }, + ], + }, + ), + TypeId( + 1, + ): Record( + Record { + type_id: TypeId( + 1, + ), + name: `MyRecord`:152..160, + fields: [ + Field { + name: `a`:168..169, + ty: Some( + TypeId( + 0, + ), + ), + }, + Field { + name: `b`:185..186, + ty: Some( + TypeId( + 0, + ), + ), + }, + ], + }, + ), + TypeId( + 2, + ): Enum( + Enum { + type_id: TypeId( + 2, + ), + name: `MyEnum`:208..214, + variants: [ + Variant { + ident: `A`:222..223, + }, + Variant { + ident: `B`:230..231, + }, + Variant { + ident: `C`:238..239, + }, + Variant { + ident: `D`:246..247, + }, + Variant { + ident: `E`:254..255, + }, + ], + }, + ), + TypeId( + 3, + ): Entity( + Entity { + type_id: TypeId( + 3, + ), + name: `HalfAdder`:270..279, + ports: [ + Port { + kind: Input, + name: `a`:291..292, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Input, + name: `b`:312..313, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Output, + name: `o`:333..334, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Output, + name: `c`:354..355, + ty: Some( + TypeId( + 0, + ), + ), + }, + ], + }, + ), + TypeId( + 4, + ): Entity( + Entity { + type_id: TypeId( + 4, + ), + name: `Lol`:533..536, + ports: [], + }, + ), + TypeId( + 5, + ): Entity( + Entity { + type_id: TypeId( + 5, + ), + name: `FullAdder`:561..570, + ports: [ + Port { + kind: Input, + name: `a`:582..583, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Input, + name: `b`:603..604, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Input, + name: `ci`:624..626, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Output, + name: `o`:646..647, + ty: Some( + TypeId( + 0, + ), + ), + }, + Port { + kind: Output, + name: `co`:667..669, + ty: Some( + TypeId( + 0, + ), + ), + }, + ], + }, + ), + }, +} diff --git a/ohdlc/tests/snapshots/basic__main.snap b/ohdlc/tests/snapshots/basic__main.snap new file mode 100644 index 0000000..e5aae6d --- /dev/null +++ b/ohdlc/tests/snapshots/basic__main.snap @@ -0,0 +1,544 @@ +--- +source: tests/basic.rs +expression: root +--- +[ + Module( + Module { + name: `std`:4..7, + items: [ + Enum( + Enum { + name: `Logical`:20..27, + variants: [ + `High`:39..43, + `Low`:54..57, + ], + }, + ):15..68, + ], + }, + ):0..73, + Use( + Use { + path: Path( + [ + PathSegment( + `std`:76..79, + ), + PathSegment( + `Logical`:81..88, + ), + ], + Root, + ):74..89, + }, + ):70..96, + Module( + Module { + name: `x`:97..98, + items: [ + Use( + Use { + path: Path( + [ + PathSegment( + `y`:110..111, + ), + ], + Local, + ):110..112, + }, + ):106..115, + ], + }, + ):93..122, + Module( + Module { + name: `y`:123..124, + items: [ + Use( + Use { + path: Path( + [ + PathSegment( + `x`:136..137, + ), + ], + Local, + ):136..138, + }, + ):132..141, + ], + }, + ):119..151, + Record( + Record { + name: `MyRecord`:152..160, + fields: [ + Field { + name: `a`:168..169, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:171..178, + ), + ], + Local, + ):171..179, + }:171..179, + }:168..179, + Field { + name: `b`:185..186, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:188..195, + ), + ], + Local, + ):188..196, + }:188..196, + }:185..196, + ], + }, + ):145..207, + Enum( + Enum { + name: `MyEnum`:208..214, + variants: [ + `A`:222..223, + `B`:230..231, + `C`:238..239, + `D`:246..247, + `E`:254..255, + ], + }, + ):203..269, + Entity( + Entity { + name: `HalfAdder`:270..279, + ports: [ + Port { + kind: Input:287..289, + name: `a`:291..292, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:294..301, + ), + ], + Local, + ):294..302, + }:294..302, + }:287..302, + Port { + kind: Input:308..310, + name: `b`:312..313, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:315..322, + ), + ], + Local, + ):315..323, + }:315..323, + }:308..323, + Port { + kind: Output:329..332, + name: `o`:333..334, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:336..343, + ), + ], + Local, + ):336..344, + }:336..344, + }:329..344, + Port { + kind: Output:350..353, + name: `c`:354..355, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:357..364, + ), + ], + Local, + ):357..365, + }:357..365, + }:350..365, + ], + }, + ):263..376, + Arch( + Arch { + name: `RTL`:377..380, + ty: Type { + path: Path( + [ + PathSegment( + `HalfAdder`:385..394, + ), + ], + Local, + ):385..396, + }:385..396, + stmts: [ + Assign( + AssignStmt { + assignee: `o`:402..403, + value: Binary { + left: Primary( + `a`:407..408, + ), + right: Primary( + `b`:413..414, + ), + operator: Xor, + }, + }, + ):402..422, + Assign( + AssignStmt { + assignee: `c`:421..422, + value: Binary { + left: Primary( + `a`:426..427, + ), + right: Primary( + `b`:432..433, + ), + operator: And, + }, + }, + ):421..437, + ], + }, + ):372..444, + Use( + Use { + path: Path( + [ + PathSegment( + `a`:445..446, + ), + PathSegment( + `b`:448..449, + ), + PathSegment( + `Lol`:451..454, + ), + ], + Local, + ):445..455, + }, + ):441..462, + Module( + Module { + name: `a`:463..464, + items: [ + Use( + Use { + path: Path( + [ + PathSegment( + `other`:476..481, + ), + PathSegment( + `b`:483..484, + ), + ], + Local, + ):476..485, + }, + ):472..488, + ], + }, + ):459..495, + Module( + Module { + name: `other`:496..501, + items: [ + Module( + Module { + name: `b`:513..514, + items: [ + Entity( + Entity { + name: `Lol`:533..536, + ports: [], + }, + ):526..547, + ], + }, + ):509..550, + ], + }, + ):492..560, + Entity( + Entity { + name: `FullAdder`:561..570, + ports: [ + Port { + kind: Input:578..580, + name: `a`:582..583, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:585..592, + ), + ], + Local, + ):585..593, + }:585..593, + }:578..593, + Port { + kind: Input:599..601, + name: `b`:603..604, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:606..613, + ), + ], + Local, + ):606..614, + }:606..614, + }:599..614, + Port { + kind: Input:620..622, + name: `ci`:624..626, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:628..635, + ), + ], + Local, + ):628..636, + }:628..636, + }:620..636, + Port { + kind: Output:642..645, + name: `o`:646..647, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:649..656, + ), + ], + Local, + ):649..657, + }:649..657, + }:642..657, + Port { + kind: Output:663..666, + name: `co`:667..669, + ty: Type { + path: Path( + [ + PathSegment( + `Logical`:671..678, + ), + ], + Local, + ):671..679, + }:671..679, + }:663..679, + ], + }, + ):554..690, + Arch( + Arch { + name: `RTL`:691..694, + ty: Type { + path: Path( + [ + PathSegment( + `FullAdder`:699..708, + ), + ], + Local, + ):699..710, + }:699..710, + stmts: [ + Place( + PlaceStmt { + entity_ty: Type { + path: Path( + [ + PathSegment( + `HalfAdder`:716..725, + ), + ], + Local, + ):716..726, + }:716..726, + arch_ty: Type { + path: Path( + [ + PathSegment( + `RTL`:726..729, + ), + ], + Local, + ):726..730, + }:726..730, + links: [ + PlaceLink { + src: `a`:742..743, + arrow_span: Span( + 744, + 746, + ), + link: Ingoing( + Primary( + `a`:747..748, + ):747..749, + ), + }:742..749, + PlaceLink { + src: `b`:759..760, + arrow_span: Span( + 761, + 763, + ), + link: Ingoing( + Primary( + `b`:764..765, + ):764..766, + ), + }:759..766, + PlaceLink { + src: `o`:776..777, + arrow_span: Span( + 778, + 780, + ), + link: Outgoing( + NewSignal( + `s`:788..789, + ):781..790, + ), + }:776..790, + PlaceLink { + src: `c`:800..801, + arrow_span: Span( + 802, + 804, + ), + link: Outgoing( + NewSignal( + `cb`:812..814, + ):805..815, + ), + }:800..815, + ], + }, + ):716..839, + Place( + PlaceStmt { + entity_ty: Type { + path: Path( + [ + PathSegment( + `HalfAdder`:830..839, + ), + ], + Local, + ):830..840, + }:830..840, + arch_ty: Type { + path: Path( + [ + PathSegment( + `RTL`:840..843, + ), + ], + Local, + ):840..844, + }:840..844, + links: [ + PlaceLink { + src: `a`:856..857, + arrow_span: Span( + 858, + 860, + ), + link: Ingoing( + Primary( + `s`:861..862, + ):861..863, + ), + }:856..863, + PlaceLink { + src: `b`:873..874, + arrow_span: Span( + 875, + 877, + ), + link: Ingoing( + Primary( + `ci`:878..880, + ):878..881, + ), + }:873..881, + PlaceLink { + src: `o`:891..892, + arrow_span: Span( + 893, + 895, + ), + link: Outgoing( + Ref( + `o`:896..897, + ):896..898, + ), + }:891..898, + PlaceLink { + src: `c`:908..909, + arrow_span: Span( + 910, + 912, + ), + link: Outgoing( + NewSignal( + `cc`:920..922, + ):913..923, + ), + }:908..923, + ], + }, + ):830..940, + Assign( + AssignStmt { + assignee: `co`:938..940, + value: Binary { + left: Primary( + `cb`:944..946, + ), + right: Primary( + `cc`:950..952, + ), + operator: Or, + }, + }, + ):938..956, + ], + }, + ):686..956, +]