///|
fn parse_int_token(
token : String,
line : Int,
label : String,
) -> Result[Int, PackError] {
try @string.parse_int(token) catch {
_ => Err(ParseError(line~, message="invalid \{label}: \{token}"))
} noraise {
value => Ok(value)
}
}
///|
fn parse_bool_token(token : String, line : Int) -> Result[Bool, PackError] {
match token {
"true" | "yes" | "on" | "1" => Ok(true)
"false" | "no" | "off" | "0" => Ok(false)
_ => Err(ParseError(line~, message="invalid boolean: \{token}"))
}
}
///|
fn parse_algorithm_token(
token : String,
line : Int,
) -> Result[Algorithm, PackError] {
match token {
"shelf" | "shelf-first-fit" => Ok(ShelfFirstFit)
"guillotine" | "guillotine-best-area" => Ok(GuillotineBestArea)
"skyline" | "skyline-bottom-left" => Ok(SkylineBottomLeft)
_ => Err(ParseError(line~, message="unknown algorithm: \{token}"))
}
}
///|
fn parse_sort_token(token : String, line : Int) -> Result[SortOrder, PackError] {
match token {
"input" => Ok(InputOrder)
"area" | "area-desc" => Ok(AreaDesc)
"height" | "height-desc" => Ok(HeightDesc)
"width" | "width-desc" => Ok(WidthDesc)
"perimeter" | "perimeter-desc" => Ok(PerimeterDesc)
_ => Err(ParseError(line~, message="unknown sort order: \{token}"))
}
}
///|
fn tokens_for_line(raw : StringView) -> Array[String] {
let body = match raw.split_once("#") {
Some((left, _)) => left.trim()
None => raw.trim()
}
let tokens : Array[String] = []
for part in body.split(" ") {
let p = part.trim()
if !p.is_empty() {
tokens.push(p.to_owned())
}
}
tokens
}
///|
/// Parse a small line-oriented specification.
///
/// Supported directives:
/// - `bin `
/// - `padding `
/// - `algorithm shelf|guillotine`
/// - `sort input|area|height|width|perimeter`
/// - `rotate true|false`
/// - `rect `
pub fn parse_spec(source : String) -> Result[Spec, PackError] {
let rects : Array[Rect] = []
let mut bin = Bin::{ width: 64, height: 64 }
let mut padding = Padding::{ outer: 0, inner: 0 }
let mut algorithm = ShelfFirstFit
let mut sort = AreaDesc
let mut allow_rotate = false
let mut line = 0
for raw in source.split("\n") {
line = line + 1
let tokens = tokens_for_line(raw)
if tokens.is_empty() {
continue
}
match tokens[0] {
"bin" => {
if tokens.length() != 3 {
return Err(ParseError(line~, message="bin expects width and height"))
}
let width = match parse_int_token(tokens[1], line, "bin width") {
Ok(v) => v
Err(err) => return Err(err)
}
let height = match parse_int_token(tokens[2], line, "bin height") {
Ok(v) => v
Err(err) => return Err(err)
}
bin = Bin::{ width, height }
}
"padding" => {
if tokens.length() != 3 {
return Err(
ParseError(line~, message="padding expects outer and inner"),
)
}
let outer = match parse_int_token(tokens[1], line, "outer padding") {
Ok(v) => v
Err(err) => return Err(err)
}
let inner = match parse_int_token(tokens[2], line, "inner padding") {
Ok(v) => v
Err(err) => return Err(err)
}
padding = Padding::{ outer, inner }
}
"algorithm" => {
if tokens.length() != 2 {
return Err(ParseError(line~, message="algorithm expects one value"))
}
algorithm = match parse_algorithm_token(tokens[1], line) {
Ok(v) => v
Err(err) => return Err(err)
}
}
"sort" => {
if tokens.length() != 2 {
return Err(ParseError(line~, message="sort expects one value"))
}
sort = match parse_sort_token(tokens[1], line) {
Ok(v) => v
Err(err) => return Err(err)
}
}
"rotate" => {
if tokens.length() != 2 {
return Err(ParseError(line~, message="rotate expects one boolean"))
}
allow_rotate = match parse_bool_token(tokens[1], line) {
Ok(v) => v
Err(err) => return Err(err)
}
}
"rect" => {
if tokens.length() != 4 {
return Err(ParseError(line~, message="rect expects id width height"))
}
let width = match parse_int_token(tokens[2], line, "rect width") {
Ok(v) => v
Err(err) => return Err(err)
}
let height = match parse_int_token(tokens[3], line, "rect height") {
Ok(v) => v
Err(err) => return Err(err)
}
rects.push(Rect::{ id: tokens[1], width, height })
}
other =>
return Err(ParseError(line~, message="unknown directive: \{other}"))
}
}
let options = PackOptions::{ bin, padding, sort, algorithm, allow_rotate }
match validate_input(rects, options) {
Ok(_) => Ok(Spec::{ options, rects })
Err(err) => Err(err)
}
}