From 452bed908030609933210cab412e110bbb361705 Mon Sep 17 00:00:00 2001 From: Alan Machado Date: Thu, 16 Apr 2026 10:29:14 -0300 Subject: [PATCH 1/5] feat: create enum Tokenkind --- frontend/src/lexer/tokens.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/lexer/tokens.rs b/frontend/src/lexer/tokens.rs index c507b944..06b0dc2a 100644 --- a/frontend/src/lexer/tokens.rs +++ b/frontend/src/lexer/tokens.rs @@ -9,6 +9,7 @@ pub struct Token { impl std::fmt::Display for TokenKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let result = match self { + Self::Enum => "enum".to_string(), Self::While => "while".to_string(), Self::CommonComent => "//".to_string(), Self::And => "&&".to_string(), @@ -124,6 +125,7 @@ pub enum TokenKind { Pub, Prop, Alias, + Enum, Object, @@ -141,6 +143,15 @@ impl std::fmt::Display for Token { } impl Token { + pub fn enum_token(pos: usize) -> Self { + Self { + kind: TokenKind::Enum, + span: Span { + end: pos, + start: pos, + } + } + } pub fn comcomment(pos: usize) -> Self { Self { kind: TokenKind::CommonComent, From e92106dc8d93a62a3bab839b725d3ed48fc97e0f Mon Sep 17 00:00:00 2001 From: Alan Machado Date: Thu, 16 Apr 2026 15:27:31 -0300 Subject: [PATCH 2/5] feat: create enum.rs and initializing the parser --- frontend/src/lexer/mod.rs | 4 ++++ frontend/src/parser/enums.rs | 33 +++++++++++++++++++++++++++++++++ frontend/src/parser/mod.rs | 1 + 3 files changed, 38 insertions(+) create mode 100644 frontend/src/parser/enums.rs diff --git a/frontend/src/lexer/mod.rs b/frontend/src/lexer/mod.rs index 18ea04d3..1e26a226 100644 --- a/frontend/src/lexer/mod.rs +++ b/frontend/src/lexer/mod.rs @@ -350,6 +350,10 @@ impl Lexer { idx -= 1; let span = Span { start, end: idx }; match buffer.as_str() { + "enum" => Token { + kind: TokenKind::Enum, + span, + }, "while" => Token { kind: TokenKind::While, span, diff --git a/frontend/src/parser/enums.rs b/frontend/src/parser/enums.rs new file mode 100644 index 00000000..a9308f01 --- /dev/null +++ b/frontend/src/parser/enums.rs @@ -0,0 +1,33 @@ +use color_eyre::eyre::Result; +use common::{ASTDeclaration, ASTDeclarationKind}; + +use crate::lexer::tokens::{Token, TokenKind}; +use crate::parser::Parser; + +impl Parser{ + pub fn parse_enum(&mut self) -> Result { + let enum_token = self.expect(&TokenKind::Enum)?; + let declaration = self.expect(&TokenKind::Identifier(String::new()))?; + self.expect(&TokenKind::LBrace)?; + let data = Vec::new(); + while let TokenKind::RBrace = self.peek()?.kind { + let name = self.expect(&TokenKind::Identifier(String::new()))?; + let comma = self.expect(&TokenKind::Comma)?; + data.push(name); + } + let brace = self.expect(&TokenKind::RBrace)?; + Ok(ASTDeclaration{ + span: Span{ + start: enum_token.span.start, + end: brace.span.end, + }, + kind: ASTDeclarationKind { + + } + } + }; +} + + + + diff --git a/frontend/src/parser/mod.rs b/frontend/src/parser/mod.rs index b70a7913..5d4523d3 100644 --- a/frontend/src/parser/mod.rs +++ b/frontend/src/parser/mod.rs @@ -6,6 +6,7 @@ mod functions; pub mod objects; mod statement; mod types; +mod enums; use color_eyre::eyre::{Report, Result}; use crate::lexer::{ From f4278cefa54ce62597165d4d72afd3f1be7a5bb9 Mon Sep 17 00:00:00 2001 From: Alan Machado Date: Thu, 23 Apr 2026 20:10:19 -0300 Subject: [PATCH 3/5] feat: add EnumDeclaration --- common/src/ast/mod.rs | 4 ++++ frontend/src/hir/mod.rs | 6 ++++++ frontend/src/hir/types/tys.rs | 4 +++- frontend/src/parser/enums.rs | 32 ++++++++++++++++++++------------ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/common/src/ast/mod.rs b/common/src/ast/mod.rs index 31c63625..e3557136 100644 --- a/common/src/ast/mod.rs +++ b/common/src/ast/mod.rs @@ -112,6 +112,10 @@ pub enum ASTDeclarationKind { return_type: GenericIdentifier, body: Vec, }, + EnumDeclaration{ + name: GenericIdentifier, + variants: Vec, + }, } impl ASTExpression { diff --git a/frontend/src/hir/mod.rs b/frontend/src/hir/mod.rs index b74fd2e8..3c759e09 100644 --- a/frontend/src/hir/mod.rs +++ b/frontend/src/hir/mod.rs @@ -172,12 +172,18 @@ impl SlynxHir { /// Hoist the provided `ast` declaration, so no errors of undefined values because declared later may occur fn hoist(&mut self, ast: &ASTDeclaration) -> Result<()> { match &ast.kind { + ASTDeclarationKind::Alias { name, target } => { self.symbols_module.intern(&target.identifier); let symbol = self.symbols_module.intern(&name.identifier); let ty = self.types_module.insert_type(symbol, HirType::Int); self.declarations_module.create_declaration(symbol, ty); } + ASTDeclarationKind::EnumDeclaration { name, variants } => { + let symbols = self.symbols_module.intern(&name.identifier); + let ty = self.types_module.insert_type(symbols, HirType::Enum); + self.declarations_module.create_declaration(symbols, ty); + } ASTDeclarationKind::ObjectDeclaration { name, fields } => { self.hoist_object(name, fields)? } diff --git a/frontend/src/hir/types/tys.rs b/frontend/src/hir/types/tys.rs index d905ef82..fde0c32a 100644 --- a/frontend/src/hir/types/tys.rs +++ b/frontend/src/hir/types/tys.rs @@ -75,7 +75,9 @@ pub enum HirType { ///Equivalent type of `string` in js Str, - + Enum{ + variants: Vec, + }, GenericComponent, ///A type specific for components Component { diff --git a/frontend/src/parser/enums.rs b/frontend/src/parser/enums.rs index a9308f01..84587aa8 100644 --- a/frontend/src/parser/enums.rs +++ b/frontend/src/parser/enums.rs @@ -1,31 +1,39 @@ use color_eyre::eyre::Result; -use common::{ASTDeclaration, ASTDeclarationKind}; +use common::{ASTDeclaration, Span, ASTDeclarationKind}; -use crate::lexer::tokens::{Token, TokenKind}; +use crate::lexer::tokens::{TokenKind}; use crate::parser::Parser; + impl Parser{ pub fn parse_enum(&mut self) -> Result { let enum_token = self.expect(&TokenKind::Enum)?; - let declaration = self.expect(&TokenKind::Identifier(String::new()))?; + let name = self.parse_type()?; self.expect(&TokenKind::LBrace)?; - let data = Vec::new(); + let mut variants = Vec::new(); while let TokenKind::RBrace = self.peek()?.kind { - let name = self.expect(&TokenKind::Identifier(String::new()))?; - let comma = self.expect(&TokenKind::Comma)?; - data.push(name); + let tokens = self.expect(&TokenKind::Identifier(String::new()))?; + if let TokenKind::Identifier(string_name) = tokens.kind{ + variants.push(string_name); + } + if self.peek()?.kind != TokenKind::RBrace{ + self.expect(&TokenKind::Comma)?; + } else{ + let _ = self.expect(&TokenKind::Comma); + } } let brace = self.expect(&TokenKind::RBrace)?; - Ok(ASTDeclaration{ + Ok(ASTDeclaration { span: Span{ start: enum_token.span.start, end: brace.span.end, }, - kind: ASTDeclarationKind { - + kind: ASTDeclarationKind::EnumDeclaration { + name, + variants } - } - }; + }) + } } From bf0088e228e7006447ea89e441344ce3075bdcb9 Mon Sep 17 00:00:00 2001 From: Alan Machado Date: Sat, 25 Apr 2026 15:58:29 -0300 Subject: [PATCH 4/5] feat: add Enum slynx file --- frontend/src/hir/definitions.rs | 8 ++++++++ frontend/src/hir/mod.rs | 7 +++++-- frontend/src/hir/types/tys.rs | 3 ++- slynx/enum.slynx | 11 +++++++++++ tests/enum.rs | 16 ++++++++++++++++ 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 slynx/enum.slynx create mode 100644 tests/enum.rs diff --git a/frontend/src/hir/definitions.rs b/frontend/src/hir/definitions.rs index 923c9ccf..11771944 100644 --- a/frontend/src/hir/definitions.rs +++ b/frontend/src/hir/definitions.rs @@ -2,6 +2,11 @@ use crate::hir::{DeclarationId, ExpressionId, PropertyId, SymbolPointer, TypeId, use common::ast::{Operator, Span}; +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct EnumVariant{ + pub name: SymbolPointer, +} #[derive(Debug)] pub enum SpecializedComponent { Text { @@ -130,4 +135,7 @@ pub enum HirExpressionKind { then_branch: Vec, else_branch: Option>, }, + Enum { + variants: Vec + } } diff --git a/frontend/src/hir/mod.rs b/frontend/src/hir/mod.rs index 3c759e09..5529e992 100644 --- a/frontend/src/hir/mod.rs +++ b/frontend/src/hir/mod.rs @@ -16,7 +16,7 @@ use crate::hir::{ declarations::DeclarationsModule, definitions::{ ComponentMemberDeclaration, HirDeclaration, HirDeclarationKind, HirStatement, - HirStatementKind, + HirStatementKind }, error::{HIRError, HIRErrorKind}, scopes::ScopeModule, @@ -181,7 +181,7 @@ impl SlynxHir { } ASTDeclarationKind::EnumDeclaration { name, variants } => { let symbols = self.symbols_module.intern(&name.identifier); - let ty = self.types_module.insert_type(symbols, HirType::Enum); + let ty = self.types_module.insert_type(symbols, HirType::Enum{variants: variants.clone()}); self.declarations_module.create_declaration(symbols, ty); } ASTDeclarationKind::ObjectDeclaration { name, fields } => { @@ -229,6 +229,9 @@ impl SlynxHir { fn resolve(&mut self, ast: ASTDeclaration) -> Result<()> { match ast.kind { + ASTDeclarationKind::EnumDeclaration { name, variants } => { + + } ASTDeclarationKind::ObjectDeclaration { name, fields } => { self.resolve_object(name, fields, ast.span)? } diff --git a/frontend/src/hir/types/tys.rs b/frontend/src/hir/types/tys.rs index fde0c32a..e23e4be0 100644 --- a/frontend/src/hir/types/tys.rs +++ b/frontend/src/hir/types/tys.rs @@ -2,6 +2,7 @@ use crate::hir::{ TypeId, VariableId, error::{HIRError, HIRErrorKind}, symbols::SymbolPointer, + definitions::EnumVariant }; use common::ast::{GenericIdentifier, VisibilityModifier}; @@ -76,7 +77,7 @@ pub enum HirType { ///Equivalent type of `string` in js Str, Enum{ - variants: Vec, + variants: Vec, }, GenericComponent, ///A type specific for components diff --git a/slynx/enum.slynx b/slynx/enum.slynx new file mode 100644 index 00000000..e5087b05 --- /dev/null +++ b/slynx/enum.slynx @@ -0,0 +1,11 @@ +enum Test{ +A, +B, +C, +D +} + +func main() : void{ +let a = Test.A; +let b = Teste.B; +} \ No newline at end of file diff --git a/tests/enum.rs b/tests/enum.rs new file mode 100644 index 00000000..fb7cd435 --- /dev/null +++ b/tests/enum.rs @@ -0,0 +1,16 @@ +use std::{path::PathBuf, sync::Arc}; + +#[test] +fn test_number_systems() { + let context = + slynx::SlynxContext::new(Arc::new(PathBuf::from("slynx/enum.slynx"))).unwrap(); + let output = context.compile().unwrap(); + + assert_eq!( + output + .output_path() + .extension() + .and_then(|ext| ext.to_str()), + Some("sir") + ); +} \ No newline at end of file From b5803120dea1f37a2e38f7e48041518d9c35f5f5 Mon Sep 17 00:00:00 2001 From: caina Date: Tue, 2 Jun 2026 20:30:36 -0300 Subject: [PATCH 5/5] feat: added hir definitioon of enums --- crates/hir/src/declarations.rs | 8 ++ crates/hir/src/expression.rs | 16 ++-- crates/hir/src/helpers/names.rs | 2 +- crates/hir/src/lib.rs | 6 ++ crates/hir/src/model/declarations.rs | 10 +++ crates/hir/src/model/mod.rs | 7 ++ crates/hir/src/model/types.rs | 7 +- crates/hir/src/modules/declarations.rs | 17 ++++ crates/hir/src/modules/mod.rs | 12 ++- crates/hir/src/names.rs | 13 ++- docs/contributing/styles.md | 119 +++++++++++++++++++++++++ 11 files changed, 207 insertions(+), 10 deletions(-) diff --git a/crates/hir/src/declarations.rs b/crates/hir/src/declarations.rs index 4d83b1b6..bc464489 100644 --- a/crates/hir/src/declarations.rs +++ b/crates/hir/src/declarations.rs @@ -5,6 +5,7 @@ use crate::{ ComponentMemberDeclaration, ComponentProperty, HirDeclaration, HirDeclarationKind, HirStatement, HirStyleUsage, HirType, }, + modules::DeclarationInfo, }; use common::Span; use slynx_parser::{ @@ -13,6 +14,13 @@ use slynx_parser::{ }; impl SlynxHir { + pub(crate) fn hoist_enum(&mut self, name: &str, variants: &[String]) -> DeclarationInfo { + let variants = variants + .iter() + .map(|variant| self.intern_name(variant)) + .collect(); + self.modules.create_enum(name, variants) + } ///Hoists a `stylesheet` declaration pub(crate) fn hoist_stylesheet(&mut self, name: &str, args: &[TypedName]) { self.modules.create_declaration( diff --git a/crates/hir/src/expression.rs b/crates/hir/src/expression.rs index f06dbb5f..0ea61a76 100644 --- a/crates/hir/src/expression.rs +++ b/crates/hir/src/expression.rs @@ -1,9 +1,7 @@ use std::mem::discriminant; use crate::{ - ExpressionId, Result, SlynxHir, SymbolPointer, TypeId, - error::{HIRError, HIRErrorKind}, - model::{FieldMethod, HirExpression, HirExpressionKind, HirStatementKind, HirType}, + ExpressionId, HirDeclarationKind, HirSymbol, Result, SlynxHir, SymbolPointer, TypeId, error::{HIRError, HIRErrorKind}, model::{FieldMethod, HirExpression, HirExpressionKind, HirStatementKind, HirType} }; use common::{Operator, Span}; use slynx_parser::{ASTExpression, ASTExpressionKind, ASTStatement, GenericIdentifier, NamedExpr}; @@ -121,7 +119,7 @@ impl SlynxHir { }; Ok(fields[*index]) } - FieldMethod::Variable(variable_id, field_name) => { + FieldMethod::Symbol(HirSymbol::Variable(variable_id), field_name) => { let variable_ty = *self .get_variable_type(*variable_id) .expect("variable type should exist before field access lowering"); @@ -140,6 +138,13 @@ impl SlynxHir { None => Err(HIRError::property_unrecognized(vec![*field_name], *span)), } } + FieldMethod::Symbol(HirSymbol::Declaration(ty), field) => { + let decl = &self.declarations[ty.as_raw() as usize]; + Ok(match decl.kind{ + HirDeclarationKind::Enum => decl.ty, + _ => self.infer_type() + }) + }, FieldMethod::Tuple(rf, index) => self.resolve_tuple_access_type(*rf, *index, span), } } @@ -368,7 +373,8 @@ impl SlynxHir { } ASTExpressionKind::Identifier(name) => { let name = self.modules.intern_name(name); - let id = self.get_variable(name, &expr.span)?; + let symbol = self.resolve_name(name, &expr.span)?; + match let tyid = self.create_type(name, HirType::VarReference(id)); Ok(self.create_identifier_expression(id, tyid, expr.span)) } diff --git a/crates/hir/src/helpers/names.rs b/crates/hir/src/helpers/names.rs index 75ef1892..d49ad129 100644 --- a/crates/hir/src/helpers/names.rs +++ b/crates/hir/src/helpers/names.rs @@ -4,7 +4,7 @@ use crate::{HIRError, Result, SlynxHir, SymbolPointer, VariableId}; impl SlynxHir { ///Tries to retrieve a variable with the provided `name` on the current active scope - pub fn get_variable(&mut self, symbol: SymbolPointer, span: &Span) -> Result { + pub fn get_variable(&self, symbol: SymbolPointer, span: &Span) -> Result { if let Some(variable) = self.modules.find_variable(symbol) { Ok(variable) } else { diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index e77fe91c..8d777f4a 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -360,6 +360,11 @@ impl SlynxHir { ASTDeclarationKind::ComponentDeclaration { name, members, .. } => { self.hoist_component(name, members)? } + ASTDeclarationKind::EnumDeclaration { name, variants } => { + let info = self.hoist_enum(&name.identifier, &variants); + self.declarations + .push(HirDeclaration::new_enum(info.id, info.ty, ast.span)); + } } Ok(()) } @@ -446,6 +451,7 @@ impl SlynxHir { usages, body, } => self.resolve_stylesheet(name, args, usages, body, ast.span), + ASTDeclarationKind::EnumDeclaration { .. } => Ok(()), //hoisted, and thats all what matters } } } diff --git a/crates/hir/src/model/declarations.rs b/crates/hir/src/model/declarations.rs index 47dc8254..0dfda484 100644 --- a/crates/hir/src/model/declarations.rs +++ b/crates/hir/src/model/declarations.rs @@ -158,6 +158,8 @@ pub struct HirDeclaration { #[derive(Debug)] #[repr(C)] pub enum HirDeclarationKind { + Enum, + /// A simple data object with fields. Object, @@ -349,6 +351,14 @@ impl ComponentMemberDeclaration { } impl HirDeclaration { + pub fn new_enum(id: DeclarationId, ty: TypeId, span: Span) -> Self { + Self { + kind: HirDeclarationKind::Enum, + id, + ty, + span, + } + } /// Creates a new function declaration. /// /// # Arguments diff --git a/crates/hir/src/model/mod.rs b/crates/hir/src/model/mod.rs index 01811c32..f4ce71e6 100644 --- a/crates/hir/src/model/mod.rs +++ b/crates/hir/src/model/mod.rs @@ -64,3 +64,10 @@ pub use declarations::*; pub use expression::*; pub use statements::*; pub use types::*; + +use crate::{DeclarationId, VariableId}; +#[derive(Debug, Clone)] +pub enum HirSymbol { + Variable(VariableId), + Declaration(DeclarationId), +} diff --git a/crates/hir/src/model/types.rs b/crates/hir/src/model/types.rs index 706af772..51717bcf 100644 --- a/crates/hir/src/model/types.rs +++ b/crates/hir/src/model/types.rs @@ -52,7 +52,7 @@ //! - [`crate::hir::TypeId`] — Type identifiers //! - [`crate::hir::modules::TypesModule`] — Type management -use crate::{SymbolPointer, TypeId, VariableId}; +use crate::{HirSymbol, SymbolPointer, TypeId, VariableId}; use slynx_parser::VisibilityModifier; @@ -107,7 +107,7 @@ pub enum FieldMethod { /// /// - `0` — The variable ID /// - `1` — The field name as a symbol - Variable(VariableId, SymbolPointer), + Symbol(HirSymbol, SymbolPointer), /// Access a field on a tuple. /// @@ -562,6 +562,9 @@ pub enum HirType { /// /// Represents a component that can work with generic type parameters. GenericComponent, + + ///An enum type. The type checking when this happens should be made related to the TypeId instead, thus enum A != enum B because of their TypeId are different + Enum, } impl HirType { diff --git a/crates/hir/src/modules/declarations.rs b/crates/hir/src/modules/declarations.rs index 74ea6eb6..5867e2ad 100644 --- a/crates/hir/src/modules/declarations.rs +++ b/crates/hir/src/modules/declarations.rs @@ -10,6 +10,8 @@ pub struct DeclarationsModule { declaration_types: Vec, /// Maps each object [`TypeId`] to its ordered list of field symbol pointers. pub objects: HashMap>, + ///Maps a enum type to its variants + pub enums: HashMap>, } impl DeclarationsModule { @@ -19,6 +21,7 @@ impl DeclarationsModule { decls: HashMap::new(), objects: HashMap::new(), declaration_types: Vec::new(), + enums: HashMap::new(), } } /// Registers a new declaration with the given name symbol and type, returning its [`DeclarationId`]. @@ -28,6 +31,20 @@ impl DeclarationsModule { self.declaration_types.push(ty); id } + + pub fn create_enum( + &mut self, + name: SymbolPointer, + variants: Vec, + ty: TypeId, + ) -> DeclarationId { + let id = DeclarationId::from_raw(self.declaration_types.len() as u64); + self.decls.insert(id, name); + self.declaration_types.push(ty); + self.enums.insert(ty, variants); + id + } + ///Creates an objct with the provided `name`, `ty` and `fields` and returns it's id pub fn create_object( &mut self, diff --git a/crates/hir/src/modules/mod.rs b/crates/hir/src/modules/mod.rs index 441c07f1..e9efe1fa 100644 --- a/crates/hir/src/modules/mod.rs +++ b/crates/hir/src/modules/mod.rs @@ -106,7 +106,17 @@ impl HirModules { symbol, } } - + ///Creates a new declaration with the given `name` and `ty`. returns its symbol, type id, and declaration id. + pub fn create_enum(&mut self, name: &str, variants: Vec) -> DeclarationInfo { + let symbol = self.symbols_resolver.intern(name); + let tyid = self.types_module.create_type(symbol, HirType::Enum); + let decl_id = self.declarations_module.create_enum(symbol, variants, tyid); + DeclarationInfo { + id: decl_id, + ty: tyid, + symbol, + } + } /// Creates an object type with the given name and fields and registers it as a declaration. pub fn create_object(&mut self, name: &str, fields: &[ObjectField]) { let name = self.symbols_resolver.intern(name); diff --git a/crates/hir/src/names.rs b/crates/hir/src/names.rs index 417acf31..a6566a35 100644 --- a/crates/hir/src/names.rs +++ b/crates/hir/src/names.rs @@ -1,8 +1,19 @@ -use crate::{Result, SlynxHir, SymbolPointer, TypeId, VariableId}; +use crate::{HIRError, HirSymbol, Result, SlynxHir, SymbolPointer, TypeId, VariableId}; use common::Span; + //file specific to implement things related to name resolution impl SlynxHir { + pub fn resolve_name(&self, symbol: SymbolPointer, span: &Span) -> Result { + match true { + _ if let Ok(out) = self.get_variable(symbol, span) => Ok(HirSymbol::Variable(out)), + _ if let Some((out, _)) = self.modules.get_declaration_by_name(&symbol) => { + Ok(HirSymbol::Declaration(out)) + } + _ => Err(HIRError::name_unrecognized(symbol, *span)), + } + } + pub fn intern_name(&mut self, name: &str) -> SymbolPointer { self.modules.intern_name(name) } diff --git a/docs/contributing/styles.md b/docs/contributing/styles.md index a315eff9..92e4f0df 100644 --- a/docs/contributing/styles.md +++ b/docs/contributing/styles.md @@ -288,6 +288,125 @@ For `border`, `shadow` — define a custom struct type in both HIR and IR. --- +--- + +## Future Direction: Intrinsic Property Declarations + +The current approach requires editing compiler source (two `.rs` files) to add +each new style property. This does not scale — every property change, addition, +or removal becomes a compiler modification, and the compiler binary is +hardcoded with the full property catalog. + +### Problem + +The numeric codes (`@sapply 0` = `BACKGROUND_COLOR`) are a **backend protocol**: +the IR, codegen, and runtime must agree on what each code means. You cannot +invent a new code in user/library code without the backend understanding it. +The compiler also needs to know each property's type at compile time for +type-checking `styles { opacity: 0.5 }`. + +### Proposed solution: `intrinsic styleProperty` + +Add a new top-level declaration kind that lets the std library declare style +properties to the compiler: + +```slynx +// In std/style.slx, compiled as part of the compiler's own build +intrinsic styleProperty backgroundColor: Color = 0; +intrinsic styleProperty foregroundColor: Color = 1; +intrinsic styleProperty padding: Vec4 = 2; +intrinsic styleProperty margin: Vec4 = 3; +intrinsic styleProperty size: Vec4 = 4; +intrinsic styleProperty fontSize: px = 5; +intrinsic styleProperty fontWeight: u16 = 6; +intrinsic styleProperty opacity: f32 = 7; +intrinsic styleProperty border: Border = 8; +intrinsic styleProperty shadow: Shadow = 9; +``` + +The types `Color`, `Vec4`, `Border`, `Shadow`, and `px` are **not** built into +the compiler. The compiler knows only primitive types (`int`, `float`, `bool`, +`str`, `void`). These compound types are themselves defined in the std/intrinsics +library — either as objects or tuples — and are available by the time the style +property declarations are processed. The compiler's type checker resolves them +the same way it resolves any user-defined type. + +Each declaration registers a property in a compiler-wide table: +- **name** — the Slynx identifier used in `styles { name: expr }` +- **type** — the HIR/IR type for compile-time checking and codegen +- **code** — the numeric backend protocol value + +The compiler processes these during its own bootstrap compilation to populate +the property table. User code and library code then inherit the catalog +without needing compiler source changes. + +### Why this approach + +| Concern | Current (hardcoded) | Intrinsic declarations | +|---|---|---| +| Add a property | Edit 2 `.rs` files, recompile | Edit 1 `.slx` file in std library | +| New backend target with different codes | Recompile compiler per target | Library provides target-specific mapping | +| Third-party properties | Not possible | Library declares them, compiler stays generic | +| Type safety | Varied match arms must stay in sync | Single source of truth (the declaration) | +| Property removal | Same 2 files, reverse | Delete one line | + +The bootstrapping cost is one-time: the compiler needs to learn the +`intrinsic styleProperty` declaration kind. The phases involved are: + +| Phase | Work | +|---|---| +| Lexer | New token or keyword? (A keyword avoids special-casing) | +| Parser | `parse_intrinsic_style_property()` → `ASTDeclarationKind::IntrinsicStyleProperty { name, type, code }` | +| HIR | New `HirDeclarationKind::IntrinsicStyleProperty`, hoist property into a queryable table | +| Checker | Validate type is a known type (or a built-in like `Color`, `Vec4`, `Border`) | +| IR | Replace `StyleProperty` enum with a runtime-populated table. Lookup is by name, not by Rust enum discriminant. The `code()` method becomes a table query. | +| Codegen | `from_name()` and `ir_type()` become table lookups, not match arms. | + +The hardcoded `StyleProperty` enum is replaced with a data-driven table: + +```rust +struct StylePropertyEntry { + name: SmolStr, + code: u16, + ir_type: IRTypeId, +} + +struct StylePropertyTable { + entries: Vec, + by_name: HashMap, +} +``` + +### Migration strategy + +1. Implement `intrinsic styleProperty` parsing, HIR, and checking +2. Build the property table in IR from these declarations +3. Move the 10 STYLES_TABLE properties from hardcoded Rust to `std/style.slx` +4. Remove the `StyleProperty` enum and the `resolve_style_type()` match arm +5. The std library is now the source of truth for the property catalog + +Compound properties like `Vec4` (padding, margin, size), `Border`, +`Shadow`, `Color`, and `px` are defined as types in the std library +(either as tuples or objects) before they are referenced by +`intrinsic styleProperty` declarations. This aligns with the goal of +partially bootstrapping the language — the type system bootstraps first +(primitives in the compiler, compound types in the std library), then +style properties reference those types. + +### Implementation notes + +- The `StylePropertyEntry::ir_type` must resolve to a type the IR already + knows about. Library-defined types (`Color`, `Vec4`, `Border`, `Shadow`, + `px`) are defined in the std library before they are referenced by style + property declarations. The compiler does not have special knowledge of + any of them — they are all just user-defined types as far as the + compiler is concerned. +- Numeric codes remain `u16` and are versioned with the std library. +- The backend protocol table (`STYLES_TABLE.md`) becomes documentation of + the std library's declarations, rather than the source of truth. + +--- + ## Known gaps - `default_expr()` in `crates/checker/src/expr.rs:563` ignores the `style` field