1use crate::ast::Module;
10use crate::naming::{module_file_stem, to_pascal_case};
11use std::fmt::Write;
12
13#[derive(Debug, Clone, Default)]
15pub struct MesonConfig {
16 pub synta_root: Option<String>,
20 pub shared_library: bool,
23}
24
25pub fn generate_meson(
31 modules: &[Module],
32 order: &[usize],
33 config: MesonConfig,
34) -> Result<String, Box<dyn std::error::Error>> {
35 let mut out = String::new();
36
37 let module_names: Vec<&str> = order.iter().map(|&i| modules[i].name.as_str()).collect();
39 let primary_idx = *order.last().unwrap();
41 let project_name = module_file_stem(&modules[primary_idx].name);
42
43 writeln!(
44 out,
45 "# Generated from ASN.1 module{} {}",
46 if modules.len() == 1 { "" } else { "s" },
47 module_names.join(", ")
48 )?;
49 writeln!(out, "# DO NOT EDIT - auto-generated code")?;
50 writeln!(out)?;
51 writeln!(out, "project('{}', 'c',", project_name)?;
52 writeln!(out, " default_options: ['c_std=c99', 'warning_level=2'],")?;
53 writeln!(out, ")")?;
54 writeln!(out)?;
55 writeln!(out, "cc = meson.get_compiler('c')")?;
56 writeln!(out)?;
57
58 writeln!(
60 out,
61 "# ── Synta dependency ────────────────────────────────────────────────────────"
62 )?;
63 if let Some(ref root) = config.synta_root {
64 writeln!(out, "_synta_lib = cc.find_library('synta',")?;
65 writeln!(out, " dirs: '{}' / 'target' / 'release',", root)?;
66 writeln!(out, " required: true,")?;
67 writeln!(out, ")")?;
68 writeln!(out, "synta_dep = declare_dependency(")?;
69 writeln!(out, " dependencies: _synta_lib,")?;
70 writeln!(
71 out,
72 " include_directories: include_directories('{}' / 'include'),",
73 root
74 )?;
75 writeln!(out, ")")?;
76 } else {
77 writeln!(
78 out,
79 "# Resolved via pkg-config, a wrap file, or a parent subproject."
80 )?;
81 writeln!(
82 out,
83 "# If synta is not installed system-wide, specify its location with:"
84 )?;
85 writeln!(out, "# meson setup build -Dsynta_root=/path/to/synta")?;
86 writeln!(out, "# and add the following option to your meson.options:")?;
87 writeln!(out, "# option('synta_root', type: 'string', value: '',")?;
88 writeln!(out, "# description: 'Root of the synta source tree')")?;
89 writeln!(out, "synta_dep = dependency('synta')")?;
90 }
91 writeln!(out)?;
92
93 let lib_fn = if config.shared_library {
95 "shared_library"
96 } else {
97 "library"
98 };
99
100 let dep_var_for: std::collections::HashMap<&str, String> = modules
102 .iter()
103 .map(|m| {
104 let stem = module_file_stem(&m.name);
105 (m.name.as_str(), format!("{}_dep", stem))
106 })
107 .collect();
108
109 for &idx in order {
111 let module = &modules[idx];
112 let stem = module_file_stem(&module.name);
113 let pascal = to_pascal_case(&module.name);
114 let lib_var = format!("{}_lib", stem);
115 let dep_var = format!("{}_dep", stem);
116
117 let module_deps: Vec<&str> = module
119 .imports
120 .iter()
121 .filter_map(|imp| {
122 dep_var_for
123 .get(imp.module_name.as_str())
124 .map(|s| s.as_str())
125 })
126 .collect();
127
128 writeln!(out, "# ASN.1 module: {}", pascal)?;
129 writeln!(out, "{} = {}('{}',", lib_var, lib_fn, stem)?;
130 writeln!(out, " sources: ['{}.c'],", stem)?;
131 write!(out, " dependencies: [synta_dep")?;
132 for dep in &module_deps {
133 write!(out, ", {}", dep)?;
134 }
135 writeln!(out, "],")?;
136 writeln!(out, " install: true,")?;
137 writeln!(out, ")")?;
138 writeln!(out)?;
139 writeln!(out, "{} = declare_dependency(", dep_var)?;
140 writeln!(
141 out,
142 " include_directories: include_directories('.', is_system: false),"
143 )?;
144 writeln!(out, " link_with: {},", lib_var)?;
145 writeln!(out, ")")?;
146 writeln!(out)?;
147 }
148
149 Ok(out)
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::parse;
156
157 #[test]
158 fn test_single_module_meson() {
159 let m = parse("MyModule DEFINITIONS ::= BEGIN Foo ::= INTEGER END").unwrap();
160 let out = generate_meson(&[m], &[0], MesonConfig::default()).unwrap();
161
162 assert!(out.contains("project('my_module', 'c'"));
163 assert!(out.contains("c_std=c99"));
164 assert!(out.contains("library('my_module',"));
165 assert!(out.contains("sources: ['my_module.c']"));
166 assert!(out.contains("synta_dep = dependency('synta')"));
167 assert!(out.contains("my_module_dep = declare_dependency("));
168 }
169
170 #[test]
171 fn test_meson_with_synta_root() {
172 let m = parse("Cert DEFINITIONS ::= BEGIN END").unwrap();
173 let config = MesonConfig {
174 synta_root: Some("/opt/synta".to_string()),
175 shared_library: false,
176 };
177 let out = generate_meson(&[m], &[0], config).unwrap();
178
179 assert!(out.contains("cc.find_library('synta'"));
180 assert!(out.contains("'/opt/synta' / 'target' / 'release'"));
181 assert!(out.contains("'/opt/synta' / 'include'"));
182 assert!(!out.contains("dependency('synta')"));
184 }
185
186 #[test]
187 fn test_meson_shared_library() {
188 let m = parse("Foo DEFINITIONS ::= BEGIN END").unwrap();
189 let config = MesonConfig {
190 shared_library: true,
191 ..Default::default()
192 };
193 let out = generate_meson(&[m], &[0], config).unwrap();
194 assert!(out.contains("shared_library('foo',"));
195 }
196
197 #[test]
198 fn test_multi_module_meson_deps() {
199 let a = parse("ModA DEFINITIONS ::= BEGIN IMPORTS X FROM ModB; END").unwrap();
200 let b = parse("ModB DEFINITIONS ::= BEGIN END").unwrap();
201 let order = vec![1usize, 0usize];
203 let out = generate_meson(&[a, b], &order, MesonConfig::default()).unwrap();
204
205 assert!(out.contains("library('mod_b',"));
207 assert!(out.contains("library('mod_a',"));
209 assert!(out.contains("dependencies: [synta_dep, mod_b_dep]"));
211 }
212}