1#![doc = include_str!("../README.md")]
2#![allow(clippy::missing_safety_doc)]
3
4use std::borrow::Cow;
5use std::collections::HashMap;
6use std::ffi::{CStr, CString};
7use std::ops::Deref;
8use std::os::raw::{c_char, c_void};
9use std::ptr;
10
11use wesl::syntax::TranslationUnit;
12use wesl::{ModulePath, ResolveError, Resolver, VirtualResolver, Wesl};
13
14#[cfg(feature = "eval")]
15use wesl::{
16 eval::{Eval, EvalAttrs, Inputs, Instance, RefInstance},
17 syntax::{AccessMode, AddressSpace},
18};
19
20#[allow(unused)]
22pub struct WeslCompiler {
23 compiler: Wesl<wesl::NoResolver>,
24}
25
26pub struct WeslTranslationUnit {
27 unit: TranslationUnit,
28}
29
30#[repr(u8)]
32#[derive(Copy, Clone)]
33pub enum WeslManglerKind {
34 WeslManglerEscape = 0,
35 WeslManglerHash = 1,
36 WeslManglerNone = 2,
37}
38
39#[repr(u8)]
41#[derive(Copy, Clone)]
42pub enum WeslBindingType {
43 WeslBindingUniform = 0,
44 WeslBindingStorage = 1,
45 WeslBindingReadOnlyStorage = 2,
46 WeslBindingFiltering = 3,
47 WeslBindingNonFiltering = 4,
48 WeslBindingComparison = 5,
49 WeslBindingFloat = 6,
50 WeslBindingUnfilterableFloat = 7,
51 WeslBindingSint = 8,
52 WeslBindingUint = 9,
53 WeslBindingDepth = 10,
54 WeslBindingWriteOnly = 11,
55 WeslBindingReadWrite = 12,
56 WeslBindingReadOnly = 13,
57}
58
59#[repr(C)]
60#[derive(Copy, Clone)]
61pub struct WeslBinding {
62 pub group: u32,
63 pub binding: u32,
64 pub kind: WeslBindingType,
65 pub data_len: usize,
66 pub data: *const u8,
67}
68
69#[repr(C)]
70pub struct WeslResolveSourceResult {
71 pub success: bool,
72 pub source: *const c_char,
73}
74
75#[repr(C)]
76pub struct WeslResolveModuleResult {
77 pub success: bool,
78 pub module: *mut WeslTranslationUnit,
79}
80
81pub type WeslResolveSourceFunction = unsafe extern "C" fn(
82 path: *const c_char,
83 userdata: *mut c_void,
84) -> *mut WeslResolveSourceResult;
85
86pub type WeslResolveSourceFreeFunction =
87 unsafe extern "C" fn(result: *const WeslResolveSourceResult, userdata: *mut c_void);
88
89pub type WeslResolveModuleFunction = unsafe extern "C" fn(
90 path: *const c_char,
91 userdata: *mut c_void,
92) -> *mut WeslResolveModuleResult;
93pub type WeslResolveModuleFreeFunction =
94 unsafe extern "C" fn(result: *const WeslResolveModuleResult, userdata: *mut c_void);
95
96pub type WeslResolveModuleFunctionOption = Option<
99 unsafe extern "C" fn(
100 path: *const c_char,
101 userdata: *mut c_void,
102 ) -> *mut WeslResolveModuleResult,
103>;
104pub type WeslResolveModuleFreeFunctionOption =
105 Option<unsafe extern "C" fn(result: *const WeslResolveModuleResult, userdata: *mut c_void)>;
106
107pub type WeslResolveStringFunction =
108 unsafe extern "C" fn(path: *const c_char, userdata: *mut c_void) -> *const c_char;
109pub type WeslResolveFreeStringFunction =
110 unsafe extern "C" fn(result: *const c_char, userdata: *mut c_void);
111
112pub type WeslResolveStringFunctionOption =
113 Option<unsafe extern "C" fn(path: *const c_char, userdata: *mut c_void) -> *const c_char>;
114pub type WeslResolveFreeStringFunctionOption =
115 Option<unsafe extern "C" fn(result: *const c_char, userdata: *mut c_void)>;
116
117#[repr(C)]
118#[derive(Copy, Clone)]
119pub struct WeslResolverOptions {
120 pub userdata: *mut std::ffi::c_void,
121
122 pub resolve_source: WeslResolveSourceFunction,
123 pub resolve_source_free: WeslResolveSourceFreeFunction,
124
125 pub resolve_module: WeslResolveModuleFunctionOption,
126 pub resolve_module_free: WeslResolveModuleFreeFunctionOption,
127
128 pub display_name: WeslResolveStringFunctionOption,
129 pub free_display_name: WeslResolveFreeStringFunctionOption,
130
131 pub fs_path: WeslResolveStringFunctionOption,
132 pub free_fs_path: WeslResolveFreeStringFunctionOption,
133}
134
135#[repr(C)]
136pub struct WeslCompileOptions {
137 pub mangler: WeslManglerKind,
138 pub sourcemap: bool,
139 pub imports: bool,
140 pub condcomp: bool,
141 pub generics: bool,
142 pub strip: bool,
143 pub lower: bool,
144 pub validate: bool,
145 pub naga: bool,
146 pub lazy: bool,
147 pub keep_root: bool,
148 pub mangle_root: bool,
149 pub resolver: *const WeslResolverOptions,
150}
151#[repr(C)]
152pub struct WeslStringMap {
153 pub keys: *const *const c_char,
154 pub values: *const *const c_char,
155 pub len: usize,
156}
157
158#[repr(C)]
159pub struct WeslBoolMap {
160 pub keys: *const *const c_char,
161 pub values: *const bool,
162 pub len: usize,
163}
164#[repr(C)]
165pub struct WeslStringArray {
166 pub items: *const *const c_char,
167 pub len: usize,
168}
169
170#[repr(C)]
171pub struct WeslBindingArray {
172 pub items: *const WeslBinding,
173 pub len: usize,
174}
175
176#[repr(C)]
177pub struct WeslDiagnostic {
178 pub file: *const c_char,
179 pub span_start: usize,
180 pub span_end: usize,
181 pub title: *const c_char,
182}
183
184#[repr(C)]
185pub struct WeslError {
186 pub source: *const c_char,
187 pub message: *const c_char,
188 pub diagnostics: *const WeslDiagnostic,
189 pub diagnostics_len: usize,
190}
191
192#[repr(C)]
193pub struct WeslResult {
194 pub success: bool,
195 pub data: *const c_char,
196 pub error: WeslError,
197}
198
199#[repr(C)]
200pub struct WeslParseResult {
201 pub success: bool,
202 pub data: *const WeslTranslationUnit,
203 pub error: WeslError,
204}
205
206#[repr(C)]
207pub struct WeslExecResult {
208 pub success: bool,
209 pub resources: *const WeslBindingArray,
210 pub error: WeslError,
211}
212
213fn map_mangler_kind(value: WeslManglerKind) -> Option<wesl::ManglerKind> {
214 match value {
215 WeslManglerKind::WeslManglerNone => Some(wesl::ManglerKind::None),
216 WeslManglerKind::WeslManglerHash => Some(wesl::ManglerKind::Hash),
217 WeslManglerKind::WeslManglerEscape => Some(wesl::ManglerKind::Escape),
218 }
219}
220
221unsafe fn string_map_to_hashmap(map: &WeslStringMap) -> HashMap<String, String> {
224 let mut result = HashMap::new();
225
226 for i in 0..map.len {
227 unsafe {
228 let key_ptr = *map.keys.add(i);
229 let value_ptr = *map.values.add(i);
230
231 if !key_ptr.is_null() && !value_ptr.is_null() {
232 let key = CStr::from_ptr(key_ptr).to_string_lossy().into_owned();
233 let value = CStr::from_ptr(value_ptr).to_string_lossy().into_owned();
234 result.insert(key, value);
235 }
236 }
237 }
238
239 result
240}
241
242unsafe fn bool_map_to_hashmap(map: &WeslBoolMap) -> HashMap<String, bool> {
243 let mut result = HashMap::new();
244
245 for i in 0..map.len {
246 unsafe {
247 let key_ptr = *map.keys.add(i);
248 let value = *map.values.add(i);
249
250 if !key_ptr.is_null() {
251 let key = CStr::from_ptr(key_ptr).to_string_lossy().into_owned();
252 result.insert(key, value);
253 }
254 }
255 }
256
257 result
258}
259
260unsafe fn string_array_to_vec(array: &WeslStringArray) -> Option<Vec<String>> {
261 let mut result = Vec::new();
262
263 for i in 0..array.len {
264 unsafe {
265 let item_ptr = *array.items.add(i);
266 if !item_ptr.is_null() {
267 let item = CStr::from_ptr(item_ptr).to_string_lossy().into_owned();
268 result.push(item);
269 }
270 }
271 }
272
273 Some(result)
274}
275
276fn create_c_string(s: &str) -> *const c_char {
277 match CString::new(s) {
278 Ok(c_str) => {
279 let ptr = c_str.as_ptr();
280 std::mem::forget(c_str);
281 ptr
282 }
283 Err(_) => ptr::null(),
284 }
285}
286
287fn wesl_error_to_c(e: wesl::Error) -> WeslError {
288 let d = wesl::Diagnostic::from(e);
289
290 let diagnostics = if let (Some(span), Some(res)) = (&d.detail.span, &d.detail.module_path) {
291 let diag = WeslDiagnostic {
292 file: create_c_string(&res.components.join("/")),
293 span_start: span.start,
294 span_end: span.end,
295 title: create_c_string(&d.error.to_string()),
296 };
297
298 let boxed = Box::new(diag);
299
300 Box::into_raw(boxed)
301 } else {
302 ptr::null()
303 };
304
305 WeslError {
306 source: d
307 .detail
308 .output
309 .as_ref()
310 .map_or(ptr::null(), |s| create_c_string(s)),
311 message: create_c_string(&d.to_string()),
312 diagnostics,
313 diagnostics_len: if diagnostics.is_null() {
314 0
315 } else {
316 1
317 },
318 }
319}
320
321#[cfg(feature = "eval")]
322unsafe fn binding_array_to_vec(array: &WeslBindingArray) -> Vec<WeslBinding> {
323 let mut result = Vec::new();
324
325 for i in 0..array.len {
326 let binding = unsafe { *array.items.add(i) };
327 result.push(binding);
328 }
329
330 result
331}
332
333#[cfg(feature = "eval")]
334fn parse_c_binding(
335 b: &WeslBinding,
336 wgsl: &wesl::syntax::TranslationUnit,
337) -> Result<((u32, u32), RefInstance), wesl::Error> {
338 let mut ctx = wesl::eval::Context::new(wgsl);
339
340 let ty_expr = wgsl
341 .global_declarations
342 .iter()
343 .find_map(|d| match d.node() {
344 wesl::syntax::GlobalDeclaration::Declaration(d) => {
345 let (group, binding) = d.attr_group_binding(&mut ctx).ok()?;
346 if group == b.group && binding == b.binding {
347 d.ty.clone()
348 } else {
349 None
350 }
351 }
352 _ => None,
353 })
354 .ok_or_else(|| {
355 wesl::Error::Custom(format!(
356 "Resource @group({}) @binding({}) not found",
357 b.group, b.binding
358 ))
359 })?;
360
361 let ty = wesl::eval::ty_eval_ty(&ty_expr, &mut ctx)
362 .map_err(|e| wesl::Error::Custom(format!("Failed to evaluate type: {e}")))?;
363
364 let (storage, access) = match b.kind {
365 WeslBindingType::WeslBindingUniform => (AddressSpace::Uniform, AccessMode::Read),
366 WeslBindingType::WeslBindingStorage => (AddressSpace::Storage, AccessMode::ReadWrite),
367 WeslBindingType::WeslBindingReadOnlyStorage => (AddressSpace::Storage, AccessMode::Read),
368 _ => return Err(wesl::Error::Custom("Unsupported binding type".to_string())),
369 };
370
371 let data_slice = unsafe { std::slice::from_raw_parts(b.data, b.data_len) };
372 let inst = Instance::from_buffer(data_slice, &ty).ok_or_else(|| {
373 wesl::Error::Custom(format!(
374 "Resource @group({}) @binding({}) ({} bytes) incompatible with type ({} bytes)",
375 b.group,
376 b.binding,
377 b.data_len,
378 ty.size_of().unwrap_or_default()
379 ))
380 })?;
381
382 Ok((
383 (b.group, b.binding),
384 RefInstance::new(inst, storage, access),
385 ))
386}
387
388#[cfg(feature = "eval")]
389fn create_c_binding_array(bindings: Vec<WeslBinding>) -> *const WeslBindingArray {
390 if bindings.is_empty() {
391 return ptr::null();
392 }
393
394 let items = bindings.into_boxed_slice();
395 let len = items.len();
396 let items_ptr = Box::into_raw(items) as *const WeslBinding;
397
398 let array = Box::new(WeslBindingArray {
399 items: items_ptr,
400 len,
401 });
402 Box::into_raw(array)
403}
404
405#[unsafe(no_mangle)]
408pub unsafe extern "C" fn wesl_create_compiler() -> *mut WeslCompiler {
409 let compiler = Wesl::new_barebones();
410 Box::into_raw(Box::new(WeslCompiler { compiler }))
411}
412
413#[unsafe(no_mangle)]
414pub unsafe extern "C" fn wesl_destroy_compiler(compiler: *mut WeslCompiler) {
415 if !compiler.is_null() {
416 let _ = unsafe { Box::from_raw(compiler) };
417 }
418}
419
420fn error_from_str(s: &str) -> WeslError {
421 WeslError {
422 source: ptr::null(),
423 message: create_c_string(s),
424 diagnostics: ptr::null(),
425 diagnostics_len: 0,
426 }
427}
428
429fn result_from_str(s: &str) -> WeslResult {
430 WeslResult {
431 success: false,
432 data: ptr::null(),
433 error: error_from_str(s),
434 }
435}
436
437fn result_invalid_parameters() -> WeslResult {
438 result_from_str("Invalid parameters")
439}
440
441const NO_ERROR: WeslError = WeslError {
442 source: ptr::null(),
443 message: ptr::null(),
444 diagnostics: ptr::null(),
445 diagnostics_len: 0,
446};
447
448struct CustomResolver {
449 pub options: WeslResolverOptions,
450}
451
452struct FreeGuard<T> {
453 pub data: *const T,
454 pub free_function: unsafe extern "C" fn(*const T, *mut c_void),
455 pub free_userdata: *mut c_void,
456}
457
458impl<T> Drop for FreeGuard<T> {
459 fn drop(&mut self) {
460 unsafe {
461 (self.free_function)(self.data, self.free_userdata);
462 }
463 }
464}
465
466impl<T> Deref for FreeGuard<T> {
467 type Target = T;
468
469 fn deref(&self) -> &Self::Target {
470 unsafe { &*self.data }
471 }
472}
473
474impl FreeGuard<c_char> {
475 unsafe fn c_str(&self) -> &CStr {
476 unsafe { CStr::from_ptr(self.data) }
477 }
478}
479
480fn mod_path_to_cstring(path: &ModulePath) -> CString {
481 let path = path.to_string();
482
483 CString::new(path).expect("Module path contained nul bytes!")
484}
485
486unsafe fn resolver_path_to_string<T, F: FnOnce(Cow<'_, str>) -> T>(
487 path: &ModulePath,
488 transform: F,
489 get_func: Option<WeslResolveStringFunction>,
490 free_func: Option<WeslResolveFreeStringFunction>,
491 userdata: *mut c_void,
492) -> Option<T> {
493 let get_func = get_func?;
494 let free_func = free_func?;
495
496 let cstring = mod_path_to_cstring(path);
497
498 let result = unsafe { get_func(cstring.as_ptr(), userdata) };
499
500 if result.is_null() {
501 return None;
502 }
503
504 let result = FreeGuard {
505 data: result,
506 free_function: free_func,
507 free_userdata: userdata,
508 };
509
510 let result_cstr = unsafe { result.c_str() };
511 let result_str = result_cstr.to_string_lossy();
512
513 let ret_result = transform(result_str);
514
515 Some(ret_result)
516}
517
518struct ProxyResolver<'a, T>(&'a T);
520
521impl<T: Resolver> wesl::Resolver for ProxyResolver<'_, T> {
522 fn resolve_source<'a>(&'a self, path: &ModulePath) -> Result<Cow<'a, str>, ResolveError> {
523 self.0.resolve_source(path)
524 }
525}
526
527impl wesl::Resolver for CustomResolver {
528 fn resolve_source<'a>(
529 &'a self,
530 path: &ModulePath,
531 ) -> Result<std::borrow::Cow<'a, str>, ResolveError> {
532 let cstring = mod_path_to_cstring(path);
533
534 let result =
535 unsafe { (self.options.resolve_source)(cstring.as_ptr(), self.options.userdata) };
536
537 if result.is_null() {
538 return Err(ResolveError::Error(
539 wesl::Error::Custom("No value returned from resolver".into()).into(),
540 ));
541 }
542
543 let result = FreeGuard {
544 data: result,
545 free_function: self.options.resolve_source_free,
546 free_userdata: self.options.userdata,
547 };
548
549 if !result.success {
550 return Err(ResolveError::Error(
552 wesl::Error::Custom("Custom resolver failed".into()).into(),
553 ));
554 }
555
556 let result_cstr = unsafe { CStr::from_ptr(result.source) };
557 let result_str = result_cstr.to_str().map_err(|_| {
558 ResolveError::Error(
559 wesl::Error::Custom("Resolved source is not valid UTF-8".into()).into(),
560 )
561 })?;
562
563 Ok(result_str.to_owned().into())
564 }
565
566 fn resolve_module(
567 &self,
568 path: &wesl::ModulePath,
569 ) -> Result<wesl::syntax::TranslationUnit, wesl::ResolveError> {
570 let Some(resolve) = self.options.resolve_module else {
571 return ProxyResolver(self).resolve_module(path);
573 };
574
575 let resolve_free = self.options.resolve_module_free.unwrap();
576 let cstring = mod_path_to_cstring(path);
577
578 let result = unsafe { resolve(cstring.as_ptr(), self.options.userdata) };
579
580 let result = FreeGuard {
581 data: result,
582 free_function: resolve_free,
583 free_userdata: self.options.userdata,
584 };
585
586 if !result.success {
587 return Err(ResolveError::Error(
589 wesl::Error::Custom("Custom resolver failed".into()).into(),
590 ));
591 }
592
593 let unit = unsafe { &*result.module };
594 Ok(unit.unit.clone())
595 }
596
597 fn display_name(&self, path: &ModulePath) -> Option<String> {
598 unsafe {
599 resolver_path_to_string(
600 path,
601 |str| str.into_owned(),
602 self.options.display_name,
603 self.options.free_display_name,
604 self.options.userdata,
605 )
606 }
607 }
608
609 fn fs_path(&self, path: &ModulePath) -> Option<std::path::PathBuf> {
610 unsafe {
611 resolver_path_to_string(
612 path,
613 |str| str.deref().into(),
614 self.options.display_name,
615 self.options.free_display_name,
616 self.options.userdata,
617 )
618 }
619 }
620}
621
622fn validate_resolver_options(options: &WeslResolverOptions) -> Result<(), &'static str> {
623 if options.resolve_module.is_none() ^ options.resolve_module_free.is_none() {
624 return Err("resolve_module and resolve_module_free must both be provide if either is");
625 }
626
627 if options.fs_path.is_none() ^ options.free_fs_path.is_none() {
628 return Err("fs_path and free_fs_path must both be provide if either is");
629 }
630
631 if options.display_name.is_none() ^ options.free_display_name.is_none() {
632 return Err("display_name and free_display_name must both be provide if either is");
633 }
634
635 Ok(())
636}
637
638#[unsafe(no_mangle)]
639pub unsafe extern "C" fn wesl_compile(
640 files: Option<&WeslStringMap>,
641 root: *const c_char,
642 options: &WeslCompileOptions,
643 keep: &WeslStringArray,
644 features: &WeslBoolMap,
645) -> WeslResult {
646 if root.is_null() {
647 return result_invalid_parameters();
648 }
649
650 let root_str = unsafe { CStr::from_ptr(root).to_string_lossy() };
651 let keep_vec = unsafe { string_array_to_vec(keep) };
652 let features_map = unsafe { bool_map_to_hashmap(features) };
653
654 let root_path = match root_str.parse() {
655 Ok(path) => path,
656 Err(e) => return result_from_str(&format!("Invalid root path: {e}")),
657 };
658
659 let resolver: Box<dyn wesl::Resolver> = match (files, options.resolver.is_null()) {
660 (Some(files), true) => {
661 let files_map = unsafe { string_map_to_hashmap(files) };
662 let mut resolver = VirtualResolver::new();
663 for (path, source) in files_map {
664 if let Ok(module_path) = path.parse() {
665 resolver.add_module(module_path, source.into());
666 }
667 }
668
669 Box::new(resolver)
670 }
671 (None, false) => {
672 let resolver_options = unsafe { &*options.resolver };
673 if let Err(msg) = validate_resolver_options(resolver_options) {
674 return result_from_str(msg);
675 }
676
677 Box::new(CustomResolver {
678 options: *resolver_options,
679 })
680 }
681 (Some(_), false) => {
682 return result_from_str("Files and custom resolver cannot be specified at once");
683 }
684 _ => return result_from_str("Files or custom resolver must be specified"),
685 };
686
687 let Some(mangler) = map_mangler_kind(options.mangler) else {
688 return result_from_str("Invalid mangler kind specified");
689 };
690
691 let mut compiler = Wesl::new_barebones().set_custom_resolver(resolver);
692 let compiler = compiler
693 .set_options(wesl::CompileOptions {
694 imports: options.imports,
695 condcomp: options.condcomp,
696 generics: options.generics,
697 strip: options.strip,
698 lower: options.lower,
699 validate: options.validate,
700 lazy: options.lazy,
701 mangle_root: options.mangle_root,
702 keep: keep_vec,
703 features: wesl::Features {
704 default: wesl::Feature::Disable,
705 flags: features_map
706 .into_iter()
707 .map(|(k, v)| (k, v.into()))
708 .collect(),
709 },
710 keep_root: options.keep_root,
711 })
712 .use_sourcemap(options.sourcemap)
713 .set_mangler(mangler);
714
715 match compiler.compile(&root_path) {
716 Ok(result) => {
717 let output = result.to_string();
718 WeslResult {
719 success: true,
720 data: create_c_string(&output),
721 error: NO_ERROR,
722 }
723 }
724 Err(e) => WeslResult {
725 success: false,
726 data: ptr::null(),
727 error: wesl_error_to_c(e),
728 },
729 }
730}
731
732#[unsafe(no_mangle)]
733pub unsafe extern "C" fn wesl_parse(source: *const c_char) -> WeslParseResult {
734 if source.is_null() {
735 return WeslParseResult {
736 success: false,
737 data: ptr::null_mut(),
738 error: error_from_str("Invalid parameters"),
739 };
740 };
741
742 let cstr = unsafe { CStr::from_ptr(source) };
743 let Ok(str) = cstr.to_str() else {
744 return WeslParseResult {
745 success: false,
746 data: ptr::null_mut(),
747 error: error_from_str("Source is not valid UTF-8"),
748 };
749 };
750
751 match str.parse::<TranslationUnit>() {
752 Ok(unit) => {
753 let ptr = Box::into_raw(Box::new(WeslTranslationUnit { unit }));
754 WeslParseResult {
755 success: true,
756 data: ptr,
757 error: NO_ERROR,
758 }
759 }
760 Err(e) => WeslParseResult {
761 success: false,
762 data: ptr::null_mut(),
763 error: wesl_error_to_c(wesl::Error::ParseError(e)),
764 },
765 }
766}
767
768#[cfg(feature = "eval")]
769#[unsafe(no_mangle)]
770pub unsafe extern "C" fn wesl_eval(
771 files: &WeslStringMap,
772 root: *const c_char,
773 expression: *const c_char,
774 options: &WeslCompileOptions,
775 features: Option<&WeslBoolMap>,
776) -> WeslResult {
777 let files_map = unsafe { string_map_to_hashmap(files) };
778 let root_str = unsafe { CStr::from_ptr(root).to_string_lossy() };
779 let expr_str = unsafe { CStr::from_ptr(expression).to_string_lossy() };
780 let features_map = features
781 .map(|features| unsafe { bool_map_to_hashmap(features) })
782 .unwrap_or_default();
783
784 let root_path = match root_str.parse() {
785 Ok(path) => path,
786 Err(e) => {
787 return WeslResult {
788 success: false,
789 data: ptr::null(),
790 error: WeslError {
791 source: ptr::null(),
792 message: create_c_string(&format!("Invalid root path: {e}")),
793 diagnostics: ptr::null(),
794 diagnostics_len: 0,
795 },
796 };
797 }
798 };
799
800 let mut resolver = VirtualResolver::new();
801 for (path, source) in files_map {
802 if let Ok(module_path) = path.parse() {
803 resolver.add_module(module_path, source.into());
804 }
805 }
806
807 let mut compiler = Wesl::new_barebones().set_custom_resolver(resolver);
808 let compiler = compiler
809 .set_options(wesl::CompileOptions {
810 imports: options.imports,
811 condcomp: options.condcomp,
812 generics: options.generics,
813 strip: options.strip,
814 lower: options.lower,
815 validate: options.validate,
816 lazy: options.lazy,
817 mangle_root: options.mangle_root,
818 keep: None,
819 features: wesl::Features {
820 default: wesl::Feature::Disable,
821 flags: features_map
822 .into_iter()
823 .map(|(k, v)| (k, v.into()))
824 .collect(),
825 },
826 keep_root: options.keep_root,
827 })
828 .use_sourcemap(options.sourcemap)
829 .set_mangler(map_mangler_kind(options.mangler).expect("invalid mangler kind"));
830
831 match compiler.compile(&root_path) {
832 Ok(result) => match result.eval(&expr_str) {
833 Ok(eval_result) => WeslResult {
834 success: true,
835 data: create_c_string(&eval_result.inst.to_string()),
836 error: WeslError {
837 source: ptr::null(),
838 message: ptr::null(),
839 diagnostics: ptr::null(),
840 diagnostics_len: 0,
841 },
842 },
843 Err(e) => WeslResult {
844 success: false,
845 data: ptr::null(),
846 error: wesl_error_to_c(e),
847 },
848 },
849 Err(e) => WeslResult {
850 success: false,
851 data: ptr::null(),
852 error: wesl_error_to_c(e),
853 },
854 }
855}
856
857#[cfg(not(feature = "eval"))]
858#[unsafe(no_mangle)]
859pub unsafe extern "C" fn wesl_eval(
860 _files: *const WeslStringMap,
861 _root: *const c_char,
862 _expression: *const c_char,
863 _options: *const WeslCompileOptions,
864 _features: *const WeslBoolMap,
865) -> WeslResult {
866 result_from_str("wesl_eval requires the 'eval' feature to be enabled")
867}
868
869#[cfg(feature = "eval")]
870#[unsafe(no_mangle)]
871pub unsafe extern "C" fn wesl_exec(
872 files: &WeslStringMap,
873 root: &c_char,
874 entrypoint: &c_char,
875 options: &WeslCompileOptions,
876 resources: Option<&WeslBindingArray>,
877 overrides: Option<&WeslStringMap>,
878 features: Option<&WeslBoolMap>,
879) -> WeslExecResult {
880 let files_map = unsafe { string_map_to_hashmap(files) };
881 let root_str = unsafe { CStr::from_ptr(root).to_string_lossy() };
882 let entrypoint_str = unsafe { CStr::from_ptr(entrypoint).to_string_lossy() };
883 let resources_vec = resources
884 .map(|resources| unsafe { binding_array_to_vec(resources) })
885 .unwrap_or_default();
886 let overrides_map = overrides
887 .map(|overrides| unsafe { string_map_to_hashmap(overrides) })
888 .unwrap_or_default();
889 let features_map: HashMap<String, bool> = features
890 .map(|features| unsafe { bool_map_to_hashmap(features) })
891 .unwrap_or_default();
892
893 let root_path = match root_str.parse() {
894 Ok(path) => path,
895 Err(e) => {
896 return WeslExecResult {
897 success: false,
898 resources: ptr::null(),
899 error: WeslError {
900 source: ptr::null(),
901 message: create_c_string(&format!("Invalid root path: {e}")),
902 diagnostics: ptr::null(),
903 diagnostics_len: 0,
904 },
905 };
906 }
907 };
908
909 let mut resolver = VirtualResolver::new();
910 for (path, source) in files_map {
911 if let Ok(module_path) = path.parse() {
912 resolver.add_module(module_path, source.into());
913 }
914 }
915
916 let mut compiler = Wesl::new_barebones().set_custom_resolver(resolver);
917 let compiler = compiler
918 .set_options(wesl::CompileOptions {
919 imports: options.imports,
920 condcomp: options.condcomp,
921 generics: options.generics,
922 strip: options.strip,
923 lower: options.lower,
924 validate: options.validate,
925 lazy: options.lazy,
926 mangle_root: options.mangle_root,
927 keep: None,
928 features: wesl::Features {
929 default: wesl::Feature::Disable,
930 flags: features_map
931 .into_iter()
932 .map(|(k, v)| (k, v.into()))
933 .collect(),
934 },
935 keep_root: options.keep_root,
936 })
937 .use_sourcemap(options.sourcemap)
938 .set_mangler(map_mangler_kind(options.mangler).expect("invalid mangler kind"));
939
940 match compiler.compile(&root_path) {
941 Ok(result) => {
942 let parsed_resources: Result<HashMap<(u32, u32), RefInstance>, wesl::Error> =
944 resources_vec
945 .iter()
946 .map(|r| parse_c_binding(r, &result.syntax))
947 .collect();
948
949 let parsed_resources = match parsed_resources {
950 Ok(resources) => resources,
951 Err(e) => {
952 return WeslExecResult {
953 success: false,
954 resources: ptr::null(),
955 error: wesl_error_to_c(e),
956 };
957 }
958 };
959
960 let parsed_overrides: Result<HashMap<String, Instance>, wesl::Error> = overrides_map
962 .iter()
963 .map(|(name, expr)| {
964 let mut ctx = wesl::eval::Context::new(&result.syntax);
965 let expr = expr.parse::<wesl::syntax::Expression>().map_err(|e| {
966 wesl::Error::Custom(format!("Failed to parse override expression: {e}"))
967 })?;
968 let inst = expr.eval_value(&mut ctx).map_err(|e| {
969 wesl::Error::Custom(format!("Failed to evaluate override: {e}"))
970 })?;
971 Ok((name.clone(), inst))
972 })
973 .collect();
974
975 let parsed_overrides = match parsed_overrides {
976 Ok(overrides) => overrides,
977 Err(e) => {
978 return WeslExecResult {
979 success: false,
980 resources: ptr::null(),
981 error: wesl_error_to_c(e),
982 };
983 }
984 };
985
986 let inputs = Inputs::new_zero_initialized();
988 match result.exec(&entrypoint_str, inputs, parsed_resources, parsed_overrides) {
989 Ok(exec_result) => {
990 let output_resources: Vec<WeslBinding> = resources_vec
992 .iter()
993 .filter_map(|r| {
994 let resource = exec_result.resource(r.group, r.binding)?;
995 let inst = resource.read().ok()?.to_owned();
996 let mut new_binding = *r;
997 if let Some(buffer) = inst.to_buffer() {
998 let boxed_data = buffer.into_boxed_slice();
999 new_binding.data_len = boxed_data.len();
1000 new_binding.data = Box::into_raw(boxed_data) as *const u8;
1001 }
1002 Some(new_binding)
1003 })
1004 .collect();
1005
1006 WeslExecResult {
1007 success: true,
1008 resources: create_c_binding_array(output_resources),
1009 error: WeslError {
1010 source: ptr::null(),
1011 message: ptr::null(),
1012 diagnostics: ptr::null(),
1013 diagnostics_len: 0,
1014 },
1015 }
1016 }
1017 Err(e) => WeslExecResult {
1018 success: false,
1019 resources: ptr::null(),
1020 error: wesl_error_to_c(e),
1021 },
1022 }
1023 }
1024 Err(e) => WeslExecResult {
1025 success: false,
1026 resources: ptr::null(),
1027 error: wesl_error_to_c(e),
1028 },
1029 }
1030}
1031
1032#[cfg(not(feature = "eval"))]
1033#[unsafe(no_mangle)]
1034pub unsafe extern "C" fn wesl_exec(
1035 _files: *const WeslStringMap,
1036 _root: *const c_char,
1037 _entrypoint: *const c_char,
1038 _options: *const WeslCompileOptions,
1039 _resources: *const WeslBindingArray,
1040 _overrides: *const WeslStringMap,
1041 _features: *const WeslBoolMap,
1042) -> WeslExecResult {
1043 WeslExecResult {
1044 success: false,
1045 resources: ptr::null(),
1046 error: error_from_str("wesl_exec requires the 'eval' feature to be enabled"),
1047 }
1048}
1049
1050#[unsafe(no_mangle)]
1053pub unsafe extern "C" fn wesl_free_string(ptr: *const c_char) {
1054 if !ptr.is_null() {
1055 let _ = unsafe { CString::from_raw(ptr as *mut c_char) };
1056 }
1057}
1058
1059unsafe fn free_error(error: &WeslError) {
1060 unsafe {
1061 if !error.source.is_null() {
1062 wesl_free_string(error.source);
1063 }
1064
1065 if !error.message.is_null() {
1066 wesl_free_string(error.message);
1067 }
1068
1069 if !error.diagnostics.is_null() {
1070 let diag = &*error.diagnostics;
1071 if !diag.file.is_null() {
1072 wesl_free_string(diag.file);
1073 }
1074 if !diag.title.is_null() {
1075 wesl_free_string(diag.title);
1076 }
1077 let _ = Box::from_raw(error.diagnostics as *mut WeslDiagnostic);
1078 }
1079 }
1080}
1081
1082#[unsafe(no_mangle)]
1083pub unsafe extern "C" fn wesl_free_result(result: *mut WeslResult) {
1084 if !result.is_null() {
1085 unsafe {
1086 let result = &mut *result;
1087
1088 if !result.data.is_null() {
1089 wesl_free_string(result.data);
1090 }
1091 }
1092 }
1093}
1094
1095#[unsafe(no_mangle)]
1096pub unsafe extern "C" fn wesl_free_exec_result(result: *mut WeslExecResult) {
1097 if !result.is_null() {
1098 unsafe {
1099 let result = &mut *result;
1100
1101 if !result.resources.is_null() {
1102 let resources = &*result.resources;
1103
1104 for i in 0..resources.len {
1106 let binding = *resources.items.add(i);
1107 if !binding.data.is_null() {
1108 let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
1109 binding.data as *mut u8,
1110 binding.data_len,
1111 ));
1112 }
1113 }
1114
1115 let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
1116 resources.items as *mut WeslBinding,
1117 resources.len,
1118 ));
1119
1120 let _ = Box::from_raw(result.resources as *mut WeslBindingArray);
1121 }
1122
1123 free_error(&result.error);
1124 }
1125 }
1126}
1127
1128#[unsafe(no_mangle)]
1129pub unsafe extern "C" fn wesl_free_parse_result(result: *mut WeslParseResult) {
1130 if !result.is_null() {
1131 unsafe {
1132 let result = &*result;
1133
1134 free_error(&result.error);
1135 }
1136 }
1137}
1138
1139#[unsafe(no_mangle)]
1140pub unsafe extern "C" fn wesl_free_translation_unit(unit: *mut WeslTranslationUnit) {
1141 if !unit.is_null() {
1142 let _ = unsafe { Box::from_raw(unit) };
1143 }
1144}
1145
1146#[unsafe(no_mangle)]
1150pub unsafe extern "C" fn wesl_version() -> *const c_char {
1151 const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
1152 VERSION.as_ptr() as *const c_char
1153}