#[rust_sitter::grammar("lingua")]
pub mod grammar {
#[rust_sitter::language]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Program {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Function(Function),
Expression(Expression),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
#[rust_sitter::leaf(text = "fn")]
_fn: (),
pub name: Identifier,
#[rust_sitter::leaf(text = "(")]
_open_parameters: (),
#[rust_sitter::leaf(text = ")")]
_close_parameters: (),
pub body: Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
#[rust_sitter::leaf(text = "{")]
_open: (),
pub statements: Vec<Statement>,
#[rust_sitter::leaf(text = "}")]
_close: (),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expression {
Number(#[rust_sitter::leaf(pattern = r"[0-9]+", transform = ToOwned::to_owned)] String),
String(#[rust_sitter::leaf(pattern = r#""[^"]*""#, transform = ToOwned::to_owned)] String),
Boolean(Boolean),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Boolean {
#[rust_sitter::leaf(text = "true")]
True,
#[rust_sitter::leaf(text = "false")]
False,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Identifier(
#[rust_sitter::leaf(
pattern = r"[a-zA-Z_][a-zA-Z0-9_]*",
transform = ToOwned::to_owned
)]
pub String,
);
#[rust_sitter::extra]
#[allow(dead_code)]
struct Whitespace {
#[rust_sitter::leaf(pattern = r"\s")]
_whitespace: (),
}
}
pub use grammar::{Block, Boolean, Expression, Function, Identifier, Program, Statement};
pub fn parse(source: &str) -> Result<Program, Vec<rust_sitter::errors::ParseError>> {
grammar::parse(source)
}
pub fn format(source: &str) -> Result<String, Vec<rust_sitter::errors::ParseError>> {
parse(source).map(|program| format_program(&program))
}
pub fn format_program(program: &Program) -> String {
let mut output = String::new();
write_statements(&mut output, &program.statements, 0);
output
}
fn write_statements(output: &mut String, statements: &[Statement], indent: usize) {
for (index, statement) in statements.iter().enumerate() {
if index > 0 {
output.push('\n');
}
write_statement(output, statement, indent);
}
}
fn write_statement(output: &mut String, statement: &Statement, indent: usize) {
output.push_str(&" ".repeat(indent));
match statement {
Statement::Function(function) => write_function(output, function, indent),
Statement::Expression(expression) => write_expression(output, expression),
}
}
fn write_function(output: &mut String, function: &Function, indent: usize) {
output.push_str("fn ");
output.push_str(&function.name.0);
output.push_str("() {\n");
write_statements(output, &function.body.statements, indent + 4);
output.push('\n');
output.push_str(&" ".repeat(indent));
output.push('}');
}
fn write_expression(output: &mut String, expression: &Expression) {
match expression {
Expression::Number(number) => output.push_str(number),
Expression::String(string) => output.push_str(string),
Expression::Boolean(Boolean::True) => output.push_str("true"),
Expression::Boolean(Boolean::False) => output.push_str("false"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_primitives() {
assert_eq!(
parse("true\n6174\n\"hello world\"\n").unwrap(),
Program {
statements: vec![
Statement::Expression(Expression::Boolean(Boolean::True)),
Statement::Expression(Expression::Number("6174".to_owned())),
Statement::Expression(Expression::String("\"hello world\"".to_owned())),
],
}
);
}
#[test]
fn parses_functions() {
let program = parse("fn test_function() {\n true\n}\n").unwrap();
let Statement::Function(function) = &program.statements[0] else {
panic!("expected a function");
};
assert_eq!(function.name.0, "test_function");
assert_eq!(
function.body.statements,
vec![Statement::Expression(Expression::Boolean(Boolean::True))]
);
}
#[test]
fn formats_functions() {
assert_eq!(
format("fn test( ) {\ntrue\n}").unwrap(),
"fn test() {\n true\n}"
);
}
#[test]
fn rejects_syntax_errors() {
assert!(parse("fn broken(").is_err());
}
#[test]
fn parses_example_files() {
for entry in std::fs::read_dir("test/examples").unwrap() {
let path = entry.unwrap().path();
if path.extension().and_then(|extension| extension.to_str()) != Some("lingua") {
continue;
}
let source = std::fs::read_to_string(&path).unwrap();
parse(&source).unwrap_or_else(|err| {
panic!("failed to parse {}: {err:?}", path.display());
});
}
}
}