Skip to main content

nargo_types/
lib.rs

1#![warn(missing_docs)]
2
3pub mod context;
4pub mod document;
5pub mod errors;
6
7pub use context::NargoContext;
8pub use document::{Document, DocumentMeta, FrontMatter};
9pub use errors::{Error, ErrorKind, Result};
10use serde::{Deserialize, Serialize};
11use std::{collections::HashMap, hash::Hash};
12
13/// 源代码中一段文本的位置范围
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
15pub struct Span {
16    /// 起始位置
17    pub start: Position,
18    /// 结束位置
19    pub end: Position,
20}
21
22impl Span {
23    /// 创建一个新的 Span
24    pub fn new(start: Position, end: Position) -> Self {
25        Self { start, end }
26    }
27
28    /// 仅从字节偏移量创建位置范围(行列信息暂设为 0)
29    pub fn from_offsets(start: usize, end: usize) -> Self {
30        Self { start: Position::from_offset(start), end: Position::from_offset(end) }
31    }
32
33    /// 创建一个表示未知位置的 Span
34    pub fn unknown() -> Self {
35        Self { start: Position::unknown(), end: Position::unknown() }
36    }
37
38    /// 判断位置范围是否为未知
39    pub fn is_unknown(&self) -> bool {
40        self.start.is_unknown() && self.end.is_unknown()
41    }
42
43    /// 合并多个 Span,取其最大范围
44    pub fn merge(spans: &[Span]) -> Self {
45        if spans.is_empty() {
46            return Self::unknown();
47        }
48        let mut start = spans[0].start;
49        let mut end = spans[0].end;
50
51        for span in &spans[1..] {
52            if span.is_unknown() {
53                continue;
54            }
55            if start.is_unknown() || (span.start.offset < start.offset) {
56                start = span.start;
57            }
58            if end.is_unknown() || (span.end.offset > end.offset) {
59                end = span.end;
60            }
61        }
62        Self { start, end }
63    }
64}
65
66/// 源代码中的具体位置
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
68pub struct Position {
69    /// 行号(从 1 开始)
70    pub line: u32,
71    /// 列号(从 1 开始,按 UTF-16 字符计数)
72    pub column: u32,
73    /// 字节偏移量(从 0 开始)
74    pub offset: u32,
75}
76
77impl Position {
78    /// 创建一个新的 Position
79    pub fn new(line: u32, column: u32, offset: u32) -> Self {
80        Self { line, column, offset }
81    }
82
83    /// 仅从字节偏移量创建位置(行列信息暂设为 0)
84    pub fn from_offset(offset: usize) -> Self {
85        Self { line: 0, column: 0, offset: offset as u32 }
86    }
87
88    /// 创建一个表示未知位置的 Position
89    pub fn unknown() -> Self {
90        Self { line: 0, column: 0, offset: 0 }
91    }
92
93    /// 判断位置是否为未知
94    pub fn is_unknown(&self) -> bool {
95        self.line == 0 && self.column == 0
96    }
97}
98
99/// Nargo 文件结构
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct NargoFile {
102    /// 文件中的所有块
103    pub blocks: Vec<NargoBlock>,
104    /// 文件的整体位置范围
105    pub span: Span,
106}
107
108/// Nargo 文件中的代码块
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct NargoBlock {
111    /// 块名称
112    pub name: String,
113    /// 块属性
114    pub attributes: HashMap<String, String>,
115    /// 块内容
116    pub content: String,
117    /// 块的位置范围
118    pub span: Span,
119    /// 块内容的位置范围
120    pub content_span: Span,
121}
122
123/// 编译模式
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
125#[serde(rename_all = "lowercase")]
126pub enum CompileMode {
127    /// Vue 2 兼容模式
128    Vue2,
129    /// Vue 3 模式
130    Vue,
131}
132
133impl Default for CompileMode {
134    fn default() -> Self {
135        Self::Vue
136    }
137}
138
139/// 编译选项
140#[derive(Debug, Clone, Default, Serialize, Deserialize)]
141pub struct CompileOptions {
142    /// 编译模式
143    pub mode: CompileMode,
144    /// 是否启用服务端渲染
145    pub ssr: bool,
146    /// 是否启用水合
147    pub hydrate: bool,
148    /// 是否压缩代码
149    pub minify: bool,
150    /// 是否为生产环境
151    pub is_prod: bool,
152    /// 目标平台
153    pub target: Option<String>,
154    /// 作用域 ID
155    pub scope_id: Option<String>,
156    /// i18n 语言
157    pub i18n_locale: Option<String>,
158    /// Vue 响应式转换
159    pub vue_reactivity_transform: bool,
160    /// Vue define_model
161    pub vue_define_model: bool,
162    /// Vue Props 解构
163    pub vue_props_destructure: bool,
164}
165
166/// Nargo 值类型
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
168pub enum NargoValue {
169    /// Null 值
170    #[default]
171    Null,
172    /// 布尔值
173    Bool(bool),
174    /// 数字
175    Number(f64),
176    /// 字符串
177    String(String),
178    /// 数组
179    Array(Vec<NargoValue>),
180    /// 对象
181    Object(HashMap<String, NargoValue>),
182    /// 响应式信号引用
183    Signal(String),
184    /// 二进制数据 (如 WASM)
185    Binary(Vec<u8>),
186    /// 源码片段或表达式代码
187    Raw(String),
188    /// 跨节点引用或 ID
189    Ref(String),
190}
191
192impl NargoValue {
193    /// 获取对象中的值
194    pub fn get(&self, key: &str) -> Option<&NargoValue> {
195        match self {
196            NargoValue::Object(map) => map.get(key),
197            _ => None,
198        }
199    }
200
201    /// 转换为数组引用
202    pub fn as_array(&self) -> Option<&Vec<NargoValue>> {
203        match self {
204            NargoValue::Array(arr) => Some(arr),
205            _ => None,
206        }
207    }
208
209    /// 检查值是否为 null
210    pub fn is_null(&self) -> bool {
211        matches!(self, NargoValue::Null)
212    }
213
214    /// 验证值的有效性
215    pub fn validate(&self, depth: usize) -> Result<()> {
216        const MAX_RECURSION_DEPTH: usize = 100;
217        const MAX_STRING_LENGTH: usize = 1024 * 1024;
218        const MAX_ARRAY_LENGTH: usize = 10000;
219        const MAX_OBJECT_SIZE: usize = 1000;
220
221        if depth > MAX_RECURSION_DEPTH {
222            return Err(Error::external_error("nargo-value".to_string(), "Value recursion depth exceeded".to_string(), Span::unknown()));
223        }
224
225        match self {
226            NargoValue::Null => Ok(()),
227            NargoValue::Bool(_) => Ok(()),
228            NargoValue::Number(_) => Ok(()),
229            NargoValue::String(s) => {
230                if s.len() > MAX_STRING_LENGTH {
231                    return Err(Error::external_error("nargo-value".to_string(), "String length exceeded".to_string(), Span::unknown()));
232                }
233                Ok(())
234            }
235            NargoValue::Array(arr) => {
236                if arr.len() > MAX_ARRAY_LENGTH {
237                    return Err(Error::external_error("nargo-value".to_string(), "Array length exceeded".to_string(), Span::unknown()));
238                }
239                for item in arr {
240                    item.validate(depth + 1)?;
241                }
242                Ok(())
243            }
244            NargoValue::Object(map) => {
245                if map.len() > MAX_OBJECT_SIZE {
246                    return Err(Error::external_error("nargo-value".to_string(), "Object size exceeded".to_string(), Span::unknown()));
247                }
248                for (key, value) in map {
249                    if key.len() > MAX_STRING_LENGTH {
250                        return Err(Error::external_error("nargo-value".to_string(), "Object key length exceeded".to_string(), Span::unknown()));
251                    }
252                    value.validate(depth + 1)?;
253                }
254                Ok(())
255            }
256            NargoValue::Signal(s) => {
257                if s.len() > MAX_STRING_LENGTH {
258                    return Err(Error::external_error("nargo-value".to_string(), "Signal length exceeded".to_string(), Span::unknown()));
259                }
260                Ok(())
261            }
262            NargoValue::Binary(b) => {
263                if b.len() > MAX_STRING_LENGTH {
264                    return Err(Error::external_error("nargo-value".to_string(), "Binary data length exceeded".to_string(), Span::unknown()));
265                }
266                Ok(())
267            }
268            NargoValue::Raw(s) => {
269                if s.len() > MAX_STRING_LENGTH {
270                    return Err(Error::external_error("nargo-value".to_string(), "Raw data length exceeded".to_string(), Span::unknown()));
271                }
272                Ok(())
273            }
274            NargoValue::Ref(s) => {
275                if s.len() > MAX_STRING_LENGTH {
276                    return Err(Error::external_error("nargo-value".to_string(), "Ref length exceeded".to_string(), Span::unknown()));
277                }
278                Ok(())
279            }
280        }
281    }
282}
283
284/// 路由配置
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct RouterConfig {
287    /// 路由列表
288    pub routes: Vec<Route>,
289    /// 路由模式
290    pub mode: String,
291    /// 基础路径
292    pub base: Option<String>,
293    /// 配置的位置范围
294    pub span: Span,
295}
296
297/// 单个路由配置
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct Route {
300    /// 路由路径
301    pub path: String,
302    /// 关联组件
303    pub component: String,
304    /// 路由名称
305    pub name: Option<String>,
306    /// 重定向路径
307    pub redirect: Option<String>,
308    /// 子路由
309    pub children: Option<Vec<Route>>,
310    /// 路由元数据
311    pub meta: Option<NargoValue>,
312    /// 路由的位置范围
313    pub span: Span,
314}
315
316/// 判断 HTML 标签是否为自闭合标签
317pub fn is_void_element(tag: &str) -> bool {
318    matches!(tag, "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link" | "meta" | "param" | "source" | "track" | "wbr")
319}
320
321/// 判断位置是否在指定的范围内
322pub fn is_pos_in_span(pos: Position, span: Span) -> bool {
323    if span.is_unknown() {
324        return false;
325    }
326
327    if pos.line < span.start.line || pos.line > span.end.line {
328        return false;
329    }
330
331    if pos.line == span.start.line && pos.column < span.start.column {
332        return false;
333    }
334
335    if pos.line == span.end.line && pos.column > span.end.column {
336        return false;
337    }
338
339    true
340}
341
342/// 判断字符是否为字母或下划线或美元符号
343pub fn is_alphabetic(c: char) -> bool {
344    c.is_alphabetic() || c == '_' || c == '$'
345}
346
347/// 判断字符是否为字母数字或下划线或美元符号
348pub fn is_alphanumeric(c: char) -> bool {
349    c.is_alphanumeric() || c == '_' || c == '$'
350}
351
352/// 用于 TypeScript 生成的桥接字段信息
353#[derive(Debug, Clone)]
354pub struct BridgeField {
355    /// 字段名称
356    pub name: String,
357    /// Rust 类型
358    pub ty: String,
359}
360
361/// 用于 TypeScript 生成的桥接类型信息
362#[derive(Debug, Clone)]
363pub struct BridgeType {
364    /// 类型名称
365    pub name: String,
366    /// 字段列表
367    pub fields: Vec<BridgeField>,
368}
369
370/// 可以桥接到 TypeScript 的类型 trait
371pub trait TypeBridge {
372    /// 获取桥接信息
373    fn bridge_info() -> BridgeType;
374}
375
376impl NargoValue {
377    /// 转换为字符串引用
378    pub fn as_str(&self) -> Option<&str> {
379        match self {
380            NargoValue::String(s) => Some(s),
381            NargoValue::Signal(s) => Some(s),
382            NargoValue::Raw(s) => Some(s),
383            NargoValue::Ref(s) => Some(s),
384            _ => None,
385        }
386    }
387
388    /// 转换为布尔值
389    pub fn as_bool(&self) -> Option<bool> {
390        match self {
391            NargoValue::Bool(b) => Some(*b),
392            _ => None,
393        }
394    }
395
396    /// 转换为数字
397    pub fn as_number(&self) -> Option<f64> {
398        match self {
399            NargoValue::Number(n) => Some(*n),
400            _ => None,
401        }
402    }
403
404    /// 转换为二进制数据引用
405    pub fn as_binary(&self) -> Option<&[u8]> {
406        match self {
407            NargoValue::Binary(b) => Some(b),
408            _ => None,
409        }
410    }
411
412    /// 转换为对象引用
413    pub fn as_object(&self) -> Option<&HashMap<String, NargoValue>> {
414        match self {
415            NargoValue::Object(o) => Some(o),
416            _ => None,
417        }
418    }
419
420    /// 序列化为 JSON 字符串
421    pub fn to_json(&self) -> serde_json::Result<String> {
422        serde_json::to_string(self)
423    }
424
425    /// 从 JSON 字符串反序列化
426    pub fn from_json(json: &str) -> serde_json::Result<Self> {
427        serde_json::from_str(json)
428    }
429
430    /// 判断值是否与 JSON 兼容
431    pub fn is_json_compatible(&self) -> bool {
432        match self {
433            NargoValue::Null | NargoValue::Bool(_) | NargoValue::Number(_) | NargoValue::String(_) | NargoValue::Array(_) | NargoValue::Object(_) => true,
434            NargoValue::Signal(_) | NargoValue::Binary(_) | NargoValue::Raw(_) | NargoValue::Ref(_) => false,
435        }
436    }
437}
438
439impl std::fmt::Display for NargoValue {
440    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441        match self {
442            NargoValue::Null => write!(f, "null"),
443            NargoValue::Bool(b) => write!(f, "{}", b),
444            NargoValue::Number(n) => write!(f, "{}", n),
445            NargoValue::String(s) => write!(f, "{:?}", s),
446            NargoValue::Array(arr) => {
447                write!(f, "[")?;
448                for (i, item) in arr.iter().enumerate() {
449                    if i > 0 {
450                        write!(f, ", ")?;
451                    }
452                    write!(f, "{}", item)?;
453                }
454                write!(f, "]")
455            }
456            NargoValue::Object(obj) => {
457                write!(f, "{{")?;
458                let mut first = true;
459                for (k, v) in obj {
460                    if !first {
461                        write!(f, ", ")?;
462                    }
463                    write!(f, "{}: {}", k, v)?;
464                    first = false;
465                }
466                write!(f, "}}")
467            }
468            NargoValue::Signal(s) => write!(f, "${}", s),
469            NargoValue::Binary(_) => write!(f, "<binary>"),
470            NargoValue::Raw(s) => write!(f, "{}", s),
471            NargoValue::Ref(s) => write!(f, "@{}", s),
472        }
473    }
474}
475
476/// 源代码解析游标
477pub struct Cursor<'a> {
478    /// 源代码字符串
479    pub source: &'a str,
480    /// 当前字节偏移位置
481    pub pos: usize,
482    /// 当前行号
483    pub line: usize,
484    /// 当前列号
485    pub column: usize,
486    /// 基础偏移量
487    pub base_offset: usize,
488}
489
490impl<'a> Cursor<'a> {
491    /// 创建一个新的游标
492    pub fn new(source: &'a str) -> Self {
493        Self { source, pos: 0, line: 1, column: 1, base_offset: 0 }
494    }
495
496    /// 从指定位置创建游标
497    pub fn with_position(source: &'a str, pos: Position) -> Self {
498        Self { source, pos: pos.offset as usize, line: pos.line as usize, column: pos.column as usize, base_offset: 0 }
499    }
500
501    /// 从切片源创建游标
502    pub fn with_sliced_source(source: &'a str, pos: Position) -> Self {
503        Self { source, pos: 0, line: pos.line as usize, column: pos.column as usize, base_offset: pos.offset as usize }
504    }
505
506    /// 判断是否已到达文件末尾
507    pub fn is_eof(&self) -> bool {
508        self.pos >= self.source.len()
509    }
510
511    /// 查看当前字符(不消费)
512    pub fn peek(&self) -> char {
513        self.source[self.pos..].chars().next().unwrap_or('\0')
514    }
515
516    /// 查看第 n 个字符(不消费)
517    pub fn peek_n(&self, n: usize) -> char {
518        self.source[self.pos..].chars().nth(n).unwrap_or('\0')
519    }
520
521    /// 判断当前位置是否以指定字符串开头
522    pub fn peek_str(&self, s: &str) -> bool {
523        self.source[self.pos..].starts_with(s)
524    }
525
526    /// 消费当前字符
527    pub fn consume(&mut self) -> char {
528        let c = self.peek();
529        if c == '\0' {
530            return c;
531        }
532        self.pos += c.len_utf8();
533        if c == '\n' {
534            self.line += 1;
535            self.column = 1;
536        }
537        else {
538            self.column += c.len_utf16();
539        }
540        c
541    }
542
543    /// 消费 n 个字符
544    pub fn consume_n(&mut self, n: usize) {
545        for _ in 0..n {
546            self.consume();
547        }
548    }
549
550    /// 如果匹配则消费字符串
551    pub fn consume_str(&mut self, s: &str) -> bool {
552        if self.peek_str(s) {
553            self.consume_n(s.chars().count());
554            true
555        }
556        else {
557            false
558        }
559    }
560
561    /// 消费满足条件的字符
562    pub fn consume_while<F>(&mut self, f: F) -> String
563    where
564        F: Fn(char) -> bool,
565    {
566        let start = self.pos;
567        while !self.is_eof() && f(self.peek()) {
568            self.consume();
569        }
570        self.current_str(start).to_string()
571    }
572
573    /// 跳过空白字符
574    pub fn skip_whitespace(&mut self) {
575        while !self.is_eof() && self.peek().is_whitespace() {
576            self.consume();
577        }
578    }
579
580    /// 消费并返回空白字符
581    pub fn consume_whitespace(&mut self) -> String {
582        let start = self.pos;
583        while !self.is_eof() && self.peek().is_whitespace() {
584            self.consume();
585        }
586        self.current_str(start).to_string()
587    }
588
589    /// 跳过空格和制表符
590    pub fn skip_spaces(&mut self) {
591        while !self.is_eof() && (self.peek() == ' ' || self.peek() == '\t') {
592            self.consume();
593        }
594    }
595
596    /// 期望消费指定字符
597    pub fn expect(&mut self, expected: char) -> Result<()> {
598        if self.peek() == expected {
599            self.consume();
600            Ok(())
601        }
602        else {
603            Err(Error::expected_char(expected, self.peek(), self.span_at_current()))
604        }
605    }
606
607    /// 期望消费指定字符串
608    pub fn expect_str(&mut self, expected: &str) -> Result<()> {
609        if self.peek_str(expected) {
610            self.consume_n(expected.chars().count());
611            Ok(())
612        }
613        else {
614            Err(Error::expected_string(expected.to_string(), self.peek().to_string(), self.span_at_current()))
615        }
616    }
617
618    /// 获取当前位置
619    pub fn position(&self) -> Position {
620        Position { line: self.line as u32, column: self.column as u32, offset: (self.base_offset + self.pos) as u32 }
621    }
622
623    /// 获取从起始位置到当前位置的字符串
624    pub fn current_str(&self, start: usize) -> &str {
625        &self.source[start..self.pos]
626    }
627
628    /// 获取当前字符的位置范围
629    pub fn span_at_current(&self) -> Span {
630        let start = self.position();
631        let mut end = start;
632        let c = self.peek();
633        if c != '\0' {
634            end.column += c.len_utf16() as u32;
635            end.offset += c.len_utf8() as u32;
636        }
637        Span { start, end }
638    }
639
640    /// 获取从指定起始位置到当前位置的范围
641    pub fn span_from(&self, start: Position) -> Span {
642        Span { start, end: self.position() }
643    }
644
645    /// 消费一个标识符
646    pub fn consume_ident(&mut self) -> Result<String> {
647        let start = self.pos;
648        if !is_alphabetic(self.peek()) {
649            return Err(Error::parse_error("Expected identifier".to_string(), self.span_at_current()));
650        }
651        while !self.is_eof() && is_alphanumeric(self.peek()) {
652            self.consume();
653        }
654        Ok(self.current_str(start).to_string())
655    }
656
657    /// 消费一个字符串字面量
658    pub fn consume_string(&mut self) -> Result<String> {
659        let quote = self.peek();
660        if quote != '"' && quote != '\'' {
661            return Err(Error::parse_error("Expected string".to_string(), self.span_at_current()));
662        }
663        self.consume();
664        let start = self.pos;
665        while !self.is_eof() && self.peek() != quote {
666            self.consume();
667        }
668        let s = self.current_str(start).to_string();
669        self.expect(quote)?;
670        Ok(s)
671    }
672
673    /// 在指定位置创建一个零宽度的 Span
674    pub fn span_at_pos(&self, pos: Position) -> Span {
675        Span { start: pos, end: pos }
676    }
677}
678
679/// 代码生成器
680#[derive(Debug, Clone, Default)]
681pub struct CodeWriter {
682    buffer: String,
683    indent_level: usize,
684    mappings: Vec<(Position, Span)>,
685    current_pos: Position,
686}
687
688impl CodeWriter {
689    /// 创建一个新的代码生成器
690    pub fn new() -> Self {
691        Self::default()
692    }
693
694    /// 写入文本
695    pub fn write(&mut self, text: &str) {
696        if self.buffer.is_empty() || self.buffer.ends_with('\n') {
697            let indent = "  ".repeat(self.indent_level);
698            self.buffer.push_str(&indent);
699            self.current_pos.column += indent.len() as u32;
700            self.current_pos.offset += indent.len() as u32;
701        }
702
703        self.buffer.push_str(text);
704        let lines: Vec<&str> = text.split('\n').collect();
705        if lines.len() > 1 {
706            self.current_pos.line += (lines.len() - 1) as u32;
707            if let Some(last_line) = lines.last() {
708                self.current_pos.column = last_line.len() as u32;
709            }
710        }
711        else {
712            self.current_pos.column += text.len() as u32;
713        }
714        self.current_pos.offset += text.len() as u32;
715    }
716
717    /// 写入文本并记录源位置映射
718    pub fn write_with_span(&mut self, text: &str, span: Span) {
719        if !span.is_unknown() {
720            self.mappings.push((self.current_pos, span));
721        }
722        self.write(text);
723    }
724
725    /// 写入一行文本
726    pub fn write_line(&mut self, text: &str) {
727        self.write(text);
728        self.newline();
729    }
730
731    /// 写入一行文本并记录源位置映射
732    pub fn write_line_with_span(&mut self, text: &str, span: Span) {
733        self.write_with_span(text, span);
734        self.newline();
735    }
736
737    /// 写入换行符
738    pub fn newline(&mut self) {
739        self.buffer.push('\n');
740        self.current_pos.line += 1;
741        self.current_pos.column = 0;
742        self.current_pos.offset += 1;
743    }
744
745    /// 增加缩进级别
746    pub fn indent(&mut self) {
747        self.indent_level += 1;
748    }
749
750    /// 减少缩进级别
751    pub fn dedent(&mut self) {
752        if self.indent_level > 0 {
753            self.indent_level -= 1;
754        }
755    }
756
757    /// 获取当前位置
758    pub fn position(&self) -> Position {
759        self.current_pos
760    }
761
762    /// 追加另一个代码生成器的内容
763    pub fn append(&mut self, other: CodeWriter) {
764        let (other_buf, other_mappings) = other.finish();
765        for (mut pos, span) in other_mappings {
766            pos.line += self.current_pos.line;
767            if pos.line == self.current_pos.line {
768                pos.column += self.current_pos.column;
769            }
770            pos.offset += self.current_pos.offset;
771            self.mappings.push((pos, span));
772        }
773        self.write(&other_buf);
774    }
775
776    /// 完成生成并返回代码和位置映射
777    pub fn finish(self) -> (String, Vec<(Position, Span)>) {
778        (self.buffer, self.mappings)
779    }
780}