Skip to content

Commit 9b66b85

Browse files
committed
Fix some grammar bugs and start semantic analysis
1 parent 14348e4 commit 9b66b85

File tree

7 files changed

+402
-3
lines changed

7 files changed

+402
-3
lines changed

optd-core/src/dsl/analyzer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod semantic;
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*use std::collections::HashSet;
2+
3+
use crate::dsl::parser::ast::{Expr, File, Function, Operator, Pattern, Properties, Type};
4+
5+
#[derive(Debug)]
6+
pub struct SemanticAnalyzer {
7+
logical_properties: HashSet<String>,
8+
operators: HashSet<String>,
9+
identifiers: Vec<HashSet<String>>,
10+
}
11+
12+
impl SemanticAnalyzer {
13+
pub fn new() -> Self {
14+
SemanticAnalyzer {
15+
logical_properties: HashSet::new(),
16+
operators: HashSet::new(),
17+
identifiers: Vec::new(),
18+
}
19+
}
20+
21+
fn enter_scope(&mut self) {
22+
self.identifiers.push(HashSet::new());
23+
}
24+
25+
fn exit_scope(&mut self) {
26+
self.identifiers.pop();
27+
}
28+
29+
fn add_identifier(&mut self, name: String) -> Result<(), String> {
30+
if let Some(scope) = self.identifiers.last_mut() {
31+
if scope.contains(&name) {
32+
return Err(format!("Duplicate identifier name: {}", name));
33+
}
34+
scope.insert(name);
35+
}
36+
Ok(())
37+
}
38+
39+
fn lookup_identifier(&self, name: &str) -> bool {
40+
self.identifiers
41+
.iter()
42+
.rev()
43+
.any(|scope| scope.contains(name))
44+
}
45+
46+
fn is_valid_scalar_type(&self, ty: &Type) -> bool {
47+
match ty {
48+
Type::Array(inner) => self.is_valid_scalar_type(inner),
49+
Type::Int64 | Type::String | Type::Bool | Type::Float64 => true,
50+
_ => false,
51+
}
52+
}
53+
54+
fn is_valid_logical_type(&self, ty: &Type) -> bool {
55+
match ty {
56+
Type::Array(inner) => self.is_valid_logical_type(inner),
57+
Type::Int64 | Type::String | Type::Bool | Type::Float64 => true,
58+
_ => false,
59+
}
60+
}
61+
62+
fn is_valid_property_type(&self, ty: &Type) -> bool {
63+
match ty {
64+
Type::Array(inner) => self.is_valid_property_type(inner),
65+
Type::Tuple(fields) => fields.iter().all(|f| self.is_valid_property_type(f)),
66+
Type::Map(a, b) => self.is_valid_property_type(a) && self.is_valid_property_type(b),
67+
Type::Int64 | Type::String | Type::Bool | Type::Float64 => true,
68+
Type::Function(_, _) => false,
69+
Type::Operator(_) => false,
70+
}
71+
}
72+
73+
fn validate_properties(&mut self, properties: &Properties) -> Result<(), String> {
74+
for field in &properties.fields {
75+
if !self.is_valid_property_type(&field.ty) {
76+
return Err(format!("Invalid type in properties: {:?}", field.ty));
77+
}
78+
}
79+
80+
self.logical_properties = properties
81+
.fields
82+
.iter()
83+
.map(|field| field.name.clone())
84+
.collect();
85+
86+
Ok(())
87+
}
88+
89+
fn validate_operator(&mut self, operator: &Operator) -> Result<(), String> {
90+
match operator {
91+
Operator::Scalar(scalar_op) => {
92+
if self.operators.contains(&scalar_op.name) {
93+
return Err(format!("Duplicate operator name: {}", scalar_op.name));
94+
}
95+
self.operators.insert(scalar_op.name.clone());
96+
97+
for field in &scalar_op.fields {
98+
if !self.is_valid_scalar_type(&field.ty) {
99+
return Err(format!("Invalid type in scalar operator: {:?}", field.ty));
100+
}
101+
}
102+
}
103+
Operator::Logical(logical_op) => {
104+
if self.operators.contains(&logical_op.name) {
105+
return Err(format!("Duplicate operator name: {}", logical_op.name));
106+
}
107+
self.operators.insert(logical_op.name.clone());
108+
109+
for field in &logical_op.fields {
110+
if !self.is_valid_logical_type(&field.ty) {
111+
return Err(format!("Invalid type in logical operator: {:?}", field.ty));
112+
}
113+
}
114+
115+
// Check that derived properties match the logical properties fields
116+
for (prop_name, _) in &logical_op.derived_props {
117+
if !self.operators.iter().any(|f| f == prop_name) {
118+
return Err(format!(
119+
"Derived property not found in logical properties: {}",
120+
prop_name
121+
));
122+
}
123+
}
124+
125+
// Check that all logical properties fields have corresponding derived properties
126+
for field in &self.logical_properties {
127+
if !logical_op.derived_props.contains_key(field) {
128+
return Err(format!(
129+
"Logical property field '{}' is missing a derived property",
130+
field
131+
));
132+
}
133+
}
134+
}
135+
}
136+
Ok(())
137+
}
138+
139+
// Validate a function definition
140+
fn validate_function(&mut self, function: &Function) -> Result<(), String> {
141+
if self.function_names.contains(&function.name) {
142+
return Err(format!("Duplicate function name: {}", function.name));
143+
}
144+
self.function_names.insert(function.name.clone());
145+
146+
self.enter_scope();
147+
for (param_name, _) in &function.params {
148+
self.add_identifier(param_name.clone())?;
149+
}
150+
self.validate_expr(&function.body)?;
151+
self.exit_scope();
152+
153+
Ok(())
154+
}
155+
156+
// Validate an expression
157+
fn validate_expr(&mut self, expr: &Expr) -> Result<(), String> {
158+
match expr {
159+
Expr::Var(name) => {
160+
if self.lookup_identifier(name) {
161+
return Err(format!("Undefined identifier: {}", name));
162+
}
163+
}
164+
Expr::Val(name, expr1, expr2) => {
165+
self.validate_expr(expr1)?;
166+
self.add_identifier(name.clone())?;
167+
self.validate_expr(expr2)?;
168+
}
169+
Expr::Match(expr, arms) => {
170+
self.validate_expr(expr)?;
171+
for arm in arms {
172+
self.validate_pattern(&arm.pattern)?;
173+
self.validate_expr(&arm.expr)?;
174+
}
175+
}
176+
Expr::If(cond, then_expr, else_expr) => {
177+
self.validate_expr(cond)?;
178+
self.validate_expr(then_expr)?;
179+
self.validate_expr(else_expr)?;
180+
}
181+
Expr::Binary(left, _, right) => {
182+
self.validate_expr(left)?;
183+
self.validate_expr(right)?;
184+
}
185+
Expr::Unary(_, expr) => {
186+
self.validate_expr(expr)?;
187+
}
188+
Expr::Call(func, args) => {
189+
self.validate_expr(func)?;
190+
for arg in args {
191+
self.validate_expr(arg)?;
192+
}
193+
}
194+
Expr::Member(expr, _) => {
195+
self.validate_expr(expr)?;
196+
}
197+
Expr::MemberCall(expr, _, args) => {
198+
self.validate_expr(expr)?;
199+
for arg in args {
200+
self.validate_expr(arg)?;
201+
}
202+
}
203+
Expr::ArrayIndex(array, index) => {
204+
self.validate_expr(array)?;
205+
self.validate_expr(index)?;
206+
}
207+
Expr::Literal(_) => {}
208+
Expr::Fail(_) => {}
209+
Expr::Closure(params, body) => {
210+
self.enter_scope();
211+
for param in params {
212+
self.add_identifier(param.clone())?;
213+
}
214+
self.validate_expr(body)?;
215+
self.exit_scope();
216+
}
217+
_ => {}
218+
}
219+
Ok(())
220+
}
221+
222+
// Validate a pattern
223+
fn validate_pattern(&mut self, pattern: &Pattern) -> Result<(), String> {
224+
match pattern {
225+
Pattern::Bind(name, pat) => {
226+
self.add_identifier(name.clone())?;
227+
self.validate_pattern(pat)?;
228+
}
229+
Pattern::Constructor(_, pats) => {
230+
for pat in pats {
231+
self.validate_pattern(pat)?;
232+
}
233+
}
234+
Pattern::Literal(_) => {}
235+
Pattern::Wildcard => {}
236+
Pattern::Var(name) => {
237+
self.add_identifier(name.clone())?;
238+
}
239+
}
240+
Ok(())
241+
}
242+
243+
// Validate a complete file
244+
pub fn validate_file(&mut self, file: &File) -> Result<(), String> {
245+
self.validate_properties(&file.properties)?;
246+
247+
for operator in &file.operators {
248+
self.validate_operator(operator)?;
249+
}
250+
251+
for function in &file.functions {
252+
self.validate_function(function)?;
253+
}
254+
255+
Ok(())
256+
}
257+
}
258+
*/

optd-core/src/dsl/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod analyzer;
12
pub mod parser;

optd-core/src/dsl/parser/expr.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,18 @@ fn parse_constructor(pair: Pair<'_, Rule>) -> Expr {
191191
let mut pairs = pair.into_inner();
192192
let name = pairs.next().unwrap().as_str().to_string();
193193
let args = pairs.map(parse_expr).collect();
194-
Expr::Constructor(name, args)
194+
195+
// Check if first character is lowercase
196+
// TODO(alexis): small hack until I rewrite the grammar using Chumsky
197+
if name
198+
.chars()
199+
.next()
200+
.map_or(false, |c| c.is_ascii_lowercase())
201+
{
202+
Expr::Call(Box::new(Expr::Var(name)), args)
203+
} else {
204+
Expr::Constructor(name, args)
205+
}
195206
}
196207

197208
/// Parse a numeric literal

optd-core/src/dsl/parser/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,13 @@ mod tests {
119119
}
120120

121121
#[test]
122-
fn parse_example_file() {
122+
fn parse_example_files() {
123123
let input = include_str!("../parser/programs/example.optd");
124124
parse_file(input).unwrap();
125+
126+
let input = include_str!("../parser/programs/working.optd");
127+
let out = parse_file(input).unwrap();
128+
println!("{:#?}", out);
125129
}
126130

127131
#[test]

optd-core/src/dsl/parser/programs/example.optd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Shared properties for Logical operators
22
Logical Props(vale: [Int64])
3+
34
Scalar ColumnRef (idx: (Int64) -> Map[(Int64, (Int64) -> String), Int64])
45
Scalar Const(asd: Int64)
5-
Scalar Eq(left: [Int64])
6+
Scalar Eq(left: Map[Int64, Int64])
67
Scalar Divide(left: Scalar, right: Scalar)
78
Scalar Not(input: Scalar)
89

0 commit comments

Comments
 (0)