#Self-Referential Struct in the Context of Parsed Zero-Copy Structure + Source

27 messages · Page 1 of 1 (latest)

gaunt rivet
#

I'm struggling to understand how to represent a self-referential structure. Ideally it'd be structured like this:

// A parsed node, 
enum Node<'src> {
  Parsed(...)
  /// Unknown node
  Unknown(&'src str)
}

// A group of nodes such as an object within the source
struct Group<'src> {
  nodes: &'src[Node<'src>]
}

// Overarching structure
struct Root {
  src: String
  nodes: Vec<Node<'src>>,
  groups: Vec<Group<'src>>
}

fn parse<'src>(src: &'src str) -> Vec<Node<'src>> {...}
fn group<'src>(nodes: &'src[Node]) -> Vec<&'src[Node]> {...}

impl Root {
  fn new(src: String) -> Self {
    let nodes = parse(&src);
    let groups = group(&nodes[..]);
    Self {
      src,
      nodes,
      groups,
    }
  }
}

For context:
My task is to parse a linear-structured language (G-Code), modify some parts and restructure it. One issue I have is I'm unable to fully parse the code as it's very non-standardised but the parts I care about are standardised, therefore provided things go out the same as they went in I'm good e.g.

; Start Block 1
G0 X50 Y20 ; G0 is standard
G0 X10 Y10
; End Block 1
; Start Block 2
G0 X10 Y120
M117 ; M117 isn't, but if it remains in the same position within the block it should be safe
G0 X80 Y10
; Start Block 2

I'd want to perform operations over the G0 commands, and reorder the blocks.

I've written a functioning parser in Nom, however I'm now struggling to fit this together and splitting the parsed file into chunks, Doing things as they are typed results in the compiler saying that both Root::src and Root::nodes don't live long enough when borrowed in parse and group.

Is my current method reasonable or should I structure things differently? How do I prove to the compiler that the underlying data lives long enough?

abstract coyote
gaunt rivet
#

Node is a big enum containing all possible commands, mostly simple things such as Move{x:f32, y:f32, z:f32}. The exception to this is some variants which reference back to the original source, such as object names or straight up unknown commands

#

Let me grab the source real quick

abstract coyote
#

just what is inside the ... in Parsed?

gaunt rivet
#
/// A command within G-Code, or a comment
#[derive(Debug, PartialEq)]
pub enum Node<'src> {
    /// G1, G0
    /// Unevaluated move command, None fields depend on previous nodes for state.
    MoveRaw {
        x: Option<f32>,
        y: Option<f32>,
        z: Option<f32>,
        e: Option<f32>,
        f: Option<f32>,
    },
    /// Move command that is absolute, E is mode dependant
    /// G1, G0
    MoveAbs {
        x: f32,
        y: f32,
        z: f32,
        /// Extrusion amount (mm)
        e: f32,
        /// Feedrate (units/minute)
        f: Option<f32>,
    },
    // TODO: Make spec compliant?
    /// G92
    SetPosition {
        e: f32,
    },
    // G4
    Dwell(Option<Duration>),
    /// G21
    SetUnits(Units),
    /// Leaves E intact
    /// G90
    SetRelative(bool),
    /// G28
    Home {
        x: bool,
        y: bool,
        z: bool,
        /// Bed mesh
        w: bool,
        /// Calibrate X and Y origin - Only MK3/s
        c: bool,
    },
    Comment(Comment<'src>),
    /// Any unrecognized gcode
    Misc(&'src str),
    // TODO:
    // G80 - Mesh-based Z probe
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum GroupType {
    SkirtBrim,
    // Removed extra variants for discord file limit
}

#[derive(Debug, PartialEq, Clone)]
pub enum Comment<'src> {
    /// ;TYPE:External perimeter
    GroupTag(GroupType),
    /// ;LAYER_CHANGE
    LayerChange,
    /// ;BEFORE_LAYER_CHANGE
    BeforeLayerChange,
    /// ;AFTER_LAYER_CHANGE
    AfterLayerChange,
    /// ;Z:0.4
    Z(f32),
    /// ;HEIGHT:0.2
    Height(f32),
    /// ; printing object Line2 id:1 copy 0
    ObjectTag {
        is_start: bool,
        name: &'src str,
        id: u32,
        copy: u32,
    },
    /// TODO
    Thumbnail,
    /// Everything else!
    Misc(&'src str),
}
#

Misc is the unknown, everything else is the parsed variant

abstract coyote
#

the only I can think of is if there's a variant in GroupType

#

that causes it

gaunt rivet
#

Grouptype is only markers, no enum fields

abstract coyote
#

can you send the error message?

gaunt rivet
#

Given the existence of crates such as ouroboros and yolk, it seems this isn't really representable without trickery, but I must admit my knowledge on advanced ownership is limited

#

Give me a sec

#

Bear with me as this is a little long-winded

#
/// A G-Code Source based upon a file
#[derive(Debug, PartialEq)]
pub struct FileSource<'src> {
    // TODO: Unload and reload string when needed? Perhaps take a hash too
    pub src: String,
    pub nodes: Vec<(usize, Node<'src>)>,
    pub start: &'src[(usize, Node<'src>)],
    // pub objects: Vec<Object<'src>>,
    pub path: Option<PathBuf>,
}

impl<'src> FileSource<'src> {
    pub fn from_path(path: PathBuf) -> Result<Self, SourceError> {
        let mut src = String::new();

        let mut file = File::open(&path)?;
        let size = file.read_to_string(&mut src)?;

        info!("Loading file {}: {}", path.display(), size);

        // TODO: Parse metadata
        let nodes = evaluate(parse(&src)).unwrap();

        let (_remainder, start) = take_until(&nodes[..], |(_, node)| {
            *node == Node::Comment(crate::Comment::LayerChange)
        })?;

        Ok(Self {
            src,
            nodes,
            start,
            // objects: Vec::new(),
            path: Some(path),
        })
    }
}

Here's the erroring code in question

#

Here's the signatures for parse and evaluate:

pub fn parse<'src>(src: &'src str) -> impl Iterator<Item = (usize, Node<'src>)>;
pub fn evaluate<'src>(nodes: impl Iterator<Item = (usize, Node<'src>)>) -> Result<Vec<(usize, Node<'src>)>, ()>
#

Or perhaps is my order of operations wrong here?

#

Should I construct my backing struct containing the source and base node list first, and then construct a second wrapper over this and the groups?

#

It complains nodes (inside from_path) is moved after borrow, which is correct, so should it be in its final position before performing the borrow operations, hence moving it into a second struct

#

Oh and all this data is immutable once created, forgot to say

#

Or should be

gaunt rivet
#

I think I solved it?

#

I've divided my structs up as mentioned into 3 structs:

struct Source<'this> {
  src: String,
  nodes: Vec<Node<'this>>
}

struct Group<'src> {
  groups: Vec<&'src [Node<'src>]>
}

struct Wrapper<'this> {
  source: Source,
  grousp: Group<'this>
}

And using Ouroboros I've marked the self-references as such and it compiles?

#

I guess time to write tests and see if it works

#

It'd be nice if I could figure out a way of not using Group, however initialising multiple values using the same intermediary data seems impossible for now, so it's a sacifice I have to make

abstract coyote
#

sorry, i got distracted
also idk the answer