rsx_compiler/
lib.rs

1use crate::ast::ProgramNode;
2pub use crate::errors::Result;
3pub use crate::errors::TranslatorError;
4use crate::generate_ts::GenerateTs;
5use crate::line_numbers::LineMarks;
6use arcstr::ArcStr;
7use lru::LruCache;
8use oxc_sourcemap::{SourceMap, SourceMapBuilder};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::mem::take;
12use std::num::NonZeroUsize;
13
14pub mod rs_translate;
15// Declare modules
16pub mod ast;
17mod errors;
18mod generate_ts;
19mod line_numbers;
20
21/// Represents the source identifier, typically a file path.
22pub type SourceId = ArcStr;
23
24#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
25#[serde(rename_all = "kebab-case", tag = "type")]
26pub enum SourceSpan {
27    Offset { x1: u64, x2: u64 },
28    LineColumn { x1: u32, y1: u32, x2: u32, y2: u32 },
29}
30
31/// Generates JavaScript code and a source map from an AST.
32pub struct TsGenerator {
33    /// Source map builder for generating mappings
34    sm_builder: SourceMapBuilder,
35    /// Buffer for generated code
36    gc_buffer: String,
37    /// Current line in the buffer
38    current_line: u32,
39    /// Current column in the buffer
40    current_column: u32,
41    /// Next source id
42    next_id: u32,
43    file_map: HashMap<ArcStr, FileInfo>,
44    file_cache: LruCache<ArcStr, FileProvider>,
45    /// Current indentation level
46    indent_level: u32,
47    /// Indentation string (e.g., "  " or "\t")
48    indent_text: &'static str,
49}
50
51pub struct FileInfo {
52    /// Map SourceId (ArcStr) to internal u32 ID
53    id: u32,
54    // path: String, // Path is the key (SourceId)
55}
56
57#[derive(Clone)] // Clone needed for LruCache interaction
58pub struct FileProvider {
59    content: String, // Use ArcStr for efficient cloning
60    line_marks: LineMarks,
61}
62
63impl TsGenerator {
64    pub fn new() -> Self {
65        Self {
66            sm_builder: Default::default(),
67            gc_buffer: String::new(),
68            current_line: 0,
69            current_column: 0,
70            next_id: 0,
71            file_map: HashMap::new(),
72            file_cache: LruCache::new(NonZeroUsize::new(100).unwrap()), // Safe unwrap for non-zero
73            indent_level: 0,
74            indent_text: "    ", // Default to 2 spaces
75        }
76    }
77}
78
79impl TsGenerator {
80    /// Generates code for a given AST node.
81    /// This is the main entry point for the generation process.
82    pub fn generate(&mut self, node: &ProgramNode) -> Result<(String, SourceMap)> {
83        node.generate_ts(self)?;
84        let code = take(&mut self.gc_buffer);
85        let map = take(&mut self.sm_builder);
86        Ok((code, map.into_sourcemap()))
87    }
88
89    /// Gets or creates the internal u32 source ID for a given SourceId (ArcStr).
90    /// Adds the source and its content to the SourceMapBuilder if it's new.
91    fn get_file(&mut self, source_id: &ArcStr) -> Result<FileProvider> {
92        match self.file_cache.get(source_id) {
93            Some(s) => Ok(s.clone()),
94            None => {
95                let content = std::fs::read_to_string(source_id.as_str())?;
96                let line_positions = LineMarks::from(content.as_str());
97                let provider = FileProvider {
98                    content,
99                    line_marks: line_positions.clone(),
100                };
101                self.file_cache.put(source_id.clone(), provider.clone());
102                Ok(provider)
103            }
104        }
105    }
106
107    fn get_content(&mut self, source_id: &ArcStr) -> Result<String> {
108        match self.file_cache.get(source_id) {
109            Some(s) => Ok(s.content.clone()),
110            None => {
111                let content = std::fs::read_to_string(source_id.as_str())?;
112                let line_positions = LineMarks::from(content.as_str());
113                let provider = FileProvider {
114                    content: content.clone(),
115                    line_marks: line_positions.clone(),
116                };
117                self.file_cache.put(source_id.clone(), provider.clone());
118                Ok(content)
119            }
120        }
121    }
122
123    fn get_file_id(&mut self, source_id: &ArcStr) -> Result<u32> {
124        match self.file_map.get(source_id) {
125            Some(file_info) => Ok(file_info.id),
126            None => {
127                let id = self.next_id;
128                self.next_id += 1;
129                self.file_map.insert(source_id.clone(), FileInfo { id });
130                Ok(id)
131            }
132        }
133    }
134
135    /// Appends code segment and adds source mapping for the start of the segment.
136    fn append_mapping(&mut self, code: &str, span: SourceSpan, source_id: &SourceId) -> Result<()> {
137        let file = self.get_file(source_id)?;
138        let (src_line, src_column) = match span {
139            SourceSpan::Offset { x1, .. } => {
140                let line = file.line_marks.from_offset(x1 as usize);
141                (line.0.line, line.1 as u32)
142            }
143            SourceSpan::LineColumn { x1, y1, .. } => (x1, y1),
144        };
145        let file_id = self.get_file_id(source_id)?;
146        self.sm_builder.add_name(code);
147        self.sm_builder
148            .add_source_and_content(source_id.as_str(), &file.content);
149        self.sm_builder.add_token(
150            self.current_line,
151            self.current_column,
152            src_line,
153            src_column,
154            Some(file_id),
155            None,
156        );
157        self.append_text(code);
158        Ok(())
159    }
160
161    /// Appends code segment without adding mapping.
162    fn append_text(&mut self, code: &str) {
163        self.gc_buffer.push_str(code);
164        for c in code.chars() {
165            if c == '\n' {
166                self.current_line += 1;
167                self.current_column = 0;
168            } else {
169                self.current_column += 1;
170            }
171        }
172    }
173
174    pub fn indent(&mut self, text: &str) {
175        self.append_text(text);
176        self.indent_level += 1;
177        self.append_newline()
178    }
179    pub fn dedent(&mut self, text: &str) {
180        self.append_text(text);
181        self.indent_level = self.indent_level.saturating_sub(1);
182        self.append_newline();
183    }
184    pub fn append_newline(&mut self) {
185        self.append_text("\n");
186        for _ in 0..self.indent_level {
187            self.append_text(self.indent_text);
188        }
189    }
190}