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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
15pub struct Span {
16 pub start: Position,
18 pub end: Position,
20}
21
22impl Span {
23 pub fn new(start: Position, end: Position) -> Self {
25 Self { start, end }
26 }
27
28 pub fn from_offsets(start: usize, end: usize) -> Self {
30 Self { start: Position::from_offset(start), end: Position::from_offset(end) }
31 }
32
33 pub fn unknown() -> Self {
35 Self { start: Position::unknown(), end: Position::unknown() }
36 }
37
38 pub fn is_unknown(&self) -> bool {
40 self.start.is_unknown() && self.end.is_unknown()
41 }
42
43 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
68pub struct Position {
69 pub line: u32,
71 pub column: u32,
73 pub offset: u32,
75}
76
77impl Position {
78 pub fn new(line: u32, column: u32, offset: u32) -> Self {
80 Self { line, column, offset }
81 }
82
83 pub fn from_offset(offset: usize) -> Self {
85 Self { line: 0, column: 0, offset: offset as u32 }
86 }
87
88 pub fn unknown() -> Self {
90 Self { line: 0, column: 0, offset: 0 }
91 }
92
93 pub fn is_unknown(&self) -> bool {
95 self.line == 0 && self.column == 0
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct NargoFile {
102 pub blocks: Vec<NargoBlock>,
104 pub span: Span,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct NargoBlock {
111 pub name: String,
113 pub attributes: HashMap<String, String>,
115 pub content: String,
117 pub span: Span,
119 pub content_span: Span,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
125#[serde(rename_all = "lowercase")]
126pub enum CompileMode {
127 Vue2,
129 Vue,
131}
132
133impl Default for CompileMode {
134 fn default() -> Self {
135 Self::Vue
136 }
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize)]
141pub struct CompileOptions {
142 pub mode: CompileMode,
144 pub ssr: bool,
146 pub hydrate: bool,
148 pub minify: bool,
150 pub is_prod: bool,
152 pub target: Option<String>,
154 pub scope_id: Option<String>,
156 pub i18n_locale: Option<String>,
158 pub vue_reactivity_transform: bool,
160 pub vue_define_model: bool,
162 pub vue_props_destructure: bool,
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
168pub enum NargoValue {
169 #[default]
171 Null,
172 Bool(bool),
174 Number(f64),
176 String(String),
178 Array(Vec<NargoValue>),
180 Object(HashMap<String, NargoValue>),
182 Signal(String),
184 Binary(Vec<u8>),
186 Raw(String),
188 Ref(String),
190}
191
192impl NargoValue {
193 pub fn get(&self, key: &str) -> Option<&NargoValue> {
195 match self {
196 NargoValue::Object(map) => map.get(key),
197 _ => None,
198 }
199 }
200
201 pub fn as_array(&self) -> Option<&Vec<NargoValue>> {
203 match self {
204 NargoValue::Array(arr) => Some(arr),
205 _ => None,
206 }
207 }
208
209 pub fn is_null(&self) -> bool {
211 matches!(self, NargoValue::Null)
212 }
213
214 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#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct RouterConfig {
287 pub routes: Vec<Route>,
289 pub mode: String,
291 pub base: Option<String>,
293 pub span: Span,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct Route {
300 pub path: String,
302 pub component: String,
304 pub name: Option<String>,
306 pub redirect: Option<String>,
308 pub children: Option<Vec<Route>>,
310 pub meta: Option<NargoValue>,
312 pub span: Span,
314}
315
316pub 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
321pub 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
342pub fn is_alphabetic(c: char) -> bool {
344 c.is_alphabetic() || c == '_' || c == '$'
345}
346
347pub fn is_alphanumeric(c: char) -> bool {
349 c.is_alphanumeric() || c == '_' || c == '$'
350}
351
352#[derive(Debug, Clone)]
354pub struct BridgeField {
355 pub name: String,
357 pub ty: String,
359}
360
361#[derive(Debug, Clone)]
363pub struct BridgeType {
364 pub name: String,
366 pub fields: Vec<BridgeField>,
368}
369
370pub trait TypeBridge {
372 fn bridge_info() -> BridgeType;
374}
375
376impl NargoValue {
377 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 pub fn as_bool(&self) -> Option<bool> {
390 match self {
391 NargoValue::Bool(b) => Some(*b),
392 _ => None,
393 }
394 }
395
396 pub fn as_number(&self) -> Option<f64> {
398 match self {
399 NargoValue::Number(n) => Some(*n),
400 _ => None,
401 }
402 }
403
404 pub fn as_binary(&self) -> Option<&[u8]> {
406 match self {
407 NargoValue::Binary(b) => Some(b),
408 _ => None,
409 }
410 }
411
412 pub fn as_object(&self) -> Option<&HashMap<String, NargoValue>> {
414 match self {
415 NargoValue::Object(o) => Some(o),
416 _ => None,
417 }
418 }
419
420 pub fn to_json(&self) -> serde_json::Result<String> {
422 serde_json::to_string(self)
423 }
424
425 pub fn from_json(json: &str) -> serde_json::Result<Self> {
427 serde_json::from_str(json)
428 }
429
430 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
476pub struct Cursor<'a> {
478 pub source: &'a str,
480 pub pos: usize,
482 pub line: usize,
484 pub column: usize,
486 pub base_offset: usize,
488}
489
490impl<'a> Cursor<'a> {
491 pub fn new(source: &'a str) -> Self {
493 Self { source, pos: 0, line: 1, column: 1, base_offset: 0 }
494 }
495
496 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 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 pub fn is_eof(&self) -> bool {
508 self.pos >= self.source.len()
509 }
510
511 pub fn peek(&self) -> char {
513 self.source[self.pos..].chars().next().unwrap_or('\0')
514 }
515
516 pub fn peek_n(&self, n: usize) -> char {
518 self.source[self.pos..].chars().nth(n).unwrap_or('\0')
519 }
520
521 pub fn peek_str(&self, s: &str) -> bool {
523 self.source[self.pos..].starts_with(s)
524 }
525
526 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 pub fn consume_n(&mut self, n: usize) {
545 for _ in 0..n {
546 self.consume();
547 }
548 }
549
550 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 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 pub fn skip_whitespace(&mut self) {
575 while !self.is_eof() && self.peek().is_whitespace() {
576 self.consume();
577 }
578 }
579
580 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 pub fn skip_spaces(&mut self) {
591 while !self.is_eof() && (self.peek() == ' ' || self.peek() == '\t') {
592 self.consume();
593 }
594 }
595
596 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 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 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 pub fn current_str(&self, start: usize) -> &str {
625 &self.source[start..self.pos]
626 }
627
628 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 pub fn span_from(&self, start: Position) -> Span {
642 Span { start, end: self.position() }
643 }
644
645 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 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 pub fn span_at_pos(&self, pos: Position) -> Span {
675 Span { start: pos, end: pos }
676 }
677}
678
679#[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 pub fn new() -> Self {
691 Self::default()
692 }
693
694 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 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 pub fn write_line(&mut self, text: &str) {
727 self.write(text);
728 self.newline();
729 }
730
731 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 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 pub fn indent(&mut self) {
747 self.indent_level += 1;
748 }
749
750 pub fn dedent(&mut self) {
752 if self.indent_level > 0 {
753 self.indent_level -= 1;
754 }
755 }
756
757 pub fn position(&self) -> Position {
759 self.current_pos
760 }
761
762 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 pub fn finish(self) -> (String, Vec<(Position, Span)>) {
778 (self.buffer, self.mappings)
779 }
780}