cursor!
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 28 Aug 2024 16:33:49 +0000 (09:33 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 28 Aug 2024 16:33:49 +0000 (09:33 -0700)
rust/pspp/src/command.rs
rust/pspp/src/engine.rs
rust/pspp/src/lex/lexer.rs

index c749b5f2dc2747cc212ca0df5d58ad8c0139a384..6a6772d16aaf1d0e90fb471c5729eab9febc1077 100644 (file)
@@ -105,7 +105,11 @@ fn commands() -> &'static [Command] {
                 no_abbrev: false,
                 name: "ECHO",
                 run: Box::new(|context| {
-                    println!("hi");
+                    let cursor = context.lexer.cursor();
+                    match cursor.force_string() {
+                        Ok(s) => println!("\"{s}\""),
+                        Err(e) => println!("{e}")
+                    }
                 }),
             },
             /*
index abfc2da8b369b55897b921feace1fb0d1ad1d300..6ee46233031e5989db289051a7128c5aa702eca6 100644 (file)
@@ -1,7 +1,7 @@
 use crate::{
     command::parse_command,
     lex::{
-        lexer::{TokenSlice, NewSource},
+        lexer::{TokenSlice, Source},
     },
     macros::MacroSet,
     message::Diagnostic,
@@ -13,7 +13,7 @@ impl Engine {
     pub fn new() -> Self {
         Self
     }
-    pub fn run(&mut self, mut source: NewSource) {
+    pub fn run(&mut self, mut source: Source) {
         let macros = MacroSet::new();
         while let Some(tokens) = source.read_command(&macros) {
             let error: Box<dyn Fn(Diagnostic)> = Box::new(|diagnostic| {
@@ -28,14 +28,14 @@ impl Engine {
 mod tests {
     use encoding_rs::UTF_8;
 
-    use crate::lex::lexer::{NewSource, SourceFile};
+    use crate::lex::lexer::{Source, SourceFile};
 
     use super::Engine;
 
     #[test]
     fn test_echo() {
         let mut engine = Engine::new();
-        engine.run(NewSource::new_default(&SourceFile::for_file_contents(
+        engine.run(Source::new_default(&SourceFile::for_file_contents(
             "ECHO 'hi there'.\nECHO 'bye there'.\n".to_string(),
             Some("test.sps".to_string()),
             UTF_8,
index 47c70d169737c450744c9b54547faff351ede6f5..02d08e1f244d9c676c025f2e0d5f20ca53787e95 100644 (file)
@@ -1,5 +1,6 @@
 use std::{
     borrow::{Borrow, Cow},
+    cell::Cell,
     collections::VecDeque,
     fmt::{Debug, Formatter, Result as FmtResult, Write},
     fs,
@@ -8,7 +9,7 @@ use std::{
     mem::take,
     ops::{Range, RangeInclusive},
     path::Path,
-    ptr, slice,
+    ptr,
     sync::Arc,
 };
 
@@ -206,19 +207,6 @@ pub struct LexToken<'a> {
     macro_rep: Option<MacroRepresentation>,
 }
 
-impl LexToken<'_> {
-    pub fn force_string(&self) -> Result<&str, Diagnostic> {
-        if let Token::String(s) = &self.token {
-            Ok(s.as_str())
-        } else {
-            let slice = TokenSlice {
-                tokens: slice::from_ref(self),
-            };
-            Err(slice.error("Syntax error expecting string."))
-        }
-    }
-}
-
 struct LexError {
     error: ScanError,
     pos: Range<usize>,
@@ -376,6 +364,10 @@ impl<'a> TokenSlice<'a> {
         }
     }
 
+    pub fn cursor(&'a self) -> Cursor<'a> {
+        Cursor::new(self)
+    }
+
     pub fn get_token(&self, index: usize) -> Option<&Token> {
         self.get(index).map(|token| &token.token)
     }
@@ -511,14 +503,39 @@ impl<'a> TokenSlice<'a> {
     }
 }
 
-pub struct NewSource<'a> {
+pub struct Cursor<'a> {
+    slice: &'a TokenSlice<'a>,
+
+    /// This allows [Self::force_string] etc. to advance while returning the
+    /// token without cloning it.
+    pos: Cell<usize>,
+}
+
+impl<'a> Cursor<'a> {
+    pub fn new(slice: &'a TokenSlice<'a>) -> Self {
+        Self { slice, pos: Cell::new(0) }
+    }
+
+    pub fn force_string(&self) -> Result<&str, Diagnostic> {
+        let pos = self.pos.get();
+        if let Some(Token::String(s)) = self.slice.get_token(pos) {
+            self.pos.set(pos + 1);
+            Ok(s.as_str())
+        } else {
+            let slice = self.slice.subslice(pos..self.slice.len());
+            Err(slice.error("Syntax error expecting string."))
+        }
+    }
+}
+
+pub struct Source<'a> {
     file: &'a SourceFile,
     segmenter: Segmenter,
     seg_pos: usize,
     lookahead: VecDeque<LexToken<'a>>,
 }
 
-impl<'a> NewSource<'a> {
+impl<'a> Source<'a> {
     pub fn new_default(file: &'a SourceFile) -> Self {
         Self::new(file, Syntax::default())
     }
@@ -693,7 +710,7 @@ mod new_lexer_tests {
 
     use crate::macros::MacroSet;
 
-    use super::{NewSource, SourceFile};
+    use super::{Source, SourceFile};
 
     #[test]
     fn test() {
@@ -709,7 +726,7 @@ CROSSTABS VARIABLES X (1,7) Y (1,7) /TABLES X BY Y.
             Some(String::from("crosstabs.sps")),
             UTF_8,
         );
-        let mut source = NewSource::new_default(&file);
+        let mut source = Source::new_default(&file);
         while let Some(tokens) = source.read_command(&MacroSet::new()) {
             println!("{tokens:?}");
         }