pub(crate) mod cache;
pub mod header;
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
mem::transmute,
ops::{Deref, Range},
sync::Arc,
};
use allocator_api2::vec::Vec;
use bumpalo::Bump;
use tyml_diagnostic::DiagnosticBuilder;
use tyml_formatter::{FormatterToken, FormatterTokenKind, SpaceFormat};
use tyml_generator::{
lexer::{GeneratorLexer, GeneratorTokenKind, TokenizerRegistry},
style::{
error::GeneratedParseError, language::LanguageStyle, ASTTokenKind, Parser, ParserGenerator,
AST,
},
};
use tyml_parser::{ast::Defines, error::ParseError, lexer::Lexer, parser::parse_defines};
use tyml_source::SourceCode;
use tyml_type::{
error::TypeError,
resolver::resolve_type,
types::{InterfaceInfo, NamedTypeMap, TypeTree},
};
use tyml_validate::{error::TymlValueValidateError, validate::ValueTypeChecker};
pub extern crate tyml_diagnostic;
pub extern crate tyml_formatter;
pub extern crate tyml_generator;
pub extern crate tyml_parser;
pub extern crate tyml_source;
pub extern crate tyml_type;
pub extern crate tyml_validate;
#[derive(Debug, Clone)]
pub struct TymlContext<State = Initial> {
state: State,
pub tyml_source: SourceCode,
}
impl TymlContext {
pub fn new(tyml_source: SourceCode) -> Self {
TymlContext {
state: Initial {},
tyml_source,
}
}
}
impl<State> TymlContext<State> {
pub fn parse(&self) -> TymlContext<Parsed>
where
State: IInitial,
{
TymlContext {
state: Parsed {
tyml: Tyml::parse(self.tyml_source.code.clone()),
},
tyml_source: self.tyml_source.clone(),
}
}
pub fn tyml(&self) -> &Tyml
where
State: IParsed,
{
self.state.tyml()
}
pub fn print_tyml_error(&self, lang: &str)
where
State: IParsed,
{
for error in self.tyml().parse_errors().iter() {
error.build(self.tyml().named_type_map()).print(
lang,
&self.tyml_source,
&self.tyml_source,
);
}
for error in self.tyml().type_errors().iter() {
error.build(self.tyml().named_type_map()).print(
lang,
&self.tyml_source,
&self.tyml_source,
);
}
}
pub fn has_tyml_error(&self) -> bool
where
State: IParsed,
{
self.tyml().has_error()
}
pub fn ml_parse_and_validate<'code>(
&self,
ml_language_style: &LanguageStyle,
ml_source_code: &'code SourceCode,
tokens: Option<&mut BTreeMap<usize, (ASTTokenKind, Range<usize>)>>,
formatter_tokens: Option<&mut std::vec::Vec<FormatterToken<'code>>>,
) -> TymlContext<Validated>
where
State: IParsed,
{
let mut registry = TokenizerRegistry::new();
let ml_parser = ml_language_style.generate(&mut registry);
registry.freeze();
let mut errors = Vec::new();
let allocator = Bump::new();
let mut lexer = GeneratorLexer::new(&ml_source_code.code, ®istry, &allocator);
let ast = ml_parser.parse(&mut lexer, &mut errors).unwrap();
if let Some(formatter_tokens) = formatter_tokens {
let mut formatter_token_kind_map = HashMap::new();
let mut formatter_token_space_info = Vec::new();
ml_parser.map_formatter_token_kind(&mut formatter_token_kind_map);
ast.take_formatter_token_space(&mut formatter_token_space_info);
let mut formatter_token_space_info_map = HashMap::new();
for token_info in formatter_token_space_info {
if token_info.left_space != SpaceFormat::None {
formatter_token_space_info_map
.insert(token_info.span.start, token_info.left_space);
}
if token_info.right_space != SpaceFormat::None {
formatter_token_space_info_map
.insert(token_info.span.end, token_info.right_space);
}
}
let lexer = GeneratorLexer::new(&ml_source_code.code, ®istry, &allocator);
for token in lexer {
let kind = if token.kinds.contains(&GeneratorTokenKind::LineFeed) {
FormatterTokenKind::LineFeed
} else {
token
.kinds
.iter()
.find_map(|kind| formatter_token_kind_map.get(kind))
.cloned()
.unwrap_or(FormatterTokenKind::Normal)
};
let left_space = formatter_token_space_info_map
.get(&token.span.start)
.cloned()
.unwrap_or(SpaceFormat::None);
let right_space = formatter_token_space_info_map
.get(&token.span.end)
.cloned()
.unwrap_or(SpaceFormat::None);
formatter_tokens.push(FormatterToken {
text: token.text.into(),
kind,
left_space,
right_space,
});
}
}
if let Some(tokens) = tokens {
ast.take_token(tokens);
for comment_span in lexer.comment_spans.iter() {
tokens.insert(
comment_span.start,
(ASTTokenKind::Comment, comment_span.clone()),
);
}
}
let mut validator = self.tyml().value_type_checker();
ast.take_value(&mut Vec::new_in(&allocator), &mut validator);
let ml_validate_error = validator.validate().err().unwrap_or_default();
let validator =
ValidatorHolder::new(self.tyml().clone(), ml_source_code.clone(), validator);
TymlContext {
state: Validated {
tyml: self.tyml().clone(),
ml_source_code: ml_source_code.clone(),
validator,
ml_parse_error: Arc::new(errors),
ml_validate_error: Arc::new(ml_validate_error),
},
tyml_source: self.tyml_source.clone(),
}
}
pub fn ml_source_code(&self) -> &SourceCode
where
State: IValidated,
{
self.state.ml_source_code()
}
pub fn validator(&self) -> &ValueTypeChecker<'_, '_, '_, '_, '_>
where
State: IValidated,
{
&self.state.validator()
}
pub fn print_ml_parse_error(&self, lang: &str)
where
State: IValidated,
{
for error in self.ml_parse_error().iter() {
error.build(self.tyml().named_type_map()).print(
lang,
&self.tyml_source,
&self.state.ml_source_code(),
);
}
}
pub fn has_ml_parse_error(&self) -> bool
where
State: IValidated,
{
!self.state.ml_parse_error().is_empty()
}
pub fn ml_parse_error(&self) -> &Arc<Vec<GeneratedParseError>>
where
State: IValidated,
{
self.state.ml_parse_error()
}
pub fn print_ml_validate_error(&self, lang: &str)
where
State: IValidated,
{
for error in self.ml_validate_error().iter() {
error.build(self.tyml().named_type_map()).print(
lang,
&self.tyml_source,
&self.state.ml_source_code(),
);
}
}
pub fn has_ml_validate_error(&self) -> bool
where
State: IValidated,
{
!self.state.ml_validate_error().is_empty()
}
pub fn ml_validate_error(&self) -> &Arc<Vec<TymlValueValidateError>>
where
State: IValidated,
{
self.state.ml_validate_error()
}
}
#[derive(Debug, Clone)]
pub struct Initial {}
#[derive(Debug, Clone)]
pub struct Parsed {
pub tyml: Tyml,
}
#[derive(Debug, Clone)]
pub struct Validated {
pub tyml: Tyml,
pub ml_source_code: SourceCode,
pub validator: ValidatorHolder,
pub ml_parse_error: Arc<Vec<GeneratedParseError>>,
pub ml_validate_error: Arc<Vec<TymlValueValidateError>>,
}
pub trait IInitial {}
impl IInitial for Initial {}
pub trait IParsed {
fn tyml(&self) -> &Tyml;
}
impl IParsed for Parsed {
fn tyml(&self) -> &Tyml {
&self.tyml
}
}
impl IParsed for Validated {
fn tyml(&self) -> &Tyml {
&self.tyml
}
}
pub trait IValidated: IParsed {
fn ml_source_code(&self) -> &SourceCode;
fn validator(&self) -> &ValueTypeChecker<'_, '_, '_, '_, '_>;
fn ml_parse_error(&self) -> &Arc<Vec<GeneratedParseError>>;
fn ml_validate_error(&self) -> &Arc<Vec<TymlValueValidateError>>;
}
impl IValidated for Validated {
fn ml_source_code(&self) -> &SourceCode {
&self.ml_source_code
}
fn validator(&self) -> &ValueTypeChecker<'_, '_, '_, '_, '_> {
self.validator.validator()
}
fn ml_parse_error(&self) -> &Arc<Vec<GeneratedParseError>> {
&self.ml_parse_error
}
fn ml_validate_error(&self) -> &Arc<Vec<TymlValueValidateError>> {
&self.ml_validate_error
}
}
#[derive(Debug, Clone)]
#[allow(unused)]
pub struct ValidatorHolder {
tyml: Tyml,
ml_source_code: SourceCode,
validator: Arc<ValueTypeChecker<'static, 'static, 'static, 'static, 'static>>,
}
impl ValidatorHolder {
pub fn new(tyml: Tyml, ml_source_code: SourceCode, validator: ValueTypeChecker) -> Self {
Self {
tyml,
ml_source_code,
validator: unsafe { transmute(Arc::new(validator)) },
}
}
pub fn validator<'this>(
&'this self,
) -> &'this ValueTypeChecker<'this, 'this, 'this, 'this, 'this> {
unsafe { transmute(self.validator.as_ref()) }
}
}
#[derive(Debug, Clone)]
pub struct Tyml {
inner: Arc<TymlInner>,
}
impl Tyml {
pub fn source_code(&self) -> &Arc<String> {
&self.inner.source_code
}
pub fn comment_ranges(&self) -> &Vec<Range<usize>> {
&self.inner.comments
}
pub fn ast<'this>(&'this self) -> &'this Defines<'this, 'this> {
self.inner.ast
}
pub fn type_tree<'this>(&'this self) -> &'this TypeTree<'this> {
&self.inner.type_tree
}
pub fn named_type_map<'this>(&'this self) -> &'this NamedTypeMap<'this> {
&self.inner.named_type_map
}
pub fn parse_errors<'this>(&'this self) -> &'this [ParseError<'this, 'this>] {
self.inner.parse_errors
}
pub fn interfaces<'this>(&'this self) -> &'this [InterfaceInfo<'this, 'this>] {
self.inner.interfaces
}
pub fn type_errors<'this>(&'this self) -> &'this [TypeError<'this>] {
self.inner.type_errors
}
pub fn has_error(&self) -> bool {
!self.parse_errors().is_empty() || !self.type_errors().is_empty()
}
pub fn value_type_checker<'this, 'section, 'value>(
&'this self,
) -> ValueTypeChecker<'this, 'this, 'this, 'section, 'value> {
ValueTypeChecker::new(self.type_tree(), self.named_type_map())
}
}
impl Tyml {
pub fn parse<T: Into<Arc<String>>>(source_code: T) -> Self {
let source_code = source_code.into();
let allocator = Box::new(Bump::new());
let mut lexer = Lexer::new(&source_code);
let mut parse_errors = Vec::new_in(allocator.deref());
let ast = parse_defines(&mut lexer, &mut parse_errors, allocator.deref());
let comments = lexer.comments.into_iter().collect();
let (type_tree, named_type_map, interfaces, type_errors) =
resolve_type(ast, allocator.deref());
let parse_errors = allocator.alloc(parse_errors).as_slice();
let interfaces = allocator.alloc(interfaces).as_slice();
let type_errors = allocator.alloc(type_errors).as_slice();
let fake_static_ast = unsafe { transmute(ast) };
let fake_static_type_tree = unsafe { transmute(type_tree) };
let fake_static_named_type_map = unsafe { transmute(named_type_map) };
let fake_static_parse_errors = unsafe { transmute(parse_errors) };
let fake_static_interfaces = unsafe { transmute(interfaces) };
let fake_static_type_errors = unsafe { transmute(type_errors) };
let _holder = FreezedAllocatorHolder {
_allocator: allocator,
};
let tyml_inner = TymlInner {
source_code,
comments,
ast: fake_static_ast,
type_tree: fake_static_type_tree,
named_type_map: fake_static_named_type_map,
parse_errors: fake_static_parse_errors,
interfaces: fake_static_interfaces,
type_errors: fake_static_type_errors,
_holder,
};
Self {
inner: Arc::new(tyml_inner),
}
}
}
#[derive(Debug)]
struct TymlInner {
source_code: Arc<String>,
comments: Vec<Range<usize>>,
ast: &'static Defines<'static, 'static>,
type_tree: TypeTree<'static>,
named_type_map: NamedTypeMap<'static>,
parse_errors: &'static [ParseError<'static, 'static>],
interfaces: &'static [InterfaceInfo<'static, 'static>],
type_errors: &'static [TypeError<'static>],
_holder: FreezedAllocatorHolder,
}
#[derive(Debug)]
struct FreezedAllocatorHolder {
_allocator: Box<Bump>,
}
unsafe impl Send for FreezedAllocatorHolder {}
unsafe impl Sync for FreezedAllocatorHolder {}
#[cfg(test)]
mod tests {
use std::{fs::File, io::Read};
use tyml_diagnostic::message::Lang;
use tyml_formatter::GeneralFormatter;
use tyml_generator::registry::STYLE_REGISTRY;
use tyml_source::SourceCode;
use crate::{cache::get_cached_file, TymlContext};
#[test]
fn lib_test() {
let source = r#"
type User {
id: string | int
name: string
}
type Claim {
iss: string
sub: string
iat: int
exp: int
}
/// The API!
interface API {
#[kind = "get"]
function register(id: int = 100, name: string = "test") -> string {
return "* Secret Token *"
}
authed function get_user(@claim: Claim) -> User {
return { id = 100, name = "test" }
}
#[kind = "get"]
function hello() -> string {
return "Hello, world!"
}
}
"#;
let ini_source = r#""#;
let tyml_source = SourceCode::new("test.tyml".to_string(), source.to_string());
let ml_source = SourceCode::new("test.json".to_string(), ini_source.to_string());
let tyml = TymlContext::new(tyml_source).parse();
let language = STYLE_REGISTRY.resolve("json").unwrap();
let mut formatter_tokens = Vec::new();
let tyml =
tyml.ml_parse_and_validate(&language, &ml_source, None, Some(&mut formatter_tokens));
tyml.print_tyml_error(Lang::system());
tyml.print_ml_parse_error(Lang::system());
tyml.print_ml_validate_error(Lang::system());
let mut formatter = GeneralFormatter::new(formatter_tokens.into_iter(), 25);
formatter.format();
println!("{}", formatter.generate_code());
}
#[test]
fn cookie_resolver_propagation_test() {
use crate::Tyml;
let source = r#"
type Token {
access_token: string
}
interface Auth {
cookie function refresh() -> Token
authed cookie function rotate(@claim: Token) -> Token
function login() -> Token
}
"#;
let tyml = Tyml::parse(source.to_string());
assert!(tyml.parse_errors().is_empty(), "{:?}", tyml.parse_errors());
assert!(tyml.type_errors().is_empty(), "{:?}", tyml.type_errors());
let interface = tyml
.interfaces()
.iter()
.find(|i| i.original_name == "Auth")
.expect("Auth interface not found");
let refresh = interface
.functions
.iter()
.find(|f| f.name.value == "refresh")
.unwrap();
assert!(refresh.cookie.is_some(), "refresh must carry cookie span");
assert!(refresh.authed.is_none());
let rotate = interface
.functions
.iter()
.find(|f| f.name.value == "rotate")
.unwrap();
assert!(rotate.cookie.is_some());
assert!(rotate.authed.is_some());
let login = interface
.functions
.iter()
.find(|f| f.name.value == "login")
.unwrap();
assert!(login.cookie.is_none());
assert!(login.authed.is_none());
}
#[test]
fn cache_test() {
let file_path = tokio::runtime::Runtime::new().unwrap().block_on(async {
get_cached_file(
"https://raw.githubusercontent.com/tyml-org/tyml/refs/heads/main/Cargo.toml",
)
.await
.unwrap()
});
let mut file = File::open(file_path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
dbg!(content);
}
}