// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
pub(all) enum PathOrigin {
Absolute
Relative(Int)
Package(String)
} derive(Eq, Hash, Debug)
///|
pub(all) struct ModulePath {
origin : PathOrigin
components : Array[String]
} derive(Eq, Hash, Debug)
///|
pub(all) suberror ModulePathParseError {
Empty
MisplacedPackage
MisplacedSelf
MisplacedSuper
}
///|
pub fn ModulePathParseError::message(self : ModulePathParseError) -> String {
match self {
Empty => "module name cannot be empty"
MisplacedPackage => "`package` must be a prefix of the module path"
MisplacedSelf => "`self` must be a prefix of the module path"
MisplacedSuper => "`super` must be a prefix of the module path"
}
}
///|
pub fn ModulePath::new(
origin : PathOrigin,
components : Array[String],
) -> ModulePath {
{ origin, components }
}
///|
pub fn ModulePath::new_root() -> ModulePath {
ModulePath::new(Absolute, [])
}
///|
fn module_path_normalize_fs(path : String) -> String {
path.replace_all(old="\\", new="/")
}
///|
fn module_path_drop_extension(path : String) -> String {
let mut last_slash = -1
let mut last_dot = -1
for i in 0.. last_slash {
path[:last_dot].to_owned()
} else {
path
}
}
///|
fn module_path_split_non_empty(path : String, sep : String) -> Array[String] {
let parts : Array[String] = []
for part in path.split(sep) {
let text = part.to_owned()
if text != "" {
parts.push(text)
}
}
parts
}
///|
fn module_path_slice(
parts : Array[String],
start : Int,
end : Int,
) -> Array[String] {
let out : Array[String] = []
for i in start.. String {
if components.length() == 0 {
""
} else {
components.join(sep)
}
}
///|
pub fn ModulePath::from_path(path : String) -> ModulePath {
let normalized = module_path_drop_extension(module_path_normalize_fs(path))
if normalized == "" {
abort("path is empty")
}
if normalized == "/" {
return ModulePath::new_root()
}
let absolute = normalized.has_prefix("/")
let parts = module_path_split_non_empty(normalized, "/")
if absolute {
return ModulePath::new(Absolute, parts)
}
if parts.length() == 0 {
abort("path must contain at least one named component")
}
match parts[0] {
"." =>
ModulePath::new(Relative(0), module_path_slice(parts, 1, parts.length()))
".." => {
let mut levels = 1
while levels < parts.length() && parts[levels] == ".." {
levels = levels + 1
}
ModulePath::new(
Relative(levels),
module_path_slice(parts, levels, parts.length()),
)
}
package_name =>
ModulePath::new(
Package(package_name),
module_path_slice(parts, 1, parts.length()),
)
}
}
///|
pub fn ModulePath::push(self : ModulePath, item : String) -> Unit {
self.components.push(item)
}
///|
pub fn ModulePath::first(self : ModulePath) -> String? {
self.components.get(0)
}
///|
pub fn ModulePath::last(self : ModulePath) -> String? {
if self.components.length() == 0 {
None
} else {
Some(self.components[self.components.length() - 1])
}
}
///|
pub fn ModulePath::join(
self : ModulePath,
suffix : Array[String],
) -> ModulePath {
let components = self.components.copy()
for item in suffix {
components.push(item)
}
{ origin: self.origin, components }
}
///|
pub fn ModulePath::join_path(
self : ModulePath,
suffix : ModulePath,
) -> ModulePath {
match suffix.origin {
Absolute =>
match self.origin {
Absolute => suffix
Relative(_) => suffix
Package(_) => { origin: self.origin, components: suffix.components }
}
Relative(levels) => {
let to_keep = if self.components.length() > levels {
self.components.length() - levels
} else {
0
}
let components = module_path_slice(self.components, 0, to_keep)
for item in suffix.components {
components.push(item)
}
let origin = match self.origin {
Absolute => Absolute
Package(_) => self.origin
Relative(self_levels) => {
let extra_levels = if levels > self.components.length() {
levels - self.components.length()
} else {
0
}
Relative(self_levels + extra_levels)
}
}
{ origin, components }
}
Package(suffix_pkg) =>
match self.origin {
Absolute => suffix
Relative(_) => suffix
Package(self_pkg) =>
{
origin: Package("\{self_pkg}/\{suffix_pkg}"),
components: suffix.components,
}
}
}
}
///|
pub fn ModulePath::starts_with(self : ModulePath, prefix : ModulePath) -> Bool {
if self.origin != prefix.origin ||
self.components.length() < prefix.components.length() {
return false
}
for i in 0.. Bool {
match self.origin {
Absolute => self.components.length() == 0
_ => false
}
}
///|
pub fn ModulePath::is_package(self : ModulePath) -> Bool {
self.origin is Package(_)
}
///|
pub fn ModulePath::is_relative(self : ModulePath) -> Bool {
self.origin is Relative(_)
}
///|
pub fn ModulePath::to_path_string(self : ModulePath) -> String {
let prefix = match self.origin {
Absolute => "/"
Relative(0) => "."
Relative(levels) => {
let pieces : Array[String] = []
for _ in 0.. name
}
if self.components.length() == 0 {
prefix
} else {
let tail = module_path_join_components(self.components, "/")
if prefix == "/" {
"/\{tail}"
} else {
"\{prefix}/\{tail}"
}
}
}
///|
pub fn ModulePath::to_string(self : ModulePath) -> String {
let prefix = match self.origin {
Absolute => "package"
Relative(0) => "self"
Relative(levels) => {
let pieces : Array[String] = []
for _ in 0.. name
}
if self.components.length() == 0 {
prefix
} else {
let tail = module_path_join_components(self.components, "::")
"\{prefix}::\{tail}"
}
}
///|
pub fn parse_module_path(
text : String,
) -> ModulePath raise ModulePathParseError {
let parsed = @module_path_parse.parse_module_path_source(text)
let path = match parsed {
Parsed(path) => path
Failed(diagnostic) =>
match diagnostic.problem {
Empty => raise Empty
MisplacedPackage => raise MisplacedPackage
MisplacedSelf => raise MisplacedSelf
MisplacedSuper => raise MisplacedSuper
}
}
let origin = match path.origin {
Absolute => Absolute
Relative(levels) => Relative(levels)
Package(name) => Package(name)
}
ModulePath::new(origin, path.components)
}