From 6029c4ea67def5e8a7060fe5a0a54e5a14a79ebc Mon Sep 17 00:00:00 2001 From: LilyRose2798 Date: Thu, 24 Jul 2025 21:33:52 +1000 Subject: [PATCH] Fix remaining legacy parsing issues --- src/kicad_sexpr/parse.gleam | 145 ++++++++++++++--------- src/kicad_sexpr/token.gleam | 230 ++++++++++++++++++++++++------------ test/kicad_sexpr_test.gleam | 48 ++++---- 3 files changed, 269 insertions(+), 154 deletions(-) diff --git a/src/kicad_sexpr/parse.gleam b/src/kicad_sexpr/parse.gleam index e9805cb..816c060 100644 --- a/src/kicad_sexpr/parse.gleam +++ b/src/kicad_sexpr/parse.gleam @@ -1,3 +1,4 @@ +import gleam/bit_array import gleam/float import gleam/int import gleam/list @@ -187,7 +188,10 @@ fn do_string(source: BitArray, acc: List(UtfCodepoint)) -> Parsed(String) { <<92, 114, rest:bits>> -> do_string(rest, [utf_codepoint_unsafe(13), ..acc]) <<92, cp:utf8_codepoint, rest:bits>> -> do_string(rest, [cp, ..acc]) <> -> do_string(rest, [cp, ..acc]) - source -> Error(InvalidUtf8Character(source)) + source -> + Error(InvalidUtf8Character( + source |> bit_array.slice(0, 4) |> result.unwrap(source), + )) } } @@ -195,6 +199,7 @@ type ParsedType { ParsedInt ParsedFloat ParsedName + ParsedString } fn do_name_number( @@ -207,7 +212,7 @@ fn do_name_number( rest, #([utf_codepoint_unsafe(i), ..cps], case cps { [] -> parsed_type - _ -> ParsedName + _ -> ParsedString }), ) <<46 as i, rest:bits>>, #(cps, parsed_type) -> @@ -215,7 +220,7 @@ fn do_name_number( rest, #([utf_codepoint_unsafe(i), ..cps], case parsed_type { ParsedInt -> ParsedFloat - _ -> ParsedName + _ -> ParsedString }), ) <<48 as i, rest:bits>>, #(cps, parsed_type) @@ -229,62 +234,77 @@ fn do_name_number( | <<56 as i, rest:bits>>, #(cps, parsed_type) | <<57 as i, rest:bits>>, #(cps, parsed_type) -> do_name_number(rest, #([utf_codepoint_unsafe(i), ..cps], parsed_type)) - <<65 as i, rest:bits>>, #(cps, _) - | <<66 as i, rest:bits>>, #(cps, _) - | <<67 as i, rest:bits>>, #(cps, _) - | <<68 as i, rest:bits>>, #(cps, _) - | <<69 as i, rest:bits>>, #(cps, _) - | <<70 as i, rest:bits>>, #(cps, _) - | <<71 as i, rest:bits>>, #(cps, _) - | <<72 as i, rest:bits>>, #(cps, _) - | <<73 as i, rest:bits>>, #(cps, _) - | <<74 as i, rest:bits>>, #(cps, _) - | <<75 as i, rest:bits>>, #(cps, _) - | <<76 as i, rest:bits>>, #(cps, _) - | <<77 as i, rest:bits>>, #(cps, _) - | <<78 as i, rest:bits>>, #(cps, _) - | <<79 as i, rest:bits>>, #(cps, _) - | <<80 as i, rest:bits>>, #(cps, _) - | <<81 as i, rest:bits>>, #(cps, _) - | <<82 as i, rest:bits>>, #(cps, _) - | <<83 as i, rest:bits>>, #(cps, _) - | <<84 as i, rest:bits>>, #(cps, _) - | <<85 as i, rest:bits>>, #(cps, _) - | <<86 as i, rest:bits>>, #(cps, _) - | <<87 as i, rest:bits>>, #(cps, _) - | <<88 as i, rest:bits>>, #(cps, _) - | <<89 as i, rest:bits>>, #(cps, _) - | <<90 as i, rest:bits>>, #(cps, _) - | <<97 as i, rest:bits>>, #(cps, _) - | <<98 as i, rest:bits>>, #(cps, _) - | <<99 as i, rest:bits>>, #(cps, _) - | <<100 as i, rest:bits>>, #(cps, _) - | <<101 as i, rest:bits>>, #(cps, _) - | <<102 as i, rest:bits>>, #(cps, _) - | <<103 as i, rest:bits>>, #(cps, _) - | <<104 as i, rest:bits>>, #(cps, _) - | <<105 as i, rest:bits>>, #(cps, _) - | <<106 as i, rest:bits>>, #(cps, _) - | <<107 as i, rest:bits>>, #(cps, _) - | <<108 as i, rest:bits>>, #(cps, _) - | <<109 as i, rest:bits>>, #(cps, _) - | <<110 as i, rest:bits>>, #(cps, _) - | <<111 as i, rest:bits>>, #(cps, _) - | <<112 as i, rest:bits>>, #(cps, _) - | <<113 as i, rest:bits>>, #(cps, _) - | <<114 as i, rest:bits>>, #(cps, _) - | <<115 as i, rest:bits>>, #(cps, _) - | <<116 as i, rest:bits>>, #(cps, _) - | <<117 as i, rest:bits>>, #(cps, _) - | <<118 as i, rest:bits>>, #(cps, _) - | <<119 as i, rest:bits>>, #(cps, _) - | <<120 as i, rest:bits>>, #(cps, _) - | <<121 as i, rest:bits>>, #(cps, _) - | <<122 as i, rest:bits>>, #(cps, _) - | <<95 as i, rest:bits>>, #(cps, _) - | <<42 as i, rest:bits>>, #(cps, _) - -> do_name_number(rest, #([utf_codepoint_unsafe(i), ..cps], ParsedName)) - source, #(cps, parsed_type) -> { + <<65 as i, rest:bits>>, #(cps, parsed_type) + | <<66 as i, rest:bits>>, #(cps, parsed_type) + | <<67 as i, rest:bits>>, #(cps, parsed_type) + | <<68 as i, rest:bits>>, #(cps, parsed_type) + | <<69 as i, rest:bits>>, #(cps, parsed_type) + | <<70 as i, rest:bits>>, #(cps, parsed_type) + | <<71 as i, rest:bits>>, #(cps, parsed_type) + | <<72 as i, rest:bits>>, #(cps, parsed_type) + | <<73 as i, rest:bits>>, #(cps, parsed_type) + | <<74 as i, rest:bits>>, #(cps, parsed_type) + | <<75 as i, rest:bits>>, #(cps, parsed_type) + | <<76 as i, rest:bits>>, #(cps, parsed_type) + | <<77 as i, rest:bits>>, #(cps, parsed_type) + | <<78 as i, rest:bits>>, #(cps, parsed_type) + | <<79 as i, rest:bits>>, #(cps, parsed_type) + | <<80 as i, rest:bits>>, #(cps, parsed_type) + | <<81 as i, rest:bits>>, #(cps, parsed_type) + | <<82 as i, rest:bits>>, #(cps, parsed_type) + | <<83 as i, rest:bits>>, #(cps, parsed_type) + | <<84 as i, rest:bits>>, #(cps, parsed_type) + | <<85 as i, rest:bits>>, #(cps, parsed_type) + | <<86 as i, rest:bits>>, #(cps, parsed_type) + | <<87 as i, rest:bits>>, #(cps, parsed_type) + | <<88 as i, rest:bits>>, #(cps, parsed_type) + | <<89 as i, rest:bits>>, #(cps, parsed_type) + | <<90 as i, rest:bits>>, #(cps, parsed_type) + | <<97 as i, rest:bits>>, #(cps, parsed_type) + | <<98 as i, rest:bits>>, #(cps, parsed_type) + | <<99 as i, rest:bits>>, #(cps, parsed_type) + | <<100 as i, rest:bits>>, #(cps, parsed_type) + | <<101 as i, rest:bits>>, #(cps, parsed_type) + | <<102 as i, rest:bits>>, #(cps, parsed_type) + | <<103 as i, rest:bits>>, #(cps, parsed_type) + | <<104 as i, rest:bits>>, #(cps, parsed_type) + | <<105 as i, rest:bits>>, #(cps, parsed_type) + | <<106 as i, rest:bits>>, #(cps, parsed_type) + | <<107 as i, rest:bits>>, #(cps, parsed_type) + | <<108 as i, rest:bits>>, #(cps, parsed_type) + | <<109 as i, rest:bits>>, #(cps, parsed_type) + | <<110 as i, rest:bits>>, #(cps, parsed_type) + | <<111 as i, rest:bits>>, #(cps, parsed_type) + | <<112 as i, rest:bits>>, #(cps, parsed_type) + | <<113 as i, rest:bits>>, #(cps, parsed_type) + | <<114 as i, rest:bits>>, #(cps, parsed_type) + | <<115 as i, rest:bits>>, #(cps, parsed_type) + | <<116 as i, rest:bits>>, #(cps, parsed_type) + | <<117 as i, rest:bits>>, #(cps, parsed_type) + | <<118 as i, rest:bits>>, #(cps, parsed_type) + | <<119 as i, rest:bits>>, #(cps, parsed_type) + | <<120 as i, rest:bits>>, #(cps, parsed_type) + | <<121 as i, rest:bits>>, #(cps, parsed_type) + | <<122 as i, rest:bits>>, #(cps, parsed_type) + | <<95 as i, rest:bits>>, #(cps, parsed_type) + -> + do_name_number( + rest, + #([utf_codepoint_unsafe(i), ..cps], case parsed_type { + ParsedInt | ParsedFloat -> ParsedName + x -> x + }), + ) + <<32, source:bits>>, #(cps, parsed_type) + | <<9, source:bits>>, #(cps, parsed_type) + | <<10, source:bits>>, #(cps, parsed_type) + | <<11, source:bits>>, #(cps, parsed_type) + | <<12, source:bits>>, #(cps, parsed_type) + | <<13, source:bits>>, #(cps, parsed_type) + | <<34, _:bits>> as source, #(cps, parsed_type) + | <<40, _:bits>> as source, #(cps, parsed_type) + | <<41, _:bits>> as source, #(cps, parsed_type) + -> { let str = cps |> list.reverse |> string.from_utf_codepoints case parsed_type { ParsedInt -> @@ -298,7 +318,14 @@ fn do_name_number( Error(Nil) -> Ok(#(Name(str), source)) } ParsedName -> Ok(#(Name(str), source)) + ParsedString -> Ok(#(String(str), source)) } } + <>, #(cps, _) -> + do_name_number(rest, #([cp, ..cps], ParsedString)) + source, #(_, _) -> + Error(InvalidUtf8Character( + source |> bit_array.slice(0, 4) |> result.unwrap(source), + )) } } diff --git a/src/kicad_sexpr/token.gleam b/src/kicad_sexpr/token.gleam index fdf59d3..345d937 100644 --- a/src/kicad_sexpr/token.gleam +++ b/src/kicad_sexpr/token.gleam @@ -1,5 +1,5 @@ import gleam/list -import gleam/option.{type Option} +import gleam/option.{type Option, None, Some} import gleam/pair import kicad_sexpr/decode.{type Decoder, type NextFn} @@ -19,7 +19,7 @@ fn allowed(then next: NextFn(Bool, a)) -> Decoder(a) { decode.enum(with: [#("allowed", True), #("not_allowed", False)], then: next) } -fn fill_flag(then next: NextFn(Bool, a)) -> Decoder(a) { +fn filled(then next: NextFn(Bool, a)) -> Decoder(a) { decode.token_wrapper(named: "fill", with: yes_no, then: next) } @@ -52,7 +52,11 @@ fn margins(then next: NextFn(#(Float, Float, Float, Float), a)) -> Decoder(a) { } fn hide(then next: NextFn(Bool, a)) -> Decoder(a) { - decode.token_wrapper(named: "hide", with: yes_no, then: next) + decode.one_of( + decode.token_wrapper(named: "hide", with: yes_no, then: _), + or: [decode.flag("hide", then: _)], + then: next, + ) } fn custom_xy(named name: String, then next: NextFn(XY, a)) -> Decoder(a) { @@ -65,14 +69,19 @@ fn custom_xy(named name: String, then next: NextFn(XY, a)) -> Decoder(a) { fn layers(then next: NextFn(List(Layer), a)) -> Decoder(a) { decode.token(named: "layers", then: next, with: { - use layers <- decode.list(decode.name_or_string) + use layers <- decode.list(decode.name_string) let layers = list.map(layers, Layer) decode.success(layers) }) } pub type PositionIdentifier { - PositionIdentifier(x: Float, y: Float, angle: Option(Float)) + PositionIdentifier( + x: Float, + y: Float, + angle: Option(Float), + locked: Option(Bool), + ) } pub fn position_identifier( @@ -82,7 +91,11 @@ pub fn position_identifier( use x <- decode.float() use y <- decode.float() use angle <- decode.optional(decode.float) - decode.success(PositionIdentifier(x:, y:, angle:)) + use locked <- decode.optional(decode.enum( + [#("locked", True), #("unlocked", False)], + then: _, + )) + decode.success(PositionIdentifier(x:, y:, angle:, locked:)) }) } @@ -257,19 +270,21 @@ pub fn stroke(then next: NextFn(Stroke, a)) -> Decoder(a) { pub type FillType { NoFill + SolidFillType OutlineFillType BackgroundFillType } pub fn fill_type(then next: NextFn(FillType, a)) -> Decoder(a) { - decode.token(named: "type", then: next, with: { - use value <- decode.enum([ + decode.enum( + [ #("none", NoFill), + #("solid", SolidFillType), #("outline", OutlineFillType), #("background", BackgroundFillType), - ]) - decode.success(value) - }) + ], + then: next, + ) } pub type Fill { @@ -278,7 +293,10 @@ pub type Fill { pub fn fill(then next: NextFn(Fill, a)) -> Decoder(a) { decode.token(named: "fill", then: next, with: { - use type_ <- fill_type() + use type_ <- decode.one_of( + decode.token_wrapper(named: "type", with: fill_type, then: _), + or: [fill_type], + ) decode.success(Fill(type_:)) }) } @@ -315,14 +333,14 @@ pub fn font(then next: NextFn(Font, a)) -> Decoder(a) { )) use size <- size() use thickness <- decode.optional(thickness) - use bold <- decode.optional(decode.token_wrapper( - named: "bold", - with: yes_no, + use bold <- decode.optional(decode.one_of( + decode.token_wrapper(named: "bold", with: yes_no, then: _), + or: [decode.flag("bold", then: _)], then: _, )) - use italic <- decode.optional(decode.token_wrapper( - named: "italic", - with: yes_no, + use italic <- decode.optional(decode.one_of( + decode.token_wrapper(named: "italic", with: yes_no, then: _), + or: [decode.flag("italic", then: _)], then: _, )) use line_spacing <- decode.optional(decode.token_wrapper( @@ -497,7 +515,7 @@ pub type Timestamp { pub fn timestamp(then next: NextFn(Timestamp, a)) -> Decoder(a) { decode.token(named: "tstamp", then: next, with: { - use ts <- decode.name_string() + use ts <- decode.string() decode.success(Timestamp(ts:)) }) } @@ -508,7 +526,7 @@ pub type Layer { pub fn layer(then next: NextFn(Layer, a)) -> Decoder(a) { decode.token(named: "layer", then: next, with: { - use layer <- decode.name_or_string() + use layer <- decode.name_string() decode.success(Layer(layer:)) }) } @@ -671,7 +689,7 @@ pub type FootprintText { pub fn footprint_text(then next: NextFn(FootprintText, a)) -> Decoder(a) { decode.token(named: "fp_text", then: next, with: { use type_ <- footprint_text_type() - use text <- decode.string() + use text <- decode.name_string() use position <- position_identifier() use unlocked <- decode.optional(decode.token_wrapper( named: "unlocked", @@ -734,7 +752,7 @@ pub fn footprint_text_box(then next: NextFn(FootprintTextBox, a)) -> Decoder(a) then: _, )) use stroke <- decode.optional(stroke) - let render_cache = option.None + let render_cache = None use timestamp <- decode.optional(timestamp) decode.success(FootprintTextBox( locked:, @@ -796,8 +814,10 @@ pub type FootprintRectangle { start: XY, end: XY, stroke: Option(Stroke), - fill: Option(Bool), + filled: Option(Bool), layer: Layer, + fill: Option(Fill), + width: Option(Float), locked: Bool, uuid: Option(Uuid), timestamp: Option(Timestamp), @@ -811,8 +831,12 @@ pub fn footprint_rectangle( use start <- custom_xy("start") use end <- custom_xy("end") use stroke <- decode.optional(stroke) - use fill <- decode.optional(fill_flag) + use filled <- decode.optional(filled) use layer <- layer() + use fill_a <- decode.optional(fill) + use width <- decode.optional(width) + use fill_b <- decode.optional(fill) + let fill = fill_a |> option.or(fill_b) use locked <- decode.flag("locked") use uuid <- decode.optional(uuid) use timestamp <- decode.optional(timestamp) @@ -820,8 +844,10 @@ pub fn footprint_rectangle( start:, end:, stroke:, - fill:, + filled:, layer:, + fill:, + width:, locked:, uuid:, timestamp:, @@ -834,8 +860,10 @@ pub type FootprintCircle { center: XY, end: XY, stroke: Option(Stroke), - fill: Option(Bool), + filled: Option(Bool), layer: Layer, + fill: Option(Fill), + width: Option(Float), locked: Bool, uuid: Option(Uuid), timestamp: Option(Timestamp), @@ -847,8 +875,12 @@ pub fn footprint_circle(then next: NextFn(FootprintCircle, a)) -> Decoder(a) { use center <- custom_xy("center") use end <- custom_xy("end") use stroke <- decode.optional(stroke) - use fill <- decode.optional(fill_flag) + use filled <- decode.optional(filled) use layer <- layer() + use fill_a <- decode.optional(fill) + use width <- decode.optional(width) + use fill_b <- decode.optional(fill) + let fill = fill_a |> option.or(fill_b) use locked <- decode.flag("locked") use uuid <- decode.optional(uuid) use timestamp <- decode.optional(timestamp) @@ -856,8 +888,10 @@ pub fn footprint_circle(then next: NextFn(FootprintCircle, a)) -> Decoder(a) { center:, end:, stroke:, - fill:, + filled:, layer:, + fill:, + width:, locked:, uuid:, timestamp:, @@ -872,6 +906,7 @@ pub type FootprintArc { end: XY, stroke: Option(Stroke), layer: Layer, + width: Option(Float), locked: Bool, uuid: Option(Uuid), timestamp: Option(Timestamp), @@ -885,6 +920,7 @@ pub fn footprint_arc(then next: NextFn(FootprintArc, a)) -> Decoder(a) { use end <- custom_xy("end") use stroke <- decode.optional(stroke) use layer <- layer() + use width <- decode.optional(width) use locked <- decode.flag("locked") use uuid <- decode.optional(uuid) use timestamp <- decode.optional(timestamp) @@ -894,6 +930,7 @@ pub fn footprint_arc(then next: NextFn(FootprintArc, a)) -> Decoder(a) { end:, stroke:, layer:, + width:, locked:, uuid:, timestamp:, @@ -905,8 +942,10 @@ pub type FootprintPolygon { FootprintPolygon( points: PolyPoints, stroke: Option(Stroke), - fill: Option(Bool), + filled: Option(Bool), layer: Layer, + fill: Option(Fill), + width: Option(Float), locked: Bool, uuid: Option(Uuid), timestamp: Option(Timestamp), @@ -917,16 +956,22 @@ pub fn footprint_polygon(then next: NextFn(FootprintPolygon, a)) -> Decoder(a) { decode.token(named: "fp_poly", then: next, with: { use points <- poly_points() use stroke <- decode.optional(stroke) - use fill <- decode.optional(fill_flag) + use filled <- decode.optional(filled) use layer <- layer() + use fill_a <- decode.optional(fill) + use width <- decode.optional(width) + use fill_b <- decode.optional(fill) + let fill = fill_a |> option.or(fill_b) use locked <- decode.flag("locked") use uuid <- decode.optional(uuid) use timestamp <- decode.optional(timestamp) decode.success(FootprintPolygon( points:, stroke:, - fill:, + filled:, layer:, + fill:, + width:, locked:, uuid:, timestamp:, @@ -939,6 +984,7 @@ pub type FootprintCurve { points: Points, stroke: Option(Stroke), layer: Layer, + width: Option(Float), locked: Bool, uuid: Option(Uuid), timestamp: Option(Timestamp), @@ -950,6 +996,7 @@ pub fn footprint_curve(then next: NextFn(FootprintCurve, a)) -> Decoder(a) { use points <- points() use stroke <- decode.optional(stroke) use layer <- layer() + use width <- decode.optional(width) use locked <- decode.flag("locked") use uuid <- decode.optional(uuid) use timestamp <- decode.optional(timestamp) @@ -957,6 +1004,7 @@ pub fn footprint_curve(then next: NextFn(FootprintCurve, a)) -> Decoder(a) { points:, stroke:, layer:, + width:, locked:, uuid:, timestamp:, @@ -1334,7 +1382,7 @@ pub fn graphical_text_box(then next: NextFn(GraphicalTextBox, a)) -> Decoder(a) use uuid <- decode.optional(uuid) use effects <- effects() use stroke <- decode.optional(stroke) - let render_cache = option.None + let render_cache = None decode.success(GraphicalTextBox( locked:, text:, @@ -1393,7 +1441,7 @@ pub fn graphical_rectangle( use end <- custom_xy("end") use layer <- decode.optional(layer) use width <- width() - use fill <- decode.optional(fill_flag) + use fill <- decode.optional(filled) use uuid <- decode.optional(uuid) decode.success(GraphicalRectangle( start:, @@ -1423,7 +1471,7 @@ pub fn graphical_circle(then next: NextFn(GraphicalCircle, a)) -> Decoder(a) { use end <- custom_xy("end") use layer <- decode.optional(layer) use width <- width() - use fill <- decode.optional(fill_flag) + use fill <- decode.optional(filled) use uuid <- decode.optional(uuid) decode.success(GraphicalCircle(center:, end:, layer:, width:, fill:, uuid:)) }) @@ -1467,7 +1515,7 @@ pub fn graphical_polygon(then next: NextFn(GraphicalPolygon, a)) -> Decoder(a) { use points <- poly_points() use layer <- decode.optional(layer) use width <- width() - use fill <- decode.optional(fill_flag) + use fill <- decode.optional(filled) use uuid <- decode.optional(uuid) decode.success(GraphicalPolygon(points:, layer:, width:, fill:, uuid:)) }) @@ -1780,12 +1828,7 @@ pub fn symbol_pin(then next: NextFn(SymbolPin, a)) -> Decoder(a) { use graphical_style <- pin_graphical_style() use position <- position_identifier() use length <- decode.token_wrapper(named: "length", with: decode.float) - use hide_flag <- decode.flag("hide") - use hide <- - case hide_flag { - True -> decode.of(with: decode.success(option.Some(True)), then: _) - False -> decode.optional(hide, then: _) - } + use hide <- decode.optional(hide) use name <- pin_name() use number <- pin_number() use alternatives <- decode.list(pin_alternate) @@ -2005,7 +2048,7 @@ pub fn custom_pad_primitives( decode.token(named: "primitives", then: next, with: { use graphic_items <- decode.list(graphic_item) use width <- decode.optional(width) - use fill <- decode.optional(fill_flag) + use fill <- decode.optional(filled) decode.success(CustomPadPrimitives(graphic_items:, width:, fill:)) }) } @@ -2049,7 +2092,7 @@ pub type Pad { pub fn pad(then next: NextFn(Pad, a)) -> Decoder(a) { decode.token(named: "pad", then: next, with: { - use number <- decode.string() + use number <- decode.name_number_string() use type_ <- pad_type() use shape <- pad_shape() use position <- position_identifier() @@ -2065,14 +2108,16 @@ pub fn pad(then next: NextFn(Pad, a)) -> Decoder(a) { use layers <- layers() use remove_unused_layers <- decode.optional(decode.token_wrapper( named: "remove_unused_layers", - with: yes_no, + with: decode.optional(yes_no, then: _), then: _, )) + let remove_unused_layers = option.flatten(remove_unused_layers) use keep_end_layers <- decode.optional(decode.token_wrapper( named: "keep_end_layers", - with: yes_no, + with: decode.optional(yes_no, then: _), then: _, )) + let keep_end_layers = option.flatten(keep_end_layers) use roundrect_rratio <- decode.optional(decode.token_wrapper( named: "roundrect_rratio", with: decode.float, @@ -2485,6 +2530,7 @@ pub type Zone { net_name: String, layers: List(Layer), uuid: Option(Uuid), + timestamp: Option(Timestamp), name: Option(String), hatch: Hatch, priority: Option(Int), @@ -2506,6 +2552,7 @@ pub fn zone(then next: NextFn(Zone, a)) -> Decoder(a) { use net_name <- decode.token_wrapper(named: "net_name", with: decode.string) use layers <- decode.one_of(layer |> decode.map(list.wrap), or: [layers]) use uuid <- decode.optional(uuid) + use timestamp <- decode.optional(timestamp) use name <- decode.optional(decode.token_wrapper( named: "name", with: decode.string, @@ -2538,6 +2585,7 @@ pub fn zone(then next: NextFn(Zone, a)) -> Decoder(a) { net_name:, layers:, uuid:, + timestamp:, name:, hatch:, priority:, @@ -2585,7 +2633,10 @@ pub fn footprint_model(then next: NextFn(FootprintModel, a)) -> Decoder(a) { decode.token(named: "model", then: next, with: { use file <- decode.string() use hide <- decode.optional(hide) - use offset <- decode.token_wrapper(named: "offset", with: xyz) + use offset <- decode.one_of( + decode.token_wrapper(named: "offset", with: xyz, then: _), + or: [decode.token_wrapper(named: "at", with: xyz, then: _)], + ) use scale <- decode.token_wrapper(named: "scale", with: xyz) use rotate <- decode.token_wrapper(named: "rotate", with: xyz) decode.success(FootprintModel(file:, hide:, offset:, scale:, rotate:)) @@ -2635,7 +2686,7 @@ fn base_footprint(then next: NextFn(Footprint, a)) -> Decoder(a) { use layer <- layer() use tedit <- decode.optional(decode.token_wrapper( named: "tedit", - with: decode.name_or_string, + with: decode.name_number_string, then: _, )) use uuid <- decode.optional(uuid) @@ -2656,6 +2707,7 @@ fn base_footprint(then next: NextFn(Footprint, a)) -> Decoder(a) { with: decode.string, then: _, )) + use attributes_a <- decode.optional(footprint_attributes) use autoplace_cost_90 <- decode.optional(decode.token_wrapper( named: "autoplace_cost90", with: decode.int, @@ -2708,7 +2760,8 @@ fn base_footprint(then next: NextFn(Footprint, a)) -> Decoder(a) { with: decode.float, then: _, )) - use attributes <- decode.optional(footprint_attributes) + use attributes_b <- decode.optional(footprint_attributes) + let attributes = attributes_a |> option.or(attributes_b) use private_layers <- decode.optional(decode.token_wrapper( named: "private_layers", with: decode.list(decode.string, then: _), @@ -2719,8 +2772,10 @@ fn base_footprint(then next: NextFn(Footprint, a)) -> Decoder(a) { with: decode.list(decode.string, then: _), then: _, )) - use graphic_items <- decode.list(footprint_graphic_item) + use graphic_items_a <- decode.list(footprint_graphic_item) use pads <- decode.list(pad) + use graphic_items_b <- decode.list(footprint_graphic_item) + let graphic_items = list.append(graphic_items_a, graphic_items_b) use zones <- decode.list(zone) use groups <- decode.list(group) use embedded_fonts <- decode.optional(decode.token_wrapper( @@ -2771,35 +2826,64 @@ pub fn footprint(then next: NextFn(Footprint, a)) -> Decoder(a) { pub type FootprintFile { FootprintFile( name: String, - version: Int, - generator: String, + version: Option(Int), + generator: Option(String), generator_version: Option(String), footprint: Footprint, ) } pub fn footprint_file(then next: NextFn(FootprintFile, a)) -> Decoder(a) { - decode.token(named: "footprint", then: next, with: { - use name <- decode.string() - use version <- decode.token_wrapper(named: "version", with: decode.int) - use generator <- decode.token_wrapper( - named: "generator", - with: decode.name_or_string, - ) - use generator_version <- decode.optional(decode.token_wrapper( - named: "generator_version", - with: decode.string, + decode.one_of( + decode.token( + named: "footprint", + with: { + use name <- decode.string() + use version <- decode.token_wrapper(named: "version", with: decode.int) + let version = Some(version) + use generator <- decode.token_wrapper( + named: "generator", + with: decode.name_string, + ) + let generator = Some(generator) + use generator_version <- decode.optional(decode.token_wrapper( + named: "generator_version", + with: decode.string, + then: _, + )) + use footprint <- base_footprint() + decode.success(FootprintFile( + name:, + version:, + generator:, + generator_version:, + footprint:, + )) + }, then: _, - )) - use footprint <- base_footprint() - decode.success(FootprintFile( - name:, - version:, - generator:, - generator_version:, - footprint:, - )) - }) + ), + or: [ + decode.token( + named: "module", + with: { + use name <- decode.name_string() + let version = None + let generator = None + let generator_version = None + use footprint <- base_footprint() + decode.success(FootprintFile( + name:, + version:, + generator:, + generator_version:, + footprint:, + )) + }, + then: _, + ), + ], + then: next, + ) } pub type SymbolProperty { @@ -2863,7 +2947,7 @@ pub type Symbol { fn base_symbol(then next: NextFn(Symbol, a)) -> Decoder(a) { decode.of(then: next, with: { - use library_unit_id <- decode.string + use library_unit_id <- decode.string() use extends <- decode.optional(decode.token_wrapper( named: "extends", with: decode.string, @@ -2963,7 +3047,7 @@ pub fn symbol_library(then next: NextFn(SymbolLibrary, a)) -> Decoder(a) { use version <- decode.token_wrapper(named: "version", with: decode.int) use generator <- decode.token_wrapper( named: "generator", - with: decode.string, + with: decode.name_string, ) use generator_version <- decode.optional(decode.token_wrapper( named: "generator_version", diff --git a/test/kicad_sexpr_test.gleam b/test/kicad_sexpr_test.gleam index 6bbe26a..f139c15 100644 --- a/test/kicad_sexpr_test.gleam +++ b/test/kicad_sexpr_test.gleam @@ -2,8 +2,6 @@ import gleam/float import gleam/int import gleam/io import gleam/list - -// import gleam/pair import gleam/result import gleam/string import gleam/time/duration @@ -18,21 +16,19 @@ pub fn main() -> Nil { io.println("\nTesting Footprints") let assert Ok(footprint_files) = simplifile.get_files("/usr/share/kicad/footprints") - // test_read_parse_decode( - // footprint_files |> list.drop(0) |> list.split(10) |> pair.first, - // token.footprint_file, - // True, - // ) - // test_read_parse_decode( - // ["test_files/test3.kicad_mod"], - // token.footprint_file, - // True, - // ) - test_read_parse_decode(footprint_files, token.footprint_file, False) + // let footprint_files = footprint_files |> list.drop(0) |> list.take(5000) + // let footprint_files = footprint_files |> list.sample(1000) + // let footprint_files = ["test_files/test3.kicad_mod"] + test_read_parse_decode(footprint_files, token.footprint_file, True) + io.println("\nTesting Symbol Libraries") let assert Ok(symbol_libraries) = simplifile.get_files("/usr/share/kicad/symbols") - test_read_parse_decode(symbol_libraries, token.symbol_library, False) + // let symbol_libraries = symbol_libraries |> list.drop(0) |> list.take(20) + // let symbol_libraries = symbol_libraries |> list.sample(1000) + // let symbol_libraries = ["test_files/test3.kicad_mod"] + test_read_parse_decode(symbol_libraries, token.symbol_library, True) + gleeunit.main() } @@ -50,20 +46,23 @@ fn print_stats( title: String, successes: Int, total: Int, + num_len: Int, start_time: Timestamp, end_time: Timestamp, ) -> Nil { io.println( title - <> int.to_string(successes) + <> { int.to_string(successes) |> string.pad_start(num_len, "0") } <> "/" - <> int.to_string(total) + <> { int.to_string(total) |> string.pad_start(num_len, "0") } <> " (" - <> int.to_float(100 * successes) /. int.to_float(total) - |> float.to_precision(1) - |> float.to_string - |> string.pad_start(5, "0") - |> string.append("%") + <> { + int.to_float(100 * successes) /. int.to_float(total) + |> float.to_precision(1) + |> float.to_string + |> string.pad_start(5, "0") + |> string.append("%") + } <> ") in " <> time_taken_string(start_time, end_time), ) @@ -75,7 +74,9 @@ fn test_read_parse_decode( print_errors: Bool, ) -> Nil { let num_file_names = list.length(file_names) - io.println("Total: " <> int.to_string(num_file_names)) + let num_file_names_str = int.to_string(num_file_names) + let num_len = string.length(num_file_names_str) + io.println("Total: " <> num_file_names_str) let time_before_read = timestamp.system_time() let #(successfully_read, failed_to_read) = @@ -92,6 +93,7 @@ fn test_read_parse_decode( "Read: ", num_successfully_read, num_file_names, + num_len, time_before_read, time_after_read, ) @@ -121,6 +123,7 @@ fn test_read_parse_decode( "Parsed: ", num_successfully_parsed, num_successfully_read, + num_len, time_before_parse, time_after_parse, ) @@ -150,6 +153,7 @@ fn test_read_parse_decode( "Decoded: ", num_successfully_decoded, num_successfully_parsed, + num_len, time_before_decode, time_after_decode, )