ruka_syntax/
lib.rs

1//! Syntax crate for lexing, parsing, CST helpers, and browser graph rendering.
2
3/// Frontend AST re-exports used by the parser and downstream stages.
4pub mod ast {
5    pub use ruka_frontend::*;
6}
7
8/// Parser and syntax helpers re-exported under the historic `syntax` path.
9pub mod syntax {
10    pub use crate::SourceLocation;
11    pub use crate::SyntaxError;
12    pub use crate::ast;
13    pub use crate::browser_graph;
14    pub use crate::cst;
15    pub use crate::lexer;
16    pub use crate::parse_program;
17    pub use crate::parse_program_cst;
18    pub use crate::parse_program_with_file_id;
19    pub use crate::token;
20}
21
22/// Browser-friendly AST graph rendering for interactive viewers.
23pub mod browser_graph;
24/// Concrete syntax tree helpers built from parsed AST values.
25pub mod cst;
26/// Token stream lexer for source text.
27pub mod lexer;
28/// Token and lex-error definitions used by the parser.
29pub mod token;
30
31use lalrpop_util::ParseError;
32use thiserror::Error;
33
34use crate::ast::{ExternFunctionDecl, Program, TypeExpr};
35use crate::cst::SyntaxNode;
36use crate::lexer::Lexer;
37use crate::token::{LexError, Token};
38
39/// Generated LALRPOP parser implementation.
40mod grammar {
41    include!(concat!(env!("OUT_DIR"), "/grammar.rs"));
42}
43
44pub use ruka_frontend::{FileId, SourceLocation};
45
46/// Parser error reported while lexing or parsing source text.
47#[derive(Debug, Error)]
48pub enum SyntaxError {
49    /// Lexing failed before parsing could continue.
50    #[error("{error}")]
51    Lex {
52        /// Underlying lexer error.
53        error: LexError,
54    },
55    /// Parser encountered an unexpected token.
56    #[error(
57        "unexpected token {found:?} at {line}:{column} (byte {byte})",
58        line = location.line,
59        column = location.column,
60        byte = location.byte
61    )]
62    UnexpectedToken {
63        /// Source location of the unexpected token.
64        location: SourceLocation,
65        /// Unexpected token value.
66        found: Token,
67    },
68    /// Parser reached end of input unexpectedly.
69    #[error(
70        "unexpected end of input at {line}:{column} (byte {byte})",
71        line = location.line,
72        column = location.column,
73        byte = location.byte
74    )]
75    UnexpectedEof {
76        /// Source location where input ended.
77        location: SourceLocation,
78    },
79    /// Parser reported an invalid token location.
80    #[error(
81        "invalid token at {line}:{column} (byte {byte})",
82        line = location.line,
83        column = location.column,
84        byte = location.byte
85    )]
86    InvalidToken {
87        /// Source location of the invalid token.
88        location: SourceLocation,
89    },
90    /// Extern signature contained a generic type application.
91    #[error(
92        "generic type applications are not allowed in extern signatures at {line}:{column} (byte {byte})",
93        line = location.line,
94        column = location.column,
95        byte = location.byte
96    )]
97    InvalidExternTypeApplication {
98        /// Source location of the invalid extern type expression.
99        location: SourceLocation,
100    },
101}
102
103/// Parse a source string into a frontend program AST.
104pub fn parse_program(input: &str) -> Result<Program, SyntaxError> {
105    parse_program_with_file_id(FileId::from_u32(0), input)
106}
107
108/// Parse a source string into a frontend program AST with a concrete file id.
109pub fn parse_program_with_file_id(file_id: FileId, input: &str) -> Result<Program, SyntaxError> {
110    let mut tokens = Vec::new();
111    for item in Lexer::new(input) {
112        tokens.push(item.map_err(|error| SyntaxError::Lex { error })?);
113    }
114    let tokens: Vec<_> = tokens
115        .into_iter()
116        .filter(|(_, token, _)| !matches!(token, Token::Comment))
117        .collect();
118    let program = grammar::ProgramParser::new()
119        .parse(file_id, tokens.into_iter())
120        .map_err(map_parse_error)?;
121    validate_extern_signatures(&program)?;
122    Ok(program)
123}
124
125/// Parse a source string and build a CST view from the resulting AST.
126pub fn parse_program_cst(input: &str) -> Result<SyntaxNode, SyntaxError> {
127    let ast = parse_program(input)?;
128    Ok(cst::build_program_cst(&ast))
129}
130
131fn map_parse_error(error: ParseError<SourceLocation, Token, LexError>) -> SyntaxError {
132    match error {
133        ParseError::InvalidToken { location } => SyntaxError::InvalidToken { location },
134        ParseError::UnrecognizedEof { location, .. } => SyntaxError::UnexpectedEof { location },
135        ParseError::UnrecognizedToken { token, .. } => {
136            let (start, found, _) = token;
137            SyntaxError::UnexpectedToken {
138                location: start,
139                found,
140            }
141        }
142        ParseError::ExtraToken { token } => {
143            let (start, found, _) = token;
144            SyntaxError::UnexpectedToken {
145                location: start,
146                found,
147            }
148        }
149        ParseError::User { error } => SyntaxError::Lex { error },
150    }
151}
152
153fn validate_extern_signatures(program: &Program) -> Result<(), SyntaxError> {
154    for module in &program.extern_modules {
155        for function in &module.functions {
156            validate_extern_function(function)?;
157        }
158    }
159    Ok(())
160}
161
162fn validate_extern_function(function: &ExternFunctionDecl) -> Result<(), SyntaxError> {
163    for param in &function.params {
164        reject_extern_type_apply(&param.ty.ty, function)?;
165    }
166    reject_extern_type_apply(&function.return_type.ty, function)
167}
168
169fn reject_extern_type_apply(
170    ty: &TypeExpr,
171    function: &ExternFunctionDecl,
172) -> Result<(), SyntaxError> {
173    match ty {
174        TypeExpr::TypeKind | TypeExpr::Named(_) => Ok(()),
175        TypeExpr::Pointer { item } | TypeExpr::Slice { item } => {
176            reject_extern_type_apply(item, function)
177        }
178        TypeExpr::Array { item, .. } => reject_extern_type_apply(item, function),
179        TypeExpr::Tuple(items) => {
180            for item in items {
181                reject_extern_type_apply(item, function)?;
182            }
183            Ok(())
184        }
185        TypeExpr::Apply { .. } => Err(SyntaxError::InvalidExternTypeApplication {
186            location: function.span.start,
187        }),
188        TypeExpr::Struct { fields } | TypeExpr::Union { fields } => {
189            for field in fields {
190                reject_extern_type_apply(&field.ty, function)?;
191            }
192            Ok(())
193        }
194        TypeExpr::Enum { variants } => {
195            for variant in variants {
196                for payload in &variant.payload {
197                    reject_extern_type_apply(payload, function)?;
198                }
199            }
200            Ok(())
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests;