///|
fn numeric_version_compare(a : StringView, b : StringView) -> Int32 {
if a.length() != b.length() {
return if a.length() < b.length() { -1 } else { 1 }
}
string_lex_compare(a.to_owned(), b.to_owned())
}
///|
fn semver_compare(a : String, b : String) -> Int32 {
let a = a.split_once("+").map(p => p.0).unwrap_or(a[:])
let b = b.split_once("+").map(p => p.0).unwrap_or(b[:])
let (ac, ap) = match a.split_once("-") {
Some((c, p)) => (c, Some(p))
None => (a, None)
}
let (bc, bp) = match b.split_once("-") {
Some((c, p)) => (c, Some(p))
None => (b, None)
}
let an = ac.split(".").to_array()
let bn = bc.split(".").to_array()
for i in 0..<3 {
let cmp = numeric_version_compare(an[i], bn[i])
if cmp != 0 {
return cmp
}
}
match (ap, bp) {
(None, None) => 0
(None, Some(_)) => 1
(Some(_), None) => -1
(Some(a), Some(b)) => {
let an = a.split(".").to_array()
let bn = b.split(".").to_array()
let n = if an.length() < bn.length() { an.length() } else { bn.length() }
for i in 0.. ch.is_ascii_digit())
let bd = bn[i].iter().all(ch => ch.is_ascii_digit())
let cmp = if ad && bd {
numeric_version_compare(an[i], bn[i])
} else if ad != bd {
if ad {
-1
} else {
1
}
} else {
string_lex_compare(an[i].to_owned(), bn[i].to_owned())
}
if cmp != 0 {
return cmp
}
}
if an.length() == bn.length() {
0
} else if an.length() < bn.length() {
-1
} else {
1
}
}
}
}
///|
fn validate_attributes(
attributes : Attributes,
pkg : PackageName?,
) -> Unit raise ParseError {
if attributes.since is Some(_) && attributes.unstable is Some(_) {
raise ParseError::Message("since and unstable are mutually exclusive", 0)
}
if attributes.deprecated is Some(_) &&
attributes.since is None &&
attributes.unstable is None {
raise ParseError::Message("deprecated requires since or unstable", 0)
}
for version in [attributes.since, attributes.deprecated] {
if version is Some(version) {
if !valid_semver(version) {
raise ParseError::Message("invalid annotation version", 0)
}
let current = match pkg {
Some(pkg) => pkg.version
None => None
}
guard current is Some(current) else {
raise ParseError::Message(
"versioned annotation requires package version", 0,
)
}
if semver_compare(version, current) > 0 {
raise ParseError::Message(
"annotation references an unreleased version", 0,
)
}
}
}
if attributes.since is Some(since) &&
attributes.deprecated is Some(deprecated) &&
semver_compare(deprecated, since) < 0 {
raise ParseError::Message("deprecation precedes introduction", 0)
}
}
///|
fn attributes_enabled(
attributes : Attributes,
options : ResolveOptions,
) -> Bool {
if attributes.unstable is Some(feature) &&
!options.all_features &&
!options.features.contains(feature) {
return false
}
if attributes.since is Some(since) &&
options.target_version is Some(target) &&
semver_compare(since, target) > 0 {
return false
}
true
}
///|
fn prepare_function(
func : Function,
pkg : PackageName?,
options : ResolveOptions,
) -> Function? raise ParseError {
validate_attributes(func.attributes, pkg)
if attributes_enabled(func.attributes, options) {
Some(func)
} else {
None
}
}
///|
fn prepare_definition(
def : TypeDef,
pkg : PackageName?,
options : ResolveOptions,
) -> TypeDef? raise ParseError {
validate_attributes(def.attributes, pkg)
let def = match def.kind {
Resource(resource) =>
{
..def,
kind: Resource({
funcs: resource.funcs.filter_map(func => {
prepare_function(func, pkg, options)
}),
}),
}
_ => def
}
if attributes_enabled(def.attributes, options) {
Some(def)
} else {
None
}
}
///|
fn prepare_interface_items(
items : Array[InterfaceItem],
pkg : PackageName?,
options : ResolveOptions,
) -> Array[InterfaceItem] raise ParseError {
items.filter_map(item => {
match item {
Function(func) =>
prepare_function(func, pkg, options).map(func => {
InterfaceItem::Function(func)
})
TypeDef(def) =>
prepare_definition(def, pkg, options).map(def => {
InterfaceItem::TypeDef(def)
})
Use(decl) => {
validate_attributes(decl.attributes, pkg)
if attributes_enabled(decl.attributes, options) {
Some(item)
} else {
None
}
}
}
})
}
///|
fn prepare_world_item(
item : WorldItem,
pkg : PackageName?,
options : ResolveOptions,
) -> WorldItem? raise ParseError {
match item {
External(id, item) =>
prepare_world_item(item, pkg, options).map(item => {
WorldItem::External(id, item)
})
Attributed(attributes, item) => {
validate_attributes(attributes, pkg)
let prepared = prepare_world_item(item, pkg, options)
if attributes_enabled(attributes, options) {
prepared.map(item => WorldItem::Attributed(attributes, item))
} else {
None
}
}
Function(func) =>
prepare_function(func, pkg, options).map(func => WorldItem::Function(func))
InlineInterface(iface) =>
Some(
InlineInterface({
..iface,
items: prepare_interface_items(iface.items, pkg, options),
}),
)
Use(decl) => {
validate_attributes(decl.attributes, pkg)
if attributes_enabled(decl.attributes, options) {
Some(item)
} else {
None
}
}
Include(decl) => {
validate_attributes(decl.attributes, pkg)
if attributes_enabled(decl.attributes, options) {
Some(item)
} else {
None
}
}
_ => Some(item)
}
}
///|
fn prepare_ast(
ast : WitFile,
options : ResolveOptions,
) -> WitFile raise ParseError {
let interfaces = ast.interfaces.filter_map(iface => {
validate_attributes(iface.attributes, ast.pkg)
let items = prepare_interface_items(iface.items, ast.pkg, options)
if attributes_enabled(iface.attributes, options) {
Some({ ..iface, items, })
} else {
None
}
})
let worlds = ast.worlds.filter_map(world => {
validate_attributes(world.attributes, ast.pkg)
let types = world.types.filter_map(def => {
prepare_definition(def, ast.pkg, options)
})
let imports = world.imports.filter_map(item => {
prepare_world_item(item, ast.pkg, options)
})
let exports = world.exports.filter_map(item => {
prepare_world_item(item, ast.pkg, options)
})
if attributes_enabled(world.attributes, options) {
Some({ ..world, types, imports, exports, })
} else {
None
}
})
let uses = ast.uses.filter(decl => {
validate_attributes(decl.attributes, ast.pkg)
attributes_enabled(decl.attributes, options)
})
{
..ast,
interfaces,
worlds,
uses,
packages: ast.packages.map(ast => prepare_ast(ast, options)),
}
}
///|
fn validate_toplevel_uses(asts : Array[WitFile]) -> Unit raise ParseError {
let names : Map[String, Bool] = Map([])
for ast in asts {
let pkg = match ast.pkg {
Some(pkg) => pkg_key(to_rpkg(pkg))
None => ""
}
for iface in ast.interfaces {
names.set(pkg + "/" + iface.name, true)
}
for world in ast.worlds {
names.set(pkg + "/" + world.name, true)
}
}
for ast in asts {
for decl in ast.uses {
let pkg = match
(
match decl.from.pkg {
Some(pkg) => Some(pkg)
None => ast.pkg
} : PackageName?) {
Some(pkg) => pkg_key(to_rpkg(pkg))
None => ""
}
if !names.contains(pkg + "/" + decl.from.interface) {
raise ParseError::Message(
"unknown top-level use: " + decl.from.interface,
0,
)
}
}
}
}