1#![cfg_attr(not(feature = "std"), no_std)]
14
15#![no_std]
16#![no_main]
17
18#[macro_use]
19extern crate alloc;
20
21pub mod ast;
23pub mod errors;
24pub mod lexer;
25pub mod parser;
26pub mod semantic;
27pub mod types;
28pub mod value;
29pub mod primitives;
30
31pub use ast::*;
33pub use errors::*;
34pub use lexer::*;
35pub use parser::*;
36pub use semantic::*;
37pub use types::*;
38pub use value::*;
39pub use primitives::*;
40use crate::alloc::*;
41
42#[cfg(not(feature = "std"))]
43use linked_list_allocator::LockedHeap;
44
45use alloc::string::{String, ToString};
46use alloc::vec::Vec;
47use core::hash::BuildHasherDefault;
48
49#[cfg(not(feature = "std"))]
50use core::panic::PanicInfo;
51
52#[cfg(not(feature = "std"))]
53#[panic_handler]
54fn panic(_info: &PanicInfo) -> ! {
55 loop {}
56}
57
58#[allow(deprecated)]
59pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<core::hash::SipHasher>>;
60
61#[derive(Debug, Clone)]
63pub struct File {
64 pub path: String,
65 pub code: String,
66 pub ast: Option<Vec<Statement>>, }
68
69#[derive(Debug, Clone)]
71pub struct Project {
72 pub files: Vec<File>,
73}
74
75#[derive(Debug, Clone)]
77pub struct ParseProjectResult {
78 pub project: Project,
79 pub errors: ErrorCollection,
80}
81
82impl ParseProjectResult {
83 pub fn is_success(&self) -> bool { self.errors.is_empty() }
84}
85
86pub fn parse_files(mut files: Vec<File>) -> ParseProjectResult {
88 let mut all_errors = ErrorCollection::new();
89
90 for file in files.iter_mut() {
91 let res = parse_code(&file.code, file.path.clone());
92 if res.is_success() {
93 file.ast = Some(res.statements);
94 } else {
95 for e in res.errors.errors.iter() {
96 all_errors.add_error(e.clone());
97 }
98 file.ast = None;
99 }
100 }
101
102 ParseProjectResult { project: Project { files }, errors: all_errors }
103}
104
105pub fn analyze_project(project: &mut Project) -> core::result::Result<(), ErrorCollection> {
107 fn extract_code_snippet(code: &str, line: usize) -> String {
108 if line == 0 { return String::new(); }
109 let lines: Vec<&str> = code.lines().collect();
110 if line > 0 && line <= lines.len() { lines[line - 1].to_string() } else { String::new() }
111 }
112
113 let mut all_errors = ErrorCollection::new();
114
115 use alloc::collections::BTreeMap;
116 let mut module_funcs: BTreeMap<String, Vec<(String, Vec<Type>, Type)>> = BTreeMap::new();
117 let mut module_vars: BTreeMap<String, Vec<(String, Type)>> = BTreeMap::new();
118
119 for file in project.files.iter() {
120 if let Some(ref ast) = file.ast {
121 let mut sigs: Vec<(String, Vec<Type>, Type)> = Vec::new();
122 let mut vars: Vec<(String, Type)> = Vec::new();
123 for stmt in ast.iter() {
124 if let StatementKind::FunctionDeclaration { name, params, return_type, .. } = &stmt.kind {
125 let param_types = params.iter().map(|p| Type::from(p.param_type.as_str())).collect::<Vec<_>>();
126 let ret = Type::from(return_type.as_str());
127 sigs.push((name.clone(), param_types, ret));
128 } else if let StatementKind::VariableDeclaration { name, var_type, .. } = &stmt.kind {
129 vars.push((name.clone(), Type::from(var_type.as_str())));
130 }
131 }
132 module_funcs.insert(file.path.clone(), sigs);
133 module_vars.insert(file.path.clone(), vars);
134 }
135 }
136
137 fn resolve_module_file<'a>(project: &'a Project, path_segments: &[String]) -> Option<&'a File> {
138 let joined = path_segments.join("/");
139 let needle = joined.as_str();
140 for f in project.files.iter() {
141 if f.path.ends_with(&format!("/{}.ft", joined)) || f.path.ends_with(&format!("{}{}.ft", if joined.is_empty() { "" } else { "/" }, joined)) {
142 return Some(f);
143 }
144 if let Some(file_name) = f.path.rsplit('/').next() {
145 let stem = file_name.strip_suffix(".ft").unwrap_or(file_name);
146 if stem == needle || stem == path_segments.last().map(|s| s.as_str()).unwrap_or(needle) {
147 return Some(f);
148 }
149 }
150 }
151 None
152 }
153
154 for file in project.files.iter() {
155 if let Some(ref ast) = file.ast {
156 let mut analyzer = SemanticAnalyzer::new(file.path.clone(), file.code.clone());
157
158 for stmt in ast.iter() {
159 if let StatementKind::Import { path, kind } = &stmt.kind {
160 let target = resolve_module_file(project, path);
161 if target.is_none() {
162 let pos = &stmt.pos;
163 all_errors.add_error(ErrorReport::with_file(
164 ErrorType::SyntaxError,
165 format!("Module not found: {}", path.join(".")),
166 file.path.clone(),
167 pos.line,
168 pos.column,
169 pos.offset,
170 pos.length,
171 extract_code_snippet(&file.code, pos.line),
172 ));
173 continue;
174 }
175 let target = target.unwrap();
176 let funcs_opt = module_funcs.get(&target.path);
177 let vars_opt = module_vars.get(&target.path);
178 match kind {
179 ImportKind::Wildcard => {
180 if let Some(funcs) = funcs_opt {
181 for (fname, params, ret) in funcs.iter() {
182 let _ = analyzer.predeclare_function(fname.clone(), params.clone(), ret.clone());
183 }
184 }
185 if let Some(vars) = vars_opt {
186 for (vname, vtype) in vars.iter() {
187 let _ = analyzer.predeclare_initialized_variable(vname.clone(), vtype.clone());
188 }
189 }
190 }
191 ImportKind::Single(name) => {
192 let mut found = false;
193 if let Some(funcs) = funcs_opt {
194 if let Some((_, params, ret)) = funcs.iter().find(|(n, _, _)| n == name) {
195 let _ = analyzer.predeclare_function(name.clone(), params.clone(), ret.clone());
196 found = true;
197 }
198 }
199 if !found {
200 if let Some(vars) = vars_opt {
201 if let Some((_, vtype)) = vars.iter().find(|(n, _)| n == name) {
202 let _ = analyzer.predeclare_initialized_variable(name.clone(), vtype.clone());
203 found = true;
204 }
205 }
206 }
207 if !found {
208 let pos = &stmt.pos;
209 all_errors.add_error(ErrorReport::with_file(
210 ErrorType::UndefinedVariable(name.clone()),
211 format!("Symbol '{}' not found in module {}", name, path.join(".")),
212 file.path.clone(),
213 pos.line,
214 pos.column,
215 pos.offset,
216 pos.length,
217 extract_code_snippet(&file.code, pos.line),
218 ));
219 }
220 }
221 ImportKind::Group(names) => {
222 for name in names.iter() {
223 let mut found = false;
224 if let Some(funcs) = funcs_opt {
225 if let Some((_, params, ret)) = funcs.iter().find(|(n, _, _)| n == name) {
226 let _ = analyzer.predeclare_function(name.clone(), params.clone(), ret.clone());
227 found = true;
228 }
229 }
230 if !found {
231 if let Some(vars) = vars_opt {
232 if let Some((_, vtype)) = vars.iter().find(|(n, _)| n == name) {
233 let _ = analyzer.predeclare_initialized_variable(name.clone(), vtype.clone());
234 found = true;
235 }
236 }
237 }
238 if !found {
239 let pos = &stmt.pos;
240 all_errors.add_error(ErrorReport::with_file(
241 ErrorType::UndefinedVariable(name.clone()),
242 format!("Symbol '{}' not found in module {}", name, path.join(".")),
243 file.path.clone(),
244 pos.line,
245 pos.column,
246 pos.offset,
247 pos.length,
248 extract_code_snippet(&file.code, pos.line),
249 ));
250 }
251 }
252 }
253 }
254 }
255 }
256
257 if let Err(errors) = analyzer.analyze(ast) {
258 for e in errors.errors.iter() {
259 all_errors.add_error(e.clone());
260 }
261 }
262 }
263 }
264
265 if all_errors.is_empty() { Ok(()) } else { Err(all_errors) }
266}
267
268pub fn parse_code(input: &str, filename: String) -> ParseResult {
270 #[cfg(not(feature = "std"))]
271 unsafe { ALLOCATOR.lock().init(HEAP_START.as_mut_ptr(), HEAP_SIZE); }
272
273 let mut lexer = Lexer::new(input, filename.clone());
274 let tokens = lexer.tokenize();
275
276 let mut parser = Parser::new(tokens, input.to_string(), filename);
277 parser.parse()
278}
279
280pub const VERSION: &str = env!("CARGO_PKG_VERSION");
282
283#[cfg(not(feature = "std"))]
285const HEAP_SIZE: usize = 16 * 1024;
286
287#[cfg(not(feature = "std"))]
288static mut HEAP_START: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
289
290#[cfg(not(feature = "std"))]
291#[global_allocator]
292static ALLOCATOR: LockedHeap = LockedHeap::empty();