feat(type-system): implement concept_comparison with pattern matching
Added complete support for the new type system syntax including: - concept: Base type declarations - sub_concept: Enum and record sub-type definitions - concept_comparison: Compile-time pattern matching with conditional guards Parser changes: - Added VariantPattern, FieldCondition, and Condition AST nodes - Implemented "is" keyword for pattern matching (e.g., "CupType is Glass or CupType is Plastic") - Added Value::Any variant to support universal type matching - Disambiguated enum-like vs record-like sub_concept syntax LSP updates: - Added Value::Any match arms across code_actions, completion, hover, inlay_hints, and semantic_tokens - Type inference and formatting support for Any values Example fixes: - Fixed syntax error in baker-family behaviors (missing closing brace in nested if) - Removed deprecated core_enums.sb file
This commit is contained in:
@@ -69,7 +69,6 @@ pub enum Declaration {
|
||||
Relationship(Relationship),
|
||||
Location(Location),
|
||||
Species(Species),
|
||||
Enum(EnumDecl),
|
||||
Concept(ConceptDecl),
|
||||
SubConcept(SubConceptDecl),
|
||||
ConceptComparison(ConceptComparisonDecl),
|
||||
@@ -171,6 +170,7 @@ pub enum Value {
|
||||
Object(Vec<Field>),
|
||||
ProseBlock(ProseBlock),
|
||||
Override(Override),
|
||||
Any, // Special marker for type system - matches any value
|
||||
}
|
||||
|
||||
/// Time literal (HH:MM or HH:MM:SS)
|
||||
@@ -409,14 +409,6 @@ pub struct Species {
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// Enum definition
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EnumDecl {
|
||||
pub name: String,
|
||||
pub variants: Vec<String>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// Concept declaration - base type definition
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConceptDecl {
|
||||
@@ -440,21 +432,35 @@ pub enum SubConceptKind {
|
||||
Record { fields: Vec<Field> },
|
||||
}
|
||||
|
||||
/// Concept comparison - compile-time enum mapping between two concepts
|
||||
/// Concept comparison - compile-time pattern matching for concept variants
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConceptComparisonDecl {
|
||||
pub name: String,
|
||||
pub left_concept: String,
|
||||
pub right_concept: String,
|
||||
pub mappings: Vec<ConceptMapping>,
|
||||
pub variants: Vec<VariantPattern>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A single mapping entry in a concept comparison
|
||||
/// A variant pattern with field conditions
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConceptMapping {
|
||||
pub left_variant: String,
|
||||
pub right_variant: String,
|
||||
pub struct VariantPattern {
|
||||
pub name: String,
|
||||
pub conditions: Vec<FieldCondition>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A condition on a field within a variant pattern
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FieldCondition {
|
||||
pub field_name: String,
|
||||
pub condition: Condition,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// The type of condition for field matching
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Condition {
|
||||
Any, // matches any value
|
||||
Is(Vec<String>), // matches specific values (e.g., "is Glass or is Plastic")
|
||||
}
|
||||
|
||||
/// Expression AST for conditions and queries
|
||||
|
||||
@@ -98,7 +98,6 @@ pub fn token_is_declaration_keyword(token: &Token) -> bool {
|
||||
Token::Relationship |
|
||||
Token::Institution |
|
||||
Token::Location |
|
||||
Token::Enum |
|
||||
Token::Schedule
|
||||
)
|
||||
}
|
||||
@@ -116,7 +115,6 @@ pub fn token_is_structural_keyword(token: &Token) -> bool {
|
||||
Token::Relationship |
|
||||
Token::Institution |
|
||||
Token::Location |
|
||||
Token::Enum |
|
||||
Token::Schedule
|
||||
)
|
||||
}
|
||||
@@ -132,7 +130,6 @@ pub fn declaration_token_to_str(token: &Token) -> Option<&'static str> {
|
||||
| Token::Relationship => Some("relationship"),
|
||||
| Token::Institution => Some("institution"),
|
||||
| Token::Location => Some("location"),
|
||||
| Token::Enum => Some("enum"),
|
||||
| Token::Schedule => Some("schedule"),
|
||||
| _ => None,
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ pub enum Token {
|
||||
Location,
|
||||
#[token("species")]
|
||||
Species,
|
||||
#[token("enum")]
|
||||
Enum,
|
||||
#[token("concept")]
|
||||
Concept,
|
||||
#[token("sub_concept")]
|
||||
|
||||
@@ -20,7 +20,9 @@ Declaration: Declaration = {
|
||||
<r:Relationship> => Declaration::Relationship(r),
|
||||
<loc:Location> => Declaration::Location(loc),
|
||||
<sp:Species> => Declaration::Species(sp),
|
||||
<e:EnumDecl> => Declaration::Enum(e),
|
||||
<concept:ConceptDecl> => Declaration::Concept(concept),
|
||||
<sub:SubConceptDecl> => Declaration::SubConcept(sub),
|
||||
<comp:ConceptComparisonDecl> => Declaration::ConceptComparison(comp),
|
||||
};
|
||||
|
||||
// ===== Use declarations =====
|
||||
@@ -246,6 +248,7 @@ Value: Value = {
|
||||
<FloatLit> => Value::Float(<>),
|
||||
<StringLit> => Value::String(<>),
|
||||
<BoolLit> => Value::Bool(<>),
|
||||
"any" => Value::Any,
|
||||
<lo:IntLit> ".." <hi:IntLit> => Value::Range(
|
||||
Box::new(Value::Int(lo)),
|
||||
Box::new(Value::Int(hi))
|
||||
@@ -740,14 +743,120 @@ Species: Species = {
|
||||
|
||||
// ===== Enum =====
|
||||
|
||||
EnumDecl: EnumDecl = {
|
||||
"enum" <name:Ident> "{" <variants:Comma<Ident>> "}" => EnumDecl {
|
||||
// ===== Type System Declarations =====
|
||||
|
||||
ConceptDecl: ConceptDecl = {
|
||||
"concept" <name:Ident> => ConceptDecl {
|
||||
name,
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
};
|
||||
|
||||
SubConceptDecl: SubConceptDecl = {
|
||||
// Enum-like sub_concept: sub_concept PlateColor { Red, Blue, Green }
|
||||
"sub_concept" <name:Ident> "{" <variants:Comma<Ident>> "}" => {
|
||||
let parent = {
|
||||
let mut last_cap = 0;
|
||||
for (i, ch) in name.char_indices().skip(1) {
|
||||
if ch.is_uppercase() {
|
||||
last_cap = i;
|
||||
}
|
||||
}
|
||||
if last_cap > 0 {
|
||||
name[..last_cap].to_string()
|
||||
} else {
|
||||
name.clone()
|
||||
}
|
||||
};
|
||||
|
||||
SubConceptDecl {
|
||||
name,
|
||||
parent_concept: parent,
|
||||
kind: SubConceptKind::Enum { variants },
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
},
|
||||
// Record-like sub_concept with at least one field
|
||||
"sub_concept" <name:Ident> "{" <first:Ident> ":" <first_val:Value> <rest:("," <Ident> ":" <Value>)*> ","? "}" => {
|
||||
let parent = {
|
||||
let mut last_cap = 0;
|
||||
for (i, ch) in name.char_indices().skip(1) {
|
||||
if ch.is_uppercase() {
|
||||
last_cap = i;
|
||||
}
|
||||
}
|
||||
if last_cap > 0 {
|
||||
name[..last_cap].to_string()
|
||||
} else {
|
||||
name.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let mut fields = vec![Field {
|
||||
name: first,
|
||||
value: first_val,
|
||||
span: Span::new(0, 0),
|
||||
}];
|
||||
|
||||
for (field_name, field_val) in rest {
|
||||
fields.push(Field {
|
||||
name: field_name,
|
||||
value: field_val,
|
||||
span: Span::new(0, 0),
|
||||
});
|
||||
}
|
||||
|
||||
SubConceptDecl {
|
||||
name,
|
||||
parent_concept: parent,
|
||||
kind: SubConceptKind::Record { fields },
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
ConceptComparisonDecl: ConceptComparisonDecl = {
|
||||
"concept_comparison" <name:Ident> "{" <variants:Comma<VariantPattern>> "}" => ConceptComparisonDecl {
|
||||
name,
|
||||
variants,
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
};
|
||||
|
||||
VariantPattern: VariantPattern = {
|
||||
<name:Ident> ":" "{" <conditions:Comma<FieldCondition>> "}" => VariantPattern {
|
||||
name,
|
||||
conditions,
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
};
|
||||
|
||||
FieldCondition: FieldCondition = {
|
||||
<field:Ident> ":" "any" => FieldCondition {
|
||||
field_name: field,
|
||||
condition: Condition::Any,
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
<field:Ident> ":" <cond:IsCondition> => FieldCondition {
|
||||
field_name: field,
|
||||
condition: Condition::Is(cond),
|
||||
span: Span::new(0, 0),
|
||||
},
|
||||
};
|
||||
|
||||
// Parse "FieldName is Value1 or FieldName is Value2" and extract the values
|
||||
IsCondition: Vec<String> = {
|
||||
<first:IsValue> <rest:("or" <IsValue>)*> => {
|
||||
let mut values = vec![first];
|
||||
values.extend(rest);
|
||||
values
|
||||
}
|
||||
};
|
||||
|
||||
IsValue: String = {
|
||||
<field:Ident> "is" <value:Ident> => value
|
||||
};
|
||||
|
||||
// ===== Expressions =====
|
||||
// Expression grammar with proper precedence:
|
||||
// or > and > not > field_access > comparison > term
|
||||
@@ -877,7 +986,10 @@ extern {
|
||||
"relationship" => Token::Relationship,
|
||||
"location" => Token::Location,
|
||||
"species" => Token::Species,
|
||||
"enum" => Token::Enum,
|
||||
"concept" => Token::Concept,
|
||||
"sub_concept" => Token::SubConcept,
|
||||
"concept_comparison" => Token::ConceptComparison,
|
||||
"any" => Token::Any,
|
||||
"state" => Token::State,
|
||||
"on" => Token::On,
|
||||
"enter" => Token::Enter,
|
||||
|
||||
14284
src/syntax/parser.rs
14284
src/syntax/parser.rs
File diff suppressed because it is too large
Load Diff
@@ -179,7 +179,7 @@ proptest! {
|
||||
#[test]
|
||||
fn test_keywords_are_distinct_from_idents(
|
||||
keyword in prop::sample::select(vec![
|
||||
"character", "template", "enum", "use", "self", "other",
|
||||
"character", "template", "use", "self", "other",
|
||||
"and", "or", "not", "is", "true", "false",
|
||||
"if", "when", "choose", "then", "include"
|
||||
])
|
||||
@@ -249,11 +249,6 @@ fn valid_template() -> impl Strategy<Value = String> {
|
||||
})
|
||||
}
|
||||
|
||||
fn valid_enum() -> impl Strategy<Value = String> {
|
||||
(valid_ident(), prop::collection::vec(valid_ident(), 1..10))
|
||||
.prop_map(|(name, variants)| format!("enum {} {{ {} }}", name, variants.join(", ")))
|
||||
}
|
||||
|
||||
fn valid_schedule() -> impl Strategy<Value = String> {
|
||||
(valid_ident(), prop::collection::vec(valid_time(), 1..5)).prop_map(|(name, times)| {
|
||||
let blocks = times
|
||||
@@ -366,22 +361,6 @@ proptest! {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_enum_parses(input in valid_enum()) {
|
||||
let lexer = Lexer::new(&input);
|
||||
let parser = FileParser::new();
|
||||
let result = parser.parse(lexer);
|
||||
assert!(result.is_ok(), "Failed to parse valid enum: {}\nError: {:?}", input, result.err());
|
||||
|
||||
if let Ok(file) = result {
|
||||
assert_eq!(file.declarations.len(), 1);
|
||||
match &file.declarations[0] {
|
||||
crate::syntax::ast::Declaration::Enum(_) => {},
|
||||
_ => panic!("Expected Enum declaration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_schedule_parses(input in valid_schedule()) {
|
||||
let lexer = Lexer::new(&input);
|
||||
@@ -466,11 +445,9 @@ proptest! {
|
||||
fn test_multiple_declarations_parse(
|
||||
chars in prop::collection::vec(valid_character(), 0..3),
|
||||
templates in prop::collection::vec(valid_template(), 0..3),
|
||||
enums in prop::collection::vec(valid_enum(), 0..3),
|
||||
) {
|
||||
let mut all = chars;
|
||||
all.extend(templates);
|
||||
all.extend(enums);
|
||||
let input = all.join("\n\n");
|
||||
|
||||
let lexer = Lexer::new(&input);
|
||||
|
||||
Reference in New Issue
Block a user