1mod args;
5mod builtins;
6mod consts;
7mod env;
8mod expr;
9mod menu;
10mod stmt;
11mod ui;
12
13use std::collections::HashMap;
14use std::collections::hash_map::Entry;
15use std::mem;
16use std::rc::Rc;
17
18use nifty_syntax::ast::{self, Item, TypeExpr, TypeExprKind};
19use nifty_syntax::{Diagnostic, Span};
20
21use crate::elements::{ElementKind, MENU, OPEN, Place, WINDOW};
22use crate::hir::{
23 FieldDef, FnDef, FnId, Program, StateDef, StateId, StructDef, StructId, Type, ViewDef, ViewId,
24 WindowId,
25};
26use crate::{elements, functions};
27use env::{Body, Context, Effects, FnEffects, LocalInfo, LocalKind};
28
29#[derive(Clone, Copy, Debug)]
30enum Global {
31 State(StateId),
32 Struct(StructId),
33 Fn(FnId),
34 View(ViewId),
35 Window(WindowId),
36}
37
38#[derive(Clone, Copy, Debug)]
40struct Declared {
41 global: Global,
42 span: Span,
43}
44
45struct Signature {
46 params: Vec<(String, Type)>,
47 return_type: Type,
48}
49
50pub(crate) struct Checker {
51 diagnostics: Vec<Diagnostic>,
52 globals: HashMap<String, Declared>,
54 program: Program,
55 fn_signatures: Vec<Signature>,
56 view_params: Vec<Vec<(String, Type)>>,
57 fn_effects: FnEffects,
58 body: Body,
59 effects: Effects,
60}
61
62pub(crate) fn check(files: &[&ast::File]) -> (Program, Vec<Diagnostic>) {
64 let mut checker = Checker {
65 diagnostics: Vec::new(),
66 globals: HashMap::new(),
67 program: Program::default(),
68 fn_signatures: Vec::new(),
69 view_params: Vec::new(),
70 fn_effects: FnEffects::default(),
71 body: Body::new(Context::Ui),
72 effects: Effects::default(),
73 };
74 checker.check_files(files);
75 let mut diagnostics = checker.diagnostics;
76 diagnostics.sort_by_key(|diagnostic| diagnostic.span);
77 (checker.program, diagnostics)
78}
79
80impl Checker {
81 fn check_files(&mut self, files: &[&ast::File]) {
82 self.declare_types(files);
83 self.declare_signatures(files);
84 self.declare_windows(files);
85 let state_effects = self.check_states(files);
86 self.check_functions(files);
87 self.check_state_values(&state_effects);
88 self.check_views(files);
89 self.check_top_level(files);
90 }
91
92 fn declare_types(&mut self, files: &[&ast::File]) {
93 let structs: Vec<&ast::StructDecl> = items(files)
94 .filter_map(|item| match item {
95 Item::Struct(decl) => Some(decl),
96 _ => None,
97 })
98 .collect();
99 for decl in &structs {
100 let id = StructId(self.program.structs.len());
101 self.declare_global(&decl.name, Global::Struct(id));
102 self.program.structs.push(StructDef {
103 name: decl.name.name.clone(),
104 fields: Vec::new(),
105 });
106 }
107 for (index, decl) in structs.iter().enumerate() {
108 let mut fields: Vec<FieldDef> = Vec::new();
109 for field in &decl.fields {
110 if fields
111 .iter()
112 .any(|existing| existing.name == field.name.name)
113 {
114 self.error(
115 format!("field `{}` is declared twice", field.name.name),
116 field.name.span,
117 );
118 continue;
119 }
120 let ty = self.resolve_type(&field.ty);
121 fields.push(FieldDef {
122 name: field.name.name.clone(),
123 ty,
124 });
125 }
126 self.program.structs[index].fields = fields;
127 }
128 }
129
130 fn declare_signatures(&mut self, files: &[&ast::File]) {
131 for item in items(files) {
132 match item {
133 Item::Fn(decl) => {
134 let id = FnId(self.fn_signatures.len());
135 self.declare_global(&decl.name, Global::Fn(id));
136 let params = self.resolve_params(&decl.params);
137 let return_type = decl
138 .return_type
139 .as_ref()
140 .map_or(Type::Void, |ty| self.resolve_type(ty));
141 self.fn_signatures.push(Signature {
142 params,
143 return_type,
144 });
145 }
146 Item::View(decl) => {
147 let id = ViewId(self.view_params.len());
148 self.declare_global(&decl.name, Global::View(id));
149 let params = self.resolve_params(&decl.params);
150 self.view_params.push(params);
151 }
152 _ => {}
153 }
154 }
155 }
156
157 fn declare_windows(&mut self, files: &[&ast::File]) {
159 let windows = items(files).filter_map(top_level_call).filter(|(_, stmt)| {
160 ui::element_call(stmt).is_some_and(|call| call.element.kind == ElementKind::Window)
161 });
162 for (index, (name, _)) in windows.enumerate() {
163 if let Some(name) = name {
164 self.declare_global(name, Global::Window(WindowId(index)));
165 }
166 }
167 }
168
169 fn resolve_params(&mut self, params: &[ast::TypedName]) -> Vec<(String, Type)> {
170 let mut resolved: Vec<(String, Type)> = Vec::new();
171 for param in params {
172 if resolved.iter().any(|(name, _)| *name == param.name.name) {
173 self.error(
174 format!("parameter `{}` is declared twice", param.name.name),
175 param.name.span,
176 );
177 }
178 let ty = self.resolve_type(¶m.ty);
179 resolved.push((param.name.name.clone(), ty));
180 }
181 resolved
182 }
183
184 fn check_states(&mut self, files: &[&ast::File]) -> Vec<(Span, Effects)> {
187 let mut all_effects = Vec::new();
188 for item in items(files) {
189 let Item::State(decl) = item else { continue };
190 let annotation = decl.ty.as_ref().map(|ty| self.resolve_type(ty));
191 self.body = Body::new(Context::StateInit);
192 let (init, effects) =
193 self.measure(|checker| checker.expr(&decl.value, annotation.as_ref()));
194 let ty = match annotation {
195 Some(annotation) => {
196 self.require(&init.ty, &annotation, init.span);
197 annotation
198 }
199 None => init.ty.clone(),
200 };
201 if ty == Type::Void {
202 self.error("this has no value to start with", init.span);
203 }
204 let id = StateId(self.program.states.len());
205 self.declare_global(&decl.name, Global::State(id));
206 self.program.states.push(StateDef {
207 name: decl.name.name.clone(),
208 ty,
209 init,
210 });
211 all_effects.push((decl.value.span, effects));
212 }
213 all_effects
214 }
215
216 fn check_functions(&mut self, files: &[&ast::File]) {
217 let mut direct = Vec::new();
218 for item in items(files) {
219 let Item::Fn(decl) = item else { continue };
220 let id = FnId(self.program.functions.len());
221 let signature = &self.fn_signatures[id.0];
222 let return_type = signature.return_type.clone();
223 let params = signature.params.clone();
224 self.body = Body::new(Context::Function {
225 return_type: return_type.clone(),
226 });
227 for (name, ty) in ¶ms {
228 self.body.declare(
229 name,
230 LocalInfo {
231 ty: ty.clone(),
232 kind: LocalKind::Variable,
233 },
234 );
235 }
236 let (body, effects) = self.measure(|checker| checker.block(&decl.body));
237 if return_type != Type::Void && !stmt::always_returns(&body) {
238 self.error(
239 format!(
240 "`{}` can reach its end without returning a `{}`",
241 decl.name.name,
242 self.type_name(&return_type)
243 ),
244 decl.name.span,
245 );
246 }
247 self.program.functions.push(FnDef {
248 name: decl.name.name.clone(),
249 params: params.len(),
250 return_type,
251 body,
252 locals: local_types(mem::take(&mut self.body.locals)),
253 });
254 direct.push(effects);
255 }
256 self.fn_effects = FnEffects::solve(&direct);
257 }
258
259 fn check_state_values(&mut self, state_effects: &[(Span, Effects)]) {
260 for (span, effects) in state_effects {
261 let (reads, writes) = self.fn_effects.total(effects);
262 if !reads.is_empty() || writes {
263 self.error("a state's starting value can't use other state", *span);
264 }
265 }
266 }
267
268 fn check_views(&mut self, files: &[&ast::File]) {
269 for item in items(files) {
270 let Item::View(decl) = item else { continue };
271 let params = self.view_params[self.program.views.len()].clone();
272 self.body = Body::new(Context::Ui);
273 for (name, ty) in ¶ms {
274 self.body.declare(
275 name,
276 LocalInfo {
277 ty: ty.clone(),
278 kind: LocalKind::Variable,
279 },
280 );
281 }
282 let body = self.ui_block(&decl.body.stmts);
283 self.program.views.push(ViewDef {
284 name: decl.name.name.clone(),
285 params: params.len(),
286 body,
287 locals: local_types(mem::take(&mut self.body.locals)),
288 });
289 }
290 }
291
292 fn check_top_level(&mut self, files: &[&ast::File]) {
294 let mut top_level_span = None;
295 let mut menus = Vec::new();
296 for (name, stmt) in items(files).filter_map(top_level_call) {
297 top_level_span.get_or_insert(stmt.span);
298 let call = ui::element_call(stmt)
299 .filter(|call| call.element.places.contains(&Place::TopLevel));
300 match call {
301 Some(call) if call.element.kind == ElementKind::Window => {
302 self.body = Body::new(Context::Ui);
303 if let Some(window) = self.window(&call, name.is_some()) {
304 self.program.windows.push(window);
305 }
306 }
307 Some(menu) => {
308 if let Some(name) = name {
309 self.error(
310 format!("only a `{WINDOW}` takes a name, to open and close it by"),
311 name.span,
312 );
313 }
314 menus.push(menu);
315 }
316 None => self.error(
317 format!(
318 "only declarations, `{WINDOW}(…)` and `{MENU}(…)` can appear at the top level"
319 ),
320 stmt.span,
321 ),
322 }
323 }
324 self.body = Body::new(Context::Action);
325 self.program.menu_bar = self.menu_bar(&menus);
326 let span = top_level_span.unwrap_or_default();
327 if self.program.windows.is_empty() {
328 self.error(
329 "a Nifty app needs a `window(\"Title\") { … }` to show",
330 span,
331 );
332 } else if !self.program.windows.iter().any(|window| window.starts_open) {
333 self.error(
334 format!(
335 "a Nifty app needs a window that opens when it starts; leave `{OPEN}: false` off one"
336 ),
337 span,
338 );
339 }
340 }
341
342 fn declare_global(&mut self, name: &ast::Ident, global: Global) {
343 let built_in = if elements::element(&name.name).is_some() {
344 Some("element")
345 } else if functions::function(&name.name).is_some() {
346 Some("function")
347 } else {
348 None
349 };
350 if let Some(kind) = built_in {
351 self.error(
352 format!("`{}` is a built-in {kind}; choose another name", name.name),
353 name.span,
354 );
355 return;
356 }
357 match self.globals.entry(name.name.clone()) {
358 Entry::Vacant(entry) => {
359 entry.insert(Declared {
360 global,
361 span: name.span,
362 });
363 }
364 Entry::Occupied(entry) => {
365 let first = entry.get().span;
366 self.diagnostics.push(
367 Diagnostic::new(format!("`{}` is already declared", name.name), name.span)
368 .with_note(format!("`{}` is first declared here", name.name), first),
369 );
370 }
371 }
372 }
373
374 fn global(&self, name: &str) -> Option<Global> {
375 self.globals.get(name).map(|declared| declared.global)
376 }
377
378 fn resolve_type(&mut self, ty: &TypeExpr) -> Type {
379 match &ty.kind {
380 TypeExprKind::List(element) => Type::List(Rc::new(self.resolve_type(element))),
381 TypeExprKind::Named(name) => {
382 if let Some(primitive) = Type::primitive(name) {
383 primitive
384 } else if let Some(Global::Struct(id)) = self.global(name) {
385 Type::Struct(id)
386 } else {
387 self.error(format!("unknown type `{name}`"), ty.span);
388 Type::Error
389 }
390 }
391 }
392 }
393
394 fn measure<T>(&mut self, check: impl FnOnce(&mut Self) -> T) -> (T, Effects) {
396 let outer = mem::take(&mut self.effects);
397 let result = check(self);
398 let inner = mem::replace(&mut self.effects, outer);
399 self.effects.merge(&inner);
400 (result, inner)
401 }
402
403 fn type_name(&self, ty: &Type) -> String {
404 if let Some(name) = ty.primitive_name() {
405 return name.to_owned();
406 }
407 match ty {
408 Type::Int | Type::Float | Type::Bool | Type::String => {
409 unreachable!("`Type::primitive_name` spells every primitive type")
410 }
411 Type::List(element) => format!("[{}]", self.type_name(element)),
412 Type::Struct(id) => self.program.structs[id.0].name.clone(),
413 Type::Void => "nothing".to_owned(),
414 Type::Error => "?".to_owned(),
415 }
416 }
417
418 fn require(&mut self, actual: &Type, expected: &Type, span: Span) {
420 if actual != expected && !actual.is_error() && !expected.is_error() {
421 self.error(
422 format!(
423 "expected `{}`, found `{}`",
424 self.type_name(expected),
425 self.type_name(actual)
426 ),
427 span,
428 );
429 }
430 }
431
432 fn error(&mut self, message: impl Into<String>, span: Span) {
433 self.diagnostics.push(Diagnostic::new(message, span));
434 }
435}
436
437fn items<'ast>(files: &[&'ast ast::File]) -> impl Iterator<Item = &'ast Item> {
439 files.iter().flat_map(|file| &file.items)
440}
441
442fn top_level_call(item: &Item) -> Option<(Option<&ast::Ident>, &ast::Stmt)> {
445 match item {
446 Item::Named(named) => Some((Some(&named.name), &named.call)),
447 Item::Stmt(stmt) => Some((None, stmt)),
448 Item::State(_) | Item::Struct(_) | Item::Fn(_) | Item::View(_) => None,
449 }
450}
451
452fn local_types(locals: Vec<LocalInfo>) -> Vec<Type> {
454 locals.into_iter().map(|local| local.ty).collect()
455}