This commit is contained in:
hkau 2024-03-11 17:46:30 -04:00
commit ebb079b88f
6 changed files with 409 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
Cargo.lock
# env
.env

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "coesite"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
quick-xml = "0.31.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_derive = "1.0.197"
toml = "0.8.11"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 hkau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# Coesite
## About
Coesite is a simple **markup** language based on XML which can compile into *anything* (given a definition string). Coesite is simply enough to be written manually, or to be a compiler target itself. Coesite must be given a language definition which will control what Coesite primitives compile into.
## Example
The following example should be able to successfully compile into JavaScript:
```xml
<!DOCTYPE coesite>
<coesite>
<variable name="number">
<p type="number">5</p>
</variable>
<function name="add" arguments="num1, num2">
<return>
<!-- raw data -->
<r>num1 + num2</r>
</return>
</function>
<variable name="res">
<call name="add" input="number, 5" />
</variable>
<call name="console.log" input="res == 10" />
</coesite>
```
```js
let number = 5;
function add(num1, num2) {
return num1 + num2;
}
let res = add(number, 5);
console.log(res == 10);
```

286
src/lib.rs Normal file
View File

@ -0,0 +1,286 @@
#![doc = include_str!("../README.md")]
#![doc(issue_tracker_base_url = "https://code.stellular.org/hkau/coesite/issues/")]
//! # Coesite
//!
//! Coesite language controller
use quick_xml::events::Event;
use quick_xml::reader::Reader;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct ReadResult {
pub success: bool,
pub message: String,
}
/// Language file definition type
#[derive(Serialize, Deserialize)]
pub struct Language {
pub semi: bool, // if we should use semicolons at the end of values and raw data
pub allow_raw: bool, // if we should allow <r> (raw) elements
// variables
pub variable_keyword: String,
pub constant_keyword: String,
// functions
pub function_template: String,
pub function_close: String,
pub call_template: String,
// primitive types
pub string_char: String,
pub bool_true: String,
pub bool_false: String,
}
/// Serialize a [`Language`]
pub fn serialize_lang(lang: Language) -> String {
return toml::to_string_pretty(&lang).unwrap();
}
// convert &[u8] to &str
pub fn u8_str(input: &[u8]) -> &str {
let res = std::str::from_utf8(input);
if res.is_err() {
return "";
} else {
return res.unwrap();
}
}
/// Run a string given a [`Language`]
pub fn from_string(lang: Language, input: String) -> ReadResult {
// reader
let mut reader = Reader::from_str(&input);
reader.trim_text(true);
// state
let mut output: String = String::new();
let mut root_element_seen: bool = false;
let mut buf: Vec<u8> = Vec::new();
let mut current_type: String = String::new();
let mut new_line_on_next: bool = false; // if we should end the line on the next text element
// main loop
loop {
match reader.read_event_into(&mut buf) {
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
Ok(Event::Eof) => {
// exit at end
break ReadResult {
success: true,
message: output,
};
}
Ok(Event::Comment(_)) => continue, // skip comments
Ok(Event::DocType(e)) => {
// make sure doctype is coesite
let text_cow = e.into_inner();
let inner_text = u8_str(text_cow.as_ref());
// ...
if inner_text != "coesite" {
return ReadResult {
success: false,
message: String::from("doctype should be \"coesite\""),
};
}
}
// start
Ok(Event::Start(e)) => {
let name = &e.name();
let name = name.as_ref();
// check for root element
if name == b"coesite" && root_element_seen == false {
root_element_seen = true;
continue;
}
if (name != b"coesite") && root_element_seen == false {
return ReadResult {
success: false,
message: String::from(
"document should begin with root element (<coesite>)",
),
};
}
// primitive
if name == b"p" {
let res = e.try_get_attribute("type");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap().unwrap();
let value = u8_str(attribute.value.as_ref());
current_type = value.to_string();
continue;
}
// raw
if name == b"r" {
current_type = String::from("raw");
continue;
}
// <variable>
if name == b"variable" {
let res = e.try_get_attribute("name");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap().unwrap();
let name = u8_str(attribute.value.as_ref());
// ...
output += &format!("{} {name} = ", lang.variable_keyword);
new_line_on_next = true; // next SHOULD be the value
}
// <function>
if name == b"function" {
let res = e.try_get_attribute("name");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap().unwrap();
let name = u8_str(attribute.value.as_ref());
// ...
let res = e.try_get_attribute("arguments");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap().unwrap();
let arguments = u8_str(attribute.value.as_ref());
// ...
output += &lang
.function_template
.replace("%n", name)
.replace("%a", arguments);
}
// <return>
if name == b"return" {
output += "return ";
new_line_on_next = true; // next SHOULD be the value
}
}
Ok(Event::End(e)) => {
let name = &e.name();
let name = name.as_ref();
// </function>
if name == b"function" {
output += &lang.function_close;
}
}
Ok(Event::Empty(e)) => {
let name = &e.name();
let name = name.as_ref();
// <call />
if name == b"call" {
let res = e.try_get_attribute("name");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap().unwrap();
let name = u8_str(attribute.value.as_ref());
// ...
let res = e.try_get_attribute("input");
if res.is_err() {
return ReadResult {
success: false,
message: res.err().unwrap().to_string(),
};
}
let attribute = res.unwrap();
if attribute.is_some() {
let attribute = attribute.unwrap();
let input = u8_str(attribute.value.as_ref());
// ...
output += &lang.call_template.replace("%n", name).replace("%p", input);
} else {
output += &lang.call_template.replace("%n", name).replace("%p", "");
}
}
}
Ok(Event::Text(e)) => {
let inner = e.unescape().unwrap().into_owned();
// figure out what to do based on the current type
match current_type.as_str() {
"string" => {
let _char = &lang.string_char;
output += &format!("{_char}{inner}{_char}");
}
"number" => {
output += &format!("{inner}");
}
"raw" => {
output += &format!("{inner}");
}
// drop, do nothing
_ => (),
}
// ...
if new_line_on_next {
output += ";\n";
}
// drop text
continue;
}
// catch-all
_ => (),
}
}
}

44
src/main.rs Normal file
View File

@ -0,0 +1,44 @@
use coesite;
fn main() {
let js_lang = coesite::Language {
semi: true,
allow_raw: true,
variable_keyword: String::from("let"),
constant_keyword: String::from("const"),
function_template: String::from("function %n(%a) {{T}}\n".replace("{T}}", "")),
function_close: String::from("}\n"),
call_template: String::from("%n(%p);\n"),
string_char: String::from("\""),
bool_true: String::from("true"),
bool_false: String::from("false"),
};
let res = coesite::from_string(
js_lang,
String::from(
"<!DOCTYPE coesite>
<coesite>
<variable name=\"number\">
<p type=\"number\">5</p>
</variable>
<function name=\"add\" arguments=\"num1, num2\">
<return>
<!-- raw data -->
<r>num1 + num2</r>
</return>
</function>
<variable name=\"res\">
<call name=\"add\" input=\"number, 5\" />
</variable>
<call name=\"console.log\" input=\"res == 10\" />
</coesite>",
),
);
println!("{}", &res.message);
}