Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,51 @@ Positional parameters cannot be passed by name, and keyword parameters cannot be
passed positionally. Keyword arguments also work in cell invocations:
`arc run --cell 'via("met1", 100., n=3)'`.

Structs group related values under named fields. Declare them at module level,
construct them with braces, and read fields with `.`. Every field must be given
exactly once unless `..base` supplies the ones not listed, and a bare `name` is
shorthand for `name: name`:

```rust
struct Size {
w: Float,
h: Float,
}

struct ViaParams {
layer: String,
size: Size,
n: Int,
}

fn grow(s: Size, by: Float) -> Size {
Size { w: s.w + by, ..s }
}

cell via(p: ViaParams) {
let r = rect(p.layer, x0=0., y0=0., w=p.size.w, h=p.size.h);
}

cell top() {
let size = grow(Size { w: 100., h: 50. }, 10.);
let n = 2;
let v = inst(via(ViaParams { layer: "met1", size, n }));
}
```

Struct types are nominal: two structs with the same fields are different types,
and a struct may not contain itself. Fields may have any type, including enums,
other structs, sequences, and tuples. A struct declared in another module is
imported with `use`, and a literal may name its module, as in
`geom::Size { w: 1., h: 2. }`. Inside an `if` condition, a `match` scrutinee, or
a `for` sequence a literal must be parenthesized, since `name {` there begins
the construct's body. Struct values are valid cell arguments, including on the
command line:

```bash
arc run --cell 'via(ViaParams { layer: "met1", size: Size { w: 100., h: 50. }, n: 1 })'
```

GDS imports are zero-argument cells. A module-qualified entry such as
`"macros::sram"` can be referenced as `lib::macros::sram()` or imported with
`use lib::macros::sram;`. Paths in the manifest are relative to `Argon.toml`,
Expand Down
44 changes: 41 additions & 3 deletions crates/compiler/src/ast/annotated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ impl<T: AstMetadata> AnnotatedAst<T> {
Decl::Enum(e) => {
decls.push(Decl::Enum(pass.transform_enum_decl(e)));
}
// Unsupported declaration kinds are rejected by the parser
// before the annotation pass is entered.
Decl::Struct(_) | Decl::Constant(_) => {}
Decl::Struct(s) => {
decls.push(Decl::Struct(pass.transform_struct_decl(s)));
}
// Constant declarations are rejected by the parser before the
// annotation pass is entered.
Decl::Constant(_) => {}
}
}

Expand Down Expand Up @@ -133,6 +136,24 @@ impl<S, T: AstMetadata> AstTransformer for AstAnnotationPass<S, T> {
input.metadata.clone()
}

fn dispatch_struct_decl(
&mut self,
input: &super::StructDecl<Self::InputS, Self::InputMetadata>,
_name: &super::Ident<Self::OutputS, Self::OutputMetadata>,
_fields: &[super::StructField<Self::OutputS, Self::OutputMetadata>],
) -> <Self::OutputMetadata as AstMetadata>::StructDecl {
input.metadata.clone()
}

fn dispatch_struct_field(
&mut self,
input: &super::StructField<Self::InputS, Self::InputMetadata>,
_name: &super::Ident<Self::OutputS, Self::OutputMetadata>,
_ty: &super::TySpec<Self::OutputS, Self::OutputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::StructField {
input.metadata.clone()
}

fn dispatch_cell_decl(
&mut self,
input: &super::CellDecl<Self::InputS, Self::InputMetadata>,
Expand Down Expand Up @@ -236,6 +257,23 @@ impl<S, T: AstMetadata> AstTransformer for AstAnnotationPass<S, T> {
input.metadata.clone()
}

fn dispatch_struct_lit_expr(
&mut self,
input: &super::StructLitExpr<Self::InputS, Self::InputMetadata>,
_path: &super::IdentPath<Self::OutputS, Self::OutputMetadata>,
_fields: &[super::StructLitField<Self::OutputS, Self::OutputMetadata>],
_base: &Option<super::Expr<Self::OutputS, Self::OutputMetadata>>,
) -> <Self::OutputMetadata as AstMetadata>::StructLitExpr {
input.metadata.clone()
}

fn dispatch_struct_lit_path(
&mut self,
input: &super::IdentPath<Self::InputS, Self::InputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::IdentPath {
input.metadata.clone()
}

fn dispatch_field_access_expr(
&mut self,
input: &super::FieldAccessExpr<Self::InputS, Self::InputMetadata>,
Expand Down
127 changes: 126 additions & 1 deletion crates/compiler/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub struct StructDecl<S, T: AstMetadata> {
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct StructField<S, T: AstMetadata> {
pub name: Ident<S, T>,
pub ty: Ident<S, T>,
pub ty: TySpec<S, T>,
pub span: cfgrammar::Span,
pub metadata: T::StructField,
}
Expand Down Expand Up @@ -263,6 +263,7 @@ pub enum Expr<S, T: AstMetadata> {
Scope(Box<Scope<S, T>>),
Cast(Box<CastExpr<S, T>>),
Tuple(TupleExpr<S, T>),
StructLit(Box<StructLitExpr<S, T>>),
}

#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
Expand Down Expand Up @@ -390,6 +391,31 @@ pub struct TupleExpr<S, T: AstMetadata> {
pub metadata: T::TupleExpr,
}

/// A struct literal, `Name { field: value, .. }`.
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct StructLitExpr<S, T: AstMetadata> {
/// The struct being constructed, optionally module-qualified.
pub path: IdentPath<S, T>,
pub fields: Vec<StructLitField<S, T>>,
/// The `..base` expression, from which every field not listed is taken.
pub base: Option<Expr<S, T>>,
pub span: cfgrammar::Span,
pub metadata: T::StructLitExpr,
}

/// One `field: value` entry of a [`StructLitExpr`].
#[derive_where(Debug, Clone, Serialize, Deserialize; S)]
pub struct StructLitField<S, T: AstMetadata> {
pub name: Ident<S, T>,
/// For the shorthand `field` (no value), an [`Expr::IdentPath`] naming
/// `field` at the same span as `name`.
pub value: Expr<S, T>,
/// Whether the field was written as the shorthand `field` rather than
/// `field: value`.
pub shorthand: bool,
pub span: cfgrammar::Span,
}

impl<S, T: AstMetadata> Expr<S, T> {
pub fn span(&self) -> cfgrammar::Span {
match self {
Expand All @@ -412,6 +438,7 @@ impl<S, T: AstMetadata> Expr<S, T> {
Self::Scope(x) => x.span,
Self::Cast(x) => x.span,
Self::Tuple(x) => x.span,
Self::StructLit(x) => x.span,
}
}
}
Expand Down Expand Up @@ -443,6 +470,7 @@ pub trait AstMetadata {
type Typ: Debug + Clone + Serialize + DeserializeOwned;
type CastExpr: Debug + Clone + Serialize + DeserializeOwned;
type TupleExpr: Debug + Clone + Serialize + DeserializeOwned;
type StructLitExpr: Debug + Clone + Serialize + DeserializeOwned;
}

pub trait AstTransformer {
Expand All @@ -465,6 +493,18 @@ pub trait AstTransformer {
name: &Ident<Self::OutputS, Self::OutputMetadata>,
variants: &[Ident<Self::OutputS, Self::OutputMetadata>],
) -> <Self::OutputMetadata as AstMetadata>::EnumDecl;
fn dispatch_struct_decl(
&mut self,
input: &StructDecl<Self::InputS, Self::InputMetadata>,
name: &Ident<Self::OutputS, Self::OutputMetadata>,
fields: &[StructField<Self::OutputS, Self::OutputMetadata>],
) -> <Self::OutputMetadata as AstMetadata>::StructDecl;
fn dispatch_struct_field(
&mut self,
input: &StructField<Self::InputS, Self::InputMetadata>,
name: &Ident<Self::OutputS, Self::OutputMetadata>,
ty: &TySpec<Self::OutputS, Self::OutputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::StructField;
fn dispatch_cell_decl(
&mut self,
input: &CellDecl<Self::InputS, Self::InputMetadata>,
Expand Down Expand Up @@ -536,6 +576,23 @@ pub trait AstTransformer {
input: &TupleExpr<Self::InputS, Self::InputMetadata>,
items: &[Expr<Self::OutputS, Self::OutputMetadata>],
) -> <Self::OutputMetadata as AstMetadata>::TupleExpr;
fn dispatch_struct_lit_expr(
&mut self,
input: &StructLitExpr<Self::InputS, Self::InputMetadata>,
path: &IdentPath<Self::OutputS, Self::OutputMetadata>,
fields: &[StructLitField<Self::OutputS, Self::OutputMetadata>],
base: &Option<Expr<Self::OutputS, Self::OutputMetadata>>,
) -> <Self::OutputMetadata as AstMetadata>::StructLitExpr;
/// Metadata for the path of a struct literal.
///
/// The path names a declaration, not a variable or an enum variant, so it
/// is resolved by [`Self::dispatch_struct_lit_expr`] and deliberately
/// bypasses [`Self::dispatch_ident_path`], which would read a qualified
/// path's last two segments as `Enum::Variant`.
fn dispatch_struct_lit_path(
&mut self,
input: &IdentPath<Self::InputS, Self::InputMetadata>,
) -> <Self::OutputMetadata as AstMetadata>::IdentPath;
fn dispatch_field_access_expr(
&mut self,
input: &FieldAccessExpr<Self::InputS, Self::InputMetadata>,
Expand Down Expand Up @@ -663,6 +720,38 @@ pub trait AstTransformer {
metadata,
}
}
fn transform_struct_decl(
&mut self,
input: &StructDecl<Self::InputS, Self::InputMetadata>,
) -> StructDecl<Self::OutputS, Self::OutputMetadata> {
let name = self.transform_ident(&input.name);
let fields = input
.fields
.iter()
.map(|field| self.transform_struct_field(field))
.collect_vec();
let metadata = self.dispatch_struct_decl(input, &name, &fields);
StructDecl {
name,
fields,
span: input.span,
metadata,
}
}
fn transform_struct_field(
&mut self,
input: &StructField<Self::InputS, Self::InputMetadata>,
) -> StructField<Self::OutputS, Self::OutputMetadata> {
let name = self.transform_ident(&input.name);
let ty = self.transform_ty_spec(&input.ty);
let metadata = self.dispatch_struct_field(input, &name, &ty);
StructField {
name,
ty,
span: input.span,
metadata,
}
}
fn transform_cell_decl(
&mut self,
input: &CellDecl<Self::InputS, Self::InputMetadata>,
Expand Down Expand Up @@ -1058,6 +1147,41 @@ pub trait AstTransformer {
}
}

fn transform_struct_lit_expr(
&mut self,
input: &StructLitExpr<Self::InputS, Self::InputMetadata>,
) -> StructLitExpr<Self::OutputS, Self::OutputMetadata> {
let path = IdentPath {
path: input
.path
.path
.iter()
.map(|ident| self.transform_ident(ident))
.collect(),
metadata: self.dispatch_struct_lit_path(&input.path),
span: input.path.span,
};
let fields = input
.fields
.iter()
.map(|field| StructLitField {
name: self.transform_ident(&field.name),
value: self.transform_expr(&field.value),
shorthand: field.shorthand,
span: field.span,
})
.collect_vec();
let base = input.base.as_ref().map(|base| self.transform_expr(base));
let metadata = self.dispatch_struct_lit_expr(input, &path, &fields, &base);
StructLitExpr {
path,
fields,
base,
span: input.span,
metadata,
}
}

fn transform_string_literal(
&mut self,
input: &StringLiteral<Self::InputS>,
Expand Down Expand Up @@ -1103,6 +1227,7 @@ pub trait AstTransformer {
Expr::Scope(scope) => Expr::Scope(Box::new(self.transform_scope(scope))),
Expr::Cast(cast) => Expr::Cast(Box::new(self.transform_cast(cast))),
Expr::Tuple(tuple) => Expr::Tuple(self.transform_tuple_expr(tuple)),
Expr::StructLit(lit) => Expr::StructLit(Box::new(self.transform_struct_lit_expr(lit))),
}
}
}
2 changes: 1 addition & 1 deletion crates/compiler/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ mod tests {

#[test]
fn unsupported_source_is_a_diagnostic_not_a_panic() {
let source = temp_source("unsupported", "struct Point {}\n");
let source = temp_source("unsupported", "const X: Int = 1;\n");
let diagnostic = render_failed(failed(check_args(source)));
assert!(
diagnostic.contains("error: error during parsing"),
Expand Down
Loading
Loading