///|
pub struct ParseLimits {
  max_lines_value : Int
  max_motions_value : Int
  max_words_per_line_value : Int
} derive(Eq, Debug)

///|
pub fn ParseLimits::standard() -> ParseLimits {
  {
    max_lines_value: 100000,
    max_motions_value: 1000000,
    max_words_per_line_value: 256,
  }
}

///|
pub fn ParseLimits::with_max_lines(
  self : ParseLimits,
  value : Int,
) -> ParseLimits {
  { ..self, max_lines_value: value }
}

///|
pub fn ParseLimits::with_max_motions(
  self : ParseLimits,
  value : Int,
) -> ParseLimits {
  { ..self, max_motions_value: value }
}

///|
pub fn ParseLimits::with_max_words_per_line(
  self : ParseLimits,
  value : Int,
) -> ParseLimits {
  { ..self, max_words_per_line_value: value }
}

///|
pub fn parse_gcode_with_limits(
  text : String,
  limits : ParseLimits,
) -> Result[GCodeProgram, GCodeError] {
  for line_number, raw in text.split("\n") {
    let line = clean_gcode_line(raw.to_owned())
    if line != "" && line[0].to_int() != 59 {
      if tokenize_gcode_line(line).length() > limits.max_words_per_line_value {
        return Err({
          code_value: InvalidWord,
          message_value: "word limit exceeded",
          location_value: SourceLocation::new(line_number + 1, 1),
        })
      }
    }
  }
  let program = match parse_gcode(text, max_lines=limits.max_lines_value) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if program.motion_count() > limits.max_motions_value {
    return Err({
      code_value: InvalidWord,
      message_value: "motion limit exceeded",
      location_value: SourceLocation::new(0, 0),
    })
  }
  Ok(program)
}