Skip to main content

lingua/
lib.rs

1#[rust_sitter::grammar("lingua")]
2pub mod grammar {
3    #[rust_sitter::language]
4    #[derive(Debug, Clone, PartialEq, Eq)]
5    pub struct Program {
6        pub statements: Vec<Statement>,
7    }
8
9    #[derive(Debug, Clone, PartialEq, Eq)]
10    pub enum Statement {
11        Function(Function),
12        Expression(Expression),
13    }
14
15    #[derive(Debug, Clone, PartialEq, Eq)]
16    pub struct Function {
17        #[rust_sitter::leaf(text = "fn")]
18        _fn: (),
19        pub name: Identifier,
20        #[rust_sitter::leaf(text = "(")]
21        _open_parameters: (),
22        #[rust_sitter::leaf(text = ")")]
23        _close_parameters: (),
24        pub body: Block,
25    }
26
27    #[derive(Debug, Clone, PartialEq, Eq)]
28    pub struct Block {
29        #[rust_sitter::leaf(text = "{")]
30        _open: (),
31        pub statements: Vec<Statement>,
32        #[rust_sitter::leaf(text = "}")]
33        _close: (),
34    }
35
36    #[derive(Debug, Clone, PartialEq, Eq)]
37    pub enum Expression {
38        Number(#[rust_sitter::leaf(pattern = r"[0-9]+", transform = ToOwned::to_owned)] String),
39        String(#[rust_sitter::leaf(pattern = r#""[^"]*""#, transform = ToOwned::to_owned)] String),
40        Boolean(Boolean),
41    }
42
43    #[derive(Debug, Clone, PartialEq, Eq)]
44    pub enum Boolean {
45        #[rust_sitter::leaf(text = "true")]
46        True,
47        #[rust_sitter::leaf(text = "false")]
48        False,
49    }
50
51    #[derive(Debug, Clone, PartialEq, Eq)]
52    pub struct Identifier(
53        #[rust_sitter::leaf(
54            pattern = r"[a-zA-Z_][a-zA-Z0-9_]*",
55            transform = ToOwned::to_owned
56        )]
57        pub String,
58    );
59
60    #[rust_sitter::extra]
61    #[allow(dead_code)]
62    struct Whitespace {
63        #[rust_sitter::leaf(pattern = r"\s")]
64        _whitespace: (),
65    }
66}
67
68pub use grammar::{Block, Boolean, Expression, Function, Identifier, Program, Statement};
69
70pub fn parse(source: &str) -> Result<Program, Vec<rust_sitter::errors::ParseError>> {
71    grammar::parse(source)
72}
73
74pub fn format(source: &str) -> Result<String, Vec<rust_sitter::errors::ParseError>> {
75    parse(source).map(|program| format_program(&program))
76}
77
78pub fn format_program(program: &Program) -> String {
79    let mut output = String::new();
80    write_statements(&mut output, &program.statements, 0);
81    output
82}
83
84fn write_statements(output: &mut String, statements: &[Statement], indent: usize) {
85    for (index, statement) in statements.iter().enumerate() {
86        if index > 0 {
87            output.push('\n');
88        }
89
90        write_statement(output, statement, indent);
91    }
92}
93
94fn write_statement(output: &mut String, statement: &Statement, indent: usize) {
95    output.push_str(&" ".repeat(indent));
96
97    match statement {
98        Statement::Function(function) => write_function(output, function, indent),
99        Statement::Expression(expression) => write_expression(output, expression),
100    }
101}
102
103fn write_function(output: &mut String, function: &Function, indent: usize) {
104    output.push_str("fn ");
105    output.push_str(&function.name.0);
106    output.push_str("() {\n");
107    write_statements(output, &function.body.statements, indent + 4);
108    output.push('\n');
109    output.push_str(&" ".repeat(indent));
110    output.push('}');
111}
112
113fn write_expression(output: &mut String, expression: &Expression) {
114    match expression {
115        Expression::Number(number) => output.push_str(number),
116        Expression::String(string) => output.push_str(string),
117        Expression::Boolean(Boolean::True) => output.push_str("true"),
118        Expression::Boolean(Boolean::False) => output.push_str("false"),
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn parses_primitives() {
128        assert_eq!(
129            parse("true\n6174\n\"hello world\"\n").unwrap(),
130            Program {
131                statements: vec![
132                    Statement::Expression(Expression::Boolean(Boolean::True)),
133                    Statement::Expression(Expression::Number("6174".to_owned())),
134                    Statement::Expression(Expression::String("\"hello world\"".to_owned())),
135                ],
136            }
137        );
138    }
139
140    #[test]
141    fn parses_functions() {
142        let program = parse("fn test_function() {\n    true\n}\n").unwrap();
143
144        let Statement::Function(function) = &program.statements[0] else {
145            panic!("expected a function");
146        };
147
148        assert_eq!(function.name.0, "test_function");
149        assert_eq!(
150            function.body.statements,
151            vec![Statement::Expression(Expression::Boolean(Boolean::True))]
152        );
153    }
154
155    #[test]
156    fn formats_functions() {
157        assert_eq!(
158            format("fn test( ) {\ntrue\n}").unwrap(),
159            "fn test() {\n    true\n}"
160        );
161    }
162
163    #[test]
164    fn rejects_syntax_errors() {
165        assert!(parse("fn broken(").is_err());
166    }
167
168    #[test]
169    fn parses_example_files() {
170        for entry in std::fs::read_dir("test/examples").unwrap() {
171            let path = entry.unwrap().path();
172            if path.extension().and_then(|extension| extension.to_str()) != Some("lingua") {
173                continue;
174            }
175
176            let source = std::fs::read_to_string(&path).unwrap();
177            parse(&source).unwrap_or_else(|err| {
178                panic!("failed to parse {}: {err:?}", path.display());
179            });
180        }
181    }
182}