Skip to content

Commit 5f09c00

Browse files
committed
really really basic build system working
1 parent 19552bf commit 5f09c00

File tree

52 files changed

+470
-3608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+470
-3608
lines changed

Cargo.lock

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ license = "none"
1212
clap = { version = "4.5.38", features = ["derive"] }
1313
colored = "3.0.0"
1414
pretty_assertions = "1.4.1"
15+
toml = "0.9.5"
1516

1617
[dev-dependencies]
1718
insta = { version = "1.43.1", features = ["glob"] }

examples/cool_app/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package = "cool_app"
2+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package = "simple_example"
2+

examples/simple_example/main.lv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use math#(two)
2+
3+
main :: fun () -> Int: {
4+
2 + two
5+
}

examples/simple_example/math.lv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
two :: 2

src/blush/combiner.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use crate::{
2+
blush::printer,
3+
parser::ast::{Expression, ExpressionKind, Program, UseTailItem},
4+
span::Span,
5+
};
6+
use std::error::Error;
7+
8+
pub struct Combiner<'a> {
9+
files: Vec<(&'a str, &'a Program)>,
10+
}
11+
12+
// TODO:
13+
// - namespace imports
14+
// - naming conflicts (might have to mangle imported variable names)
15+
16+
impl<'a> Combiner<'a> {
17+
pub const fn new(programs: Vec<(&'a str, &'a Program)>) -> Self {
18+
Self { files: programs }
19+
}
20+
21+
pub fn combine(self) -> Result<Program, Box<dyn Error>> {
22+
printer::info(&format!("Combining {} ASTs...", self.files.len()));
23+
let Some(main_file) = self
24+
.files
25+
.iter()
26+
.find(|(name, _)| name.eq_ignore_ascii_case("main.lv"))
27+
else {
28+
return Err("No main.lv file found".into());
29+
};
30+
31+
self.resolve_imports(main_file.1)
32+
}
33+
34+
fn resolve_imports(&self, main_file: &Program) -> Result<Program, Box<dyn Error>> {
35+
let mut new_main_file = Program(vec![]);
36+
37+
for expr in &main_file.0 {
38+
match &expr.kind {
39+
crate::parser::ast::ExpressionKind::Use { segments, tail } => {
40+
let mut ref_program_name = segments.join("/");
41+
ref_program_name.push_str(".lv");
42+
let Some((_, ref_program)) = self
43+
.files
44+
.iter()
45+
.find(|(name, _)| name == &ref_program_name)
46+
else {
47+
return Err(
48+
format!("Couldn't find reference program {ref_program_name}").into(),
49+
);
50+
};
51+
52+
if tail.is_empty() {
53+
todo!("namespaced imports")
54+
} else {
55+
for tail_item in tail {
56+
let Some(corresponding_def) =
57+
ref_program.0.iter().find(|expr| match &expr.kind {
58+
ExpressionKind::VariableDecl { name, .. } => {
59+
name == &tail_item.name
60+
}
61+
_ => false,
62+
})
63+
else {
64+
return Err(format!(
65+
"Couldn't find corresponding definition for {tail_item:?}"
66+
)
67+
.into());
68+
};
69+
70+
let referenced_vars =
71+
Self::referenced_variables(corresponding_def, vec![]);
72+
73+
let mut uses_used_in_corresponding_def = ref_program
74+
.0
75+
.iter()
76+
.filter_map(|expr| match &expr.kind {
77+
ExpressionKind::Use { segments, tail } => {
78+
Some((segments, tail))
79+
}
80+
_ => None,
81+
})
82+
.filter(|(segments, tail_items)| {
83+
referenced_vars.iter().any(|(name, namespace)| {
84+
Self::matches_import(
85+
name,
86+
namespace.as_ref(),
87+
segments,
88+
tail_items,
89+
)
90+
})
91+
})
92+
.map(|(segments, tail_items)| {
93+
Expression::new(
94+
ExpressionKind::Use {
95+
segments: segments.clone(),
96+
tail: tail_items.clone(),
97+
},
98+
Span::from_range(0, 0), // TODO: real span
99+
)
100+
})
101+
.collect::<Vec<_>>();
102+
103+
new_main_file.0.append(&mut uses_used_in_corresponding_def);
104+
new_main_file.0.push(corresponding_def.clone());
105+
}
106+
}
107+
}
108+
109+
ExpressionKind::BoolLiteral(_)
110+
| ExpressionKind::IntLiteral(_)
111+
| ExpressionKind::Ident { .. }
112+
| ExpressionKind::Block(_)
113+
| ExpressionKind::Prefix { .. }
114+
| ExpressionKind::Infix { .. }
115+
| ExpressionKind::VariableDecl { .. }
116+
| ExpressionKind::Function { .. }
117+
| ExpressionKind::FunctionCall { .. } => {
118+
// TODO: some of these will need to be handled differently, like namespaced
119+
// variable idents
120+
new_main_file.0.push(expr.clone());
121+
}
122+
}
123+
}
124+
125+
if new_main_file
126+
.0
127+
.iter()
128+
.any(|expr| matches!(&expr.kind, ExpressionKind::Use { .. }))
129+
{
130+
self.resolve_imports(&new_main_file)
131+
} else {
132+
Ok(new_main_file)
133+
}
134+
}
135+
136+
fn referenced_variables(
137+
expr: &Expression,
138+
base: Vec<(String, Option<String>)>,
139+
) -> Vec<(String, Option<String>)> {
140+
match &expr.kind {
141+
ExpressionKind::BoolLiteral(_) | ExpressionKind::IntLiteral(_) => base,
142+
ExpressionKind::Ident { name, namespace } => {
143+
let mut new_base = base;
144+
new_base.push((name.clone(), namespace.clone()));
145+
new_base
146+
}
147+
ExpressionKind::Block(expressions) => expressions
148+
.iter()
149+
.fold(base, |acc, expr| Self::referenced_variables(expr, acc)),
150+
ExpressionKind::Prefix { expression, .. } => {
151+
Self::referenced_variables(expression, base)
152+
}
153+
ExpressionKind::Infix { left, right, .. } => {
154+
let mut new_base = Self::referenced_variables(left, base);
155+
new_base = Self::referenced_variables(right, new_base);
156+
new_base
157+
}
158+
ExpressionKind::VariableDecl { value, .. } => Self::referenced_variables(value, base),
159+
ExpressionKind::Function { body, .. } => Self::referenced_variables(body, base),
160+
ExpressionKind::FunctionCall {
161+
name,
162+
namespace,
163+
arguments,
164+
} => {
165+
let mut new_base = base;
166+
new_base.push((name.clone(), namespace.clone()));
167+
arguments.iter().fold(new_base, |acc, arg| {
168+
Self::referenced_variables(&arg.value, acc)
169+
})
170+
}
171+
ExpressionKind::Use { .. } => todo!("not sure what to do here yet"),
172+
}
173+
}
174+
175+
fn matches_import(
176+
name: &str,
177+
namespace: Option<&String>,
178+
segments: &[String],
179+
tail_items: &[UseTailItem],
180+
) -> bool {
181+
namespace.as_ref().map_or_else(
182+
|| {
183+
tail_items.iter().any(|tail_item| {
184+
tail_item
185+
.alias
186+
.as_ref()
187+
.map_or_else(|| tail_item.name == name, |alias| alias == name)
188+
})
189+
},
190+
|namespace| tail_items.is_empty() && segments.last() == Some(namespace),
191+
)
192+
}
193+
}
194+
195+
#[cfg(test)]
196+
mod tests {
197+
use crate::{blush::combiner::Combiner, parser::ast::UseTailItem};
198+
199+
#[test]
200+
fn test_matches_import() {
201+
// normal variable import
202+
assert!(Combiner::matches_import(
203+
"foo",
204+
None,
205+
&["lorem".to_string(), "ipsum".to_string()],
206+
&[UseTailItem {
207+
name: "foo".to_string(),
208+
alias: None
209+
}]
210+
));
211+
212+
// missing variable import
213+
assert!(!Combiner::matches_import(
214+
"foo",
215+
None,
216+
&["lorem".to_string(), "ipsum".to_string()],
217+
&[]
218+
));
219+
220+
// aliased variable import
221+
assert!(Combiner::matches_import(
222+
"bar",
223+
None,
224+
&["lorem".to_string(), "ipsum".to_string()],
225+
&[UseTailItem {
226+
name: "foo".to_string(),
227+
alias: Some("bar".to_string())
228+
}]
229+
));
230+
231+
// aliased but using non-aliased name should fail
232+
assert!(!Combiner::matches_import(
233+
"foo",
234+
None,
235+
&["lorem".to_string(), "ipsum".to_string()],
236+
&[UseTailItem {
237+
name: "foo".to_string(),
238+
alias: Some("bar".to_string())
239+
}]
240+
));
241+
242+
// namespaced import
243+
assert!(Combiner::matches_import(
244+
"foo",
245+
Some(&"ipsum".to_string()),
246+
&["lorem".to_string(), "ipsum".to_string()],
247+
&[]
248+
));
249+
}
250+
}

0 commit comments

Comments
 (0)