1pub mod checker;
14pub mod env;
15pub mod reference;
16pub mod stdlib;
17pub mod ty;
18
19use pepl_codegen::CodegenError;
20use pepl_types::ast::Program;
21use pepl_types::{CompileErrors, SourceFile};
22use serde::{Deserialize, Serialize};
23use sha2::{Digest, Sha256};
24
25pub const PEPL_LANGUAGE_VERSION: &str = "0.1.0";
29
30pub const PEPL_COMPILER_VERSION: &str = env!("CARGO_PKG_VERSION");
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct FieldInfo {
38 pub name: String,
39 #[serde(rename = "type")]
40 pub ty: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ActionInfo {
46 pub name: String,
47 pub params: Vec<FieldInfo>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CompileResult {
55 pub success: bool,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub wasm: Option<Vec<u8>>,
60 pub errors: CompileErrors,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
67 pub ast: Option<Program>,
68
69 pub source_hash: String,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub wasm_hash: Option<String>,
75
76 pub state_fields: Vec<FieldInfo>,
78
79 pub actions: Vec<ActionInfo>,
81
82 pub views: Vec<String>,
84
85 pub capabilities: Vec<String>,
87
88 pub credentials: Vec<FieldInfo>,
90
91 pub language_version: String,
93
94 pub compiler_version: String,
96
97 pub warnings: Vec<pepl_types::PeplError>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub source_map: Option<pepl_codegen::SourceMap>,
103}
104
105pub fn type_check(source: &str, name: &str) -> CompileErrors {
111 let source_file = SourceFile::new(name.to_string(), source.to_string());
112
113 let lex_result = pepl_lexer::Lexer::new(&source_file).lex();
115 if lex_result.errors.has_errors() {
116 return lex_result.errors;
117 }
118
119 let parse_result = pepl_parser::Parser::new(lex_result.tokens, &source_file).parse();
121 if parse_result.errors.has_errors() {
122 return parse_result.errors;
123 }
124
125 let program = match parse_result.program {
126 Some(p) => p,
127 None => return parse_result.errors,
128 };
129
130 let mut errors = CompileErrors::empty();
132 let mut tc = checker::TypeChecker::new(&mut errors, &source_file);
133 tc.check(&program);
134
135 errors
136}
137
138pub fn compile(source: &str, name: &str) -> Result<Vec<u8>, CompileErrors> {
146 let source_file = SourceFile::new(name.to_string(), source.to_string());
147
148 let lex_result = pepl_lexer::Lexer::new(&source_file).lex();
150 if lex_result.errors.has_errors() {
151 return Err(lex_result.errors);
152 }
153
154 let parse_result = pepl_parser::Parser::new(lex_result.tokens, &source_file).parse();
156 if parse_result.errors.has_errors() {
157 return Err(parse_result.errors);
158 }
159
160 let program = match parse_result.program {
161 Some(p) => p,
162 None => return Err(parse_result.errors),
163 };
164
165 let mut errors = CompileErrors::empty();
167 {
168 let mut tc = checker::TypeChecker::new(&mut errors, &source_file);
169 tc.check(&program);
170 }
171 if errors.has_errors() {
172 return Err(errors);
173 }
174
175 match pepl_codegen::compile(&program) {
177 Ok(wasm) => Ok(wasm),
178 Err(e) => {
179 let mut errors = CompileErrors::empty();
180 errors.push_error(codegen_error_to_pepl_error(&e, name));
181 Err(errors)
182 }
183 }
184}
185
186pub fn compile_to_result(source: &str, name: &str) -> CompileResult {
191 let source_hash = sha256_hex(source.as_bytes());
192 let source_file = SourceFile::new(name.to_string(), source.to_string());
193
194 let lex_result = pepl_lexer::Lexer::new(&source_file).lex();
196 if lex_result.errors.has_errors() {
197 return CompileResult {
198 success: false,
199 wasm: None,
200 errors: lex_result.errors,
201 ast: None,
202 source_hash,
203 wasm_hash: None,
204 state_fields: Vec::new(),
205 actions: Vec::new(),
206 views: Vec::new(),
207 capabilities: Vec::new(),
208 credentials: Vec::new(),
209 language_version: PEPL_LANGUAGE_VERSION.to_string(),
210 compiler_version: PEPL_COMPILER_VERSION.to_string(),
211 warnings: Vec::new(),
212 source_map: None,
213 };
214 }
215
216 let parse_result = pepl_parser::Parser::new(lex_result.tokens, &source_file).parse();
218 if parse_result.errors.has_errors() {
219 return CompileResult {
220 success: false,
221 wasm: None,
222 errors: parse_result.errors,
223 ast: None,
224 source_hash,
225 wasm_hash: None,
226 state_fields: Vec::new(),
227 actions: Vec::new(),
228 views: Vec::new(),
229 capabilities: Vec::new(),
230 credentials: Vec::new(),
231 language_version: PEPL_LANGUAGE_VERSION.to_string(),
232 compiler_version: PEPL_COMPILER_VERSION.to_string(),
233 warnings: Vec::new(),
234 source_map: None,
235 };
236 }
237
238 let program = match parse_result.program {
239 Some(p) => p,
240 None => {
241 return CompileResult {
242 success: false,
243 wasm: None,
244 errors: parse_result.errors,
245 ast: None,
246 source_hash,
247 wasm_hash: None,
248 state_fields: Vec::new(),
249 actions: Vec::new(),
250 views: Vec::new(),
251 capabilities: Vec::new(),
252 credentials: Vec::new(),
253 language_version: PEPL_LANGUAGE_VERSION.to_string(),
254 compiler_version: PEPL_COMPILER_VERSION.to_string(),
255 warnings: Vec::new(),
256 source_map: None,
257 };
258 }
259 };
260
261 let metadata = extract_metadata(&program);
263
264 let mut errors = CompileErrors::empty();
266 {
267 let mut tc = checker::TypeChecker::new(&mut errors, &source_file);
268 tc.check(&program);
269 }
270
271 let warnings = errors.warnings.clone();
272
273 if errors.has_errors() {
274 return CompileResult {
275 success: false,
276 wasm: None,
277 errors,
278 ast: Some(program),
279 source_hash,
280 wasm_hash: None,
281 state_fields: metadata.state_fields,
282 actions: metadata.actions,
283 views: metadata.views,
284 capabilities: metadata.capabilities,
285 credentials: metadata.credentials,
286 language_version: PEPL_LANGUAGE_VERSION.to_string(),
287 compiler_version: PEPL_COMPILER_VERSION.to_string(),
288 warnings,
289 source_map: None,
290 };
291 }
292
293 match pepl_codegen::compile_with_source_map(&program) {
295 Ok((wasm, source_map)) => {
296 let wasm_hash = sha256_hex(&wasm);
297 CompileResult {
298 success: true,
299 wasm: Some(wasm),
300 errors: CompileErrors::empty(),
301 ast: Some(program),
302 source_hash,
303 wasm_hash: Some(wasm_hash),
304 state_fields: metadata.state_fields,
305 actions: metadata.actions,
306 views: metadata.views,
307 capabilities: metadata.capabilities,
308 credentials: metadata.credentials,
309 language_version: PEPL_LANGUAGE_VERSION.to_string(),
310 compiler_version: PEPL_COMPILER_VERSION.to_string(),
311 warnings,
312 source_map: Some(source_map),
313 }
314 }
315 Err(e) => {
316 let mut errors = CompileErrors::empty();
317 errors.push_error(codegen_error_to_pepl_error(&e, name));
318 CompileResult {
319 success: false,
320 wasm: None,
321 errors,
322 ast: Some(program),
323 source_hash,
324 wasm_hash: None,
325 state_fields: metadata.state_fields,
326 actions: metadata.actions,
327 views: metadata.views,
328 capabilities: metadata.capabilities,
329 credentials: metadata.credentials,
330 language_version: PEPL_LANGUAGE_VERSION.to_string(),
331 compiler_version: PEPL_COMPILER_VERSION.to_string(),
332 warnings,
333 source_map: None,
334 }
335 }
336 }
337}
338
339struct SpaceMetadata {
342 state_fields: Vec<FieldInfo>,
343 actions: Vec<ActionInfo>,
344 views: Vec<String>,
345 capabilities: Vec<String>,
346 credentials: Vec<FieldInfo>,
347}
348
349fn extract_metadata(program: &Program) -> SpaceMetadata {
350 let body = &program.space.body;
351
352 let state_fields = body
353 .state
354 .fields
355 .iter()
356 .map(|f| FieldInfo {
357 name: f.name.name.clone(),
358 ty: format!("{}", f.type_ann),
359 })
360 .collect();
361
362 let actions = body
363 .actions
364 .iter()
365 .map(|a| ActionInfo {
366 name: a.name.name.clone(),
367 params: a
368 .params
369 .iter()
370 .map(|p| FieldInfo {
371 name: p.name.name.clone(),
372 ty: format!("{}", p.type_ann),
373 })
374 .collect(),
375 })
376 .collect();
377
378 let views = body.views.iter().map(|v| v.name.name.clone()).collect();
379
380 let capabilities = body
381 .capabilities
382 .as_ref()
383 .map(|c| {
384 c.required
385 .iter()
386 .chain(c.optional.iter())
387 .map(|i| i.name.clone())
388 .collect()
389 })
390 .unwrap_or_default();
391
392 let credentials = body
393 .credentials
394 .as_ref()
395 .map(|c| {
396 c.fields
397 .iter()
398 .map(|f| FieldInfo {
399 name: f.name.name.clone(),
400 ty: format!("{}", f.type_ann),
401 })
402 .collect()
403 })
404 .unwrap_or_default();
405
406 SpaceMetadata {
407 state_fields,
408 actions,
409 views,
410 capabilities,
411 credentials,
412 }
413}
414
415fn sha256_hex(data: &[u8]) -> String {
418 let mut hasher = Sha256::new();
419 hasher.update(data);
420 let result = hasher.finalize();
421 hex_encode(&result)
422}
423
424fn hex_encode(bytes: &[u8]) -> String {
425 let mut s = String::with_capacity(bytes.len() * 2);
426 for b in bytes {
427 use std::fmt::Write;
428 write!(s, "{:02x}", b).unwrap();
429 }
430 s
431}
432
433fn codegen_error_to_pepl_error(e: &CodegenError, file: &str) -> pepl_types::PeplError {
435 use pepl_types::{ErrorCode, Span};
436
437 let (code, message) = match e {
438 CodegenError::Unsupported(msg) => (ErrorCode(700), format!("Unsupported: {}", msg)),
439 CodegenError::Internal(msg) => (ErrorCode(701), format!("Internal: {}", msg)),
440 CodegenError::ValidationFailed(msg) => {
441 (ErrorCode(702), format!("WASM validation failed: {}", msg))
442 }
443 CodegenError::UnresolvedSymbol(msg) => {
444 (ErrorCode(703), format!("Unresolved symbol: {}", msg))
445 }
446 CodegenError::LimitExceeded(msg) => {
447 (ErrorCode(704), format!("Limit exceeded: {}", msg))
448 }
449 };
450
451 pepl_types::PeplError::new(file, code, message, Span::new(1, 1, 1, 1), "")
452}