mod function_calls;
use core::fmt;
use std::{
ffi,
io::{self, Read},
ptr::null_mut,
string::ToString,
};
use crate::function_calls::FunctionCallBuilder;
#[must_use]
pub fn version() -> String {
unsafe {
let version = umka_sys::umkaGetVersion();
ffi::CStr::from_ptr(version).to_string_lossy().to_string()
}
}
pub struct Umka(*mut umka_sys::tagUmka);
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to read source: {0}")]
IO(#[from] io::Error),
#[error("source string contains a null byte")]
NullByte(#[from] ffi::NulError),
#[error("failed to allocate memory for umka interpreter")]
AllocError,
#[error("failed to initialise umka")]
InitError,
#[error("{}", .0.as_ref().map_or_else(
|| "generic compilation error".to_string(),
ToString::to_string
))]
ModuleError(Option<ErrorDetails>),
#[error("{}", .0.as_ref().map_or_else(
|| "generic compilation error".to_string(),
ToString::to_string
))]
CompileError(Option<ErrorDetails>),
#[error("{}", .0.as_ref().map_or_else(
|| "generic runtime error".to_string(),
ToString::to_string
))]
RuntimeError(Option<ErrorDetails>),
}
#[derive(Debug)]
pub struct ErrorDetails {
pub pos: i32,
pub line: i32,
pub code: i32,
pub fn_name: String,
pub message: String,
pub file_name: String,
}
impl Umka {
pub fn new<R: Read>(mut reader: R, file_name: &str) -> Result<Self, Error> {
let mut source = String::new();
reader.read_to_string(&mut source).map_err(Error::IO)?;
let umka = init(file_name, &source)?;
Ok(Self(umka))
}
pub fn add_module<R: Read>(&self, mut reader: R, file_name: &str) -> Result<(), Error> {
let mut source = String::new();
reader.read_to_string(&mut source).map_err(Error::IO)?;
unsafe {
let file_name = ffi::CString::new(file_name).map_err(Error::NullByte)?;
let source = ffi::CString::new(source).map_err(Error::NullByte)?;
let ok = umka_sys::umkaAddModule(self.0, file_name.as_ptr(), source.as_ptr());
if ok {
Ok(())
} else {
Err(Error::ModuleError(self.error_details()))
}
}
}
pub fn compile(&self) -> Result<(), Error> {
if unsafe { umka_sys::umkaCompile(self.0) } {
Ok(())
} else {
Err(Error::CompileError(self.error_details()))
}
}
#[must_use]
pub fn run(&self) -> i32 {
unsafe { umka_sys::umkaRun(self.0) }
}
}
impl Umka {
pub fn function(&self, name: &str) -> Result<FunctionCallBuilder<'_>, Error> {
let name = ffi::CString::new(name).map_err(Error::NullByte)?;
Ok(FunctionCallBuilder::new(self, name))
}
}
impl Umka {
fn error_details(&self) -> Option<ErrorDetails> {
unsafe {
let error = *umka_sys::umkaGetError(self.0);
if error.msg.is_null() {
None
} else {
let fn_name = ffi::CStr::from_ptr(error.fileName)
.to_string_lossy()
.to_string();
let message = ffi::CStr::from_ptr(error.msg).to_string_lossy().to_string();
let file_name = ffi::CStr::from_ptr(error.fileName)
.to_string_lossy()
.to_string();
Some(ErrorDetails {
pos: error.pos,
line: error.line,
code: error.code,
fn_name,
message,
file_name,
})
}
}
}
}
impl Drop for Umka {
fn drop(&mut self) {
unsafe {
umka_sys::umkaFree(self.0);
}
}
}
fn init(file_name: &str, source_string: &str) -> Result<*mut umka_sys::tagUmka, Error> {
unsafe {
let umka = umka_sys::umkaAlloc();
if umka.is_null() {
return Err(Error::AllocError);
}
let file_name = ffi::CString::new(file_name).map_err(Error::NullByte)?;
let source_string = ffi::CString::new(source_string).map_err(Error::NullByte)?;
let ok = umka_sys::umkaInit(
umka,
file_name.as_ptr(),
source_string.as_ptr(),
4096,
null_mut(),
0,
null_mut(),
true,
true,
None,
);
if !ok {
return Err(Error::InitError);
}
Ok(umka)
}
}
impl fmt::Display for ErrorDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Error {} ({}, {}): {}",
self.file_name, self.line, self.pos, self.message
)?;
Ok(())
}
}