Skip to main content

rl_lang/parser/statements/
import_statement.rs

1//! Import statement parser (`get`).
2//!
3//! Handles all four import forms in rl-lang:
4//!
5//! ```text
6//! // 1. single file module
7//! get mymodule
8//!
9//! // 2. file module with path
10//! get mymodule::utils
11//!
12//! // 3. stdlib function
13//! get std::math::sin
14//!
15//! // 4. named imports from a module or stdlib
16//! get sin, cos from std::math
17//! get add, sub from mymodule::utils
18//! ```
19//!
20//! The first token after `get` and whether `::` or `from` follows determines
21//! which [`StatementKind`] variant is produced:
22//!
23//! | syntax | kind |
24//! |---|---|
25//! | `get mod` | [`StatementKind::ImportFile`] |
26//! | `get mod::sub` | [`StatementKind::ImportFile`] |
27//! | `get std::ns::fn` | [`StatementKind::Import`] |
28//! | `get fn, fn from std::ns` | [`StatementKind::Import`] |
29//! | `get fn, fn from mod::sub` | [`StatementKind::ImportFileNamed`] |
30
31use crate::{
32    ast::statements::{Statement, StatementKind},
33    lexer::tokentypes::TokenType,
34    parser::parser_logic::Parser,
35    utils::errors::Error,
36};
37
38impl Parser {
39    /// Parses a `get` import statement.
40    ///
41    /// Called after `get` has been consumed. Dispatches on the tokens that
42    /// follow the first identifier:
43    ///
44    /// - **`get mod`** (no `::`, no `from`) - single-segment file import.
45    ///   Produces [`StatementKind::ImportFile`]`{ path: [mod] }`.
46    ///
47    /// - **`get mod::sub::…`** - multi-segment path. If the first segment is
48    ///   `std`, the last segment is treated as the function name and the rest
49    ///   as the namespace path -> [`StatementKind::Import`]. Otherwise the whole
50    ///   path is a file module -> [`StatementKind::ImportFile`].
51    ///
52    /// - **`get name, name from path`** - named imports. If `path` starts with
53    ///   `std` -> [`StatementKind::Import`]`{ names, path }`. Otherwise ->
54    ///   [`StatementKind::ImportFileNamed`]`{ path, names }`.
55    ///
56    /// # Errors
57    /// Returns an error if an identifier is missing after `get`, `::`, `,`, or
58    /// `from`, or if `from` itself is absent in the named-import form.
59    pub fn parse_import(&mut self, start: crate::utils::span::Span) -> Result<Statement, Error> {
60        let first = match self.peek() {
61            TokenType::Identifier(name) => name,
62            _ => return Err(self.err("expected identifier after 'get'", self.peek_span())),
63        };
64        self.advance();
65
66        // multi-segment path: get mod::sub  OR  get std::math::sin
67        if self.match_type(&[TokenType::ColonColon]) {
68            let mut segments = vec![first];
69            loop {
70                match self.peek() {
71                    TokenType::Identifier(seg) => {
72                        self.advance();
73                        segments.push(seg);
74                    }
75                    _ => return Err(self.err("expected identifier after '::'", self.peek_span())),
76                }
77                if !self.match_type(&[TokenType::ColonColon]) {
78                    break;
79                }
80            }
81            let span = start.join(self.previous_span());
82            let is_std = segments[0] == "std";
83            return if is_std {
84                // last segment is the function name; everything before it is the path
85                let name = segments
86                    .pop()
87                    .ok_or_else(|| self.err("expected function name after '::'", start))?;
88                Ok(Statement::new(
89                    StatementKind::Import {
90                        names: vec![name],
91                        path: segments,
92                    },
93                    span,
94                ))
95            } else {
96                Ok(Statement::new(
97                    StatementKind::ImportFile { path: segments },
98                    span,
99                ))
100            };
101        }
102
103        // single-segment file import: get mymodule
104        if !matches!(self.peek(), TokenType::Comma | TokenType::From) {
105            let span = start.join(self.previous_span());
106            return Ok(Statement::new(
107                StatementKind::ImportFile { path: vec![first] },
108                span,
109            ));
110        }
111
112        // named imports: get add, sub from …
113        let mut names = vec![first];
114        if self.match_type(&[TokenType::Comma]) {
115            loop {
116                match self.peek() {
117                    TokenType::Identifier(name) => {
118                        self.advance();
119                        names.push(name);
120                    }
121                    _ => return Err(self.err("expected identifier after ','", self.peek_span())),
122                }
123                if !self.match_type(&[TokenType::Comma]) {
124                    break;
125                }
126            }
127        }
128
129        if !self.match_type(&[TokenType::From]) {
130            return Err(self.err("expected 'from' after names", self.peek_span()));
131        }
132
133        let mut path = Vec::new();
134        loop {
135            match self.peek() {
136                TokenType::Identifier(segment) => {
137                    self.advance();
138                    path.push(segment);
139                }
140                _ => return Err(self.err("expected path after 'from'", self.peek_span())),
141            }
142            if !self.match_type(&[TokenType::ColonColon]) {
143                break;
144            }
145        }
146
147        let span = start.join(self.previous_span());
148        let is_std = path.first().map(|s| s == "std").unwrap_or(false);
149
150        if is_std {
151            Ok(Statement::new(StatementKind::Import { names, path }, span))
152        } else {
153            Ok(Statement::new(
154                StatementKind::ImportFileNamed { path, names },
155                span,
156            ))
157        }
158    }
159}