1use anyhow::{Context, Result, ensure};
22use std::collections::BTreeMap;
23
24#[derive(Clone, Debug, Default)]
25pub struct Source {
26 pub name: Option<String>,
28 pub contents: Vec<u8>,
29}
30
31impl From<&[u8]> for Source {
32 fn from(contents: &[u8]) -> Self {
33 Self {
34 contents: contents.to_vec(),
35 name: None,
36 }
37 }
38}
39
40#[derive(Clone, Debug)]
42pub struct Module {
43 pub name: String,
44 pub contents: Vec<u8>,
45}
46
47#[derive(Clone, Debug, Default)]
48pub struct Program {
49 pub source: Source,
50 pub modules: Vec<Module>,
51}
52
53#[derive(Clone, Debug)]
54pub enum Package {
55 Sources(Vec<Source>),
56 Encoded(Vec<u8>),
57}
58
59impl From<&[u8]> for Package {
60 fn from(contents: &[u8]) -> Self {
61 Self::Sources(vec![contents.into()])
62 }
63}
64
65#[derive(Clone, Debug)]
66pub struct Wit {
67 pub application: Package,
68 pub dependencies: Vec<Package>,
69}
70
71#[derive(Clone, Debug, Default)]
72pub struct Options {
73 pub world: Option<String>,
74 pub core_only: bool,
76}
77
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct Artifact {
80 pub name: String,
81 pub contents: Vec<u8>,
82}
83
84pub(crate) fn text<'a>(contents: &'a [u8], name: Option<&str>) -> Result<&'a str> {
85 std::str::from_utf8(contents)
86 .with_context(|| format!("{} is not UTF-8", name.unwrap_or("input")))
87}
88
89fn module_name(name: &str) -> Result<&str> {
90 ensure!(
91 !name.is_empty()
92 && !name.contains(['\\', '\0', ':'])
93 && name
94 .split('/')
95 .all(|part| !part.is_empty() && part != "." && part != ".."),
96 "module identity must be package-relative: {name:?}"
97 );
98 Ok(name)
99}
100
101pub(crate) fn program(input: &Program) -> Result<(&str, &str, BTreeMap<String, String>)> {
102 let identity = module_name(input.source.name.as_deref().unwrap_or("input.ts"))?;
103 let mut modules = BTreeMap::new();
104 for source in &input.modules {
105 let name = module_name(&source.name)?;
106 ensure!(
107 name != identity && !modules.contains_key(name),
108 "duplicate module identity: {name}"
109 );
110 modules.insert(
111 name.to_owned(),
112 text(&source.contents, Some(name))?.to_owned(),
113 );
114 }
115 Ok((
116 text(&input.source.contents, Some(identity))?,
117 identity,
118 modules,
119 ))
120}
121
122fn compile_impl(input: &Program, wit: &Wit, options: &Options) -> Result<crate::Compiled> {
124 let (source, identity, modules) = program(input)?;
125 let (resolve, package) = crate::component::wit::resolve_contents(wit)?;
126 let world = crate::component::wit::select_world(&resolve, package, options.world.as_deref())?;
127 crate::compiler::compile_sources(
128 source,
129 identity,
130 &modules,
131 &resolve,
132 world,
133 !options.core_only,
134 false,
135 )
136}
137
138fn compile_core_impl(input: &Program) -> Result<Vec<u8>> {
140 let (source, identity, modules) = program(input)?;
141 crate::compiler::compile_core_sources(source, identity, &modules, false)
142}
143
144fn generate_sdk_impl(wit: &Wit, world: Option<&str>) -> Result<Vec<Artifact>> {
146 let (resolve, package) = crate::component::wit::resolve_contents(wit)?;
147 let world = crate::component::wit::select_world(&resolve, package, world)?;
148 crate::sdk::declarations(&resolve, world)
149}
150
151pub fn compile(
188 input: &Program,
189 wit: &Wit,
190 options: &Options,
191) -> std::result::Result<crate::Compiled, crate::CompileError> {
192 compile_impl(input, wit, options).map_err(|error| {
193 crate::CompileError::from_error(error, crate::DiagnosticCode::InvalidInput)
194 })
195}
196
197pub fn compile_core(input: &Program) -> std::result::Result<Vec<u8>, crate::CompileError> {
198 compile_core_impl(input).map_err(|error| {
199 crate::CompileError::from_error(error, crate::DiagnosticCode::InvalidInput)
200 })
201}
202
203pub fn generate_sdk(
204 wit: &Wit,
205 world: Option<&str>,
206) -> std::result::Result<Vec<Artifact>, crate::CompileError> {
207 generate_sdk_impl(wit, world).map_err(|error| {
208 crate::CompileError::from_error(error, crate::DiagnosticCode::WitResolution)
209 })
210}