Skip to content

Commit e181cac

Browse files
committed
Merge branch 'nicolube-master'
2 parents 65136b8 + d0ecca8 commit e181cac

3 files changed

Lines changed: 192 additions & 83 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gerber_parser"
3-
version = "0.3.1"
3+
version = "0.4.0"
44
documentation = "https://docs.rs/gerber-parser"
55
repository = "https://github.com/MakerPnP/gerber-parser"
66
homepage = "https://github.com/MakerPnP/gerber-parser"
@@ -28,9 +28,9 @@ edition = "2018"
2828
env_logger = ["dep:env_logger"]
2929

3030
[dependencies]
31-
gerber-types = "0.6.0"
32-
# currently un-released, using latest gerber_types with updates to support macro expressions
33-
#gerber-types = { git = "https://github.com/MakerPnP/gerber-types.git", rev = "d66f0d117f41243846beb29f92b721d37ed2eee4"}
31+
gerber-types = "0.7.0"
32+
# currently un-released, using latest gerber_types with changes from PR #51
33+
#gerber-types = { git = "https://github.com/MakerPnP/gerber-types.git", rev = "580e6afd666d2b3a98246a4d6531ae8ca0317de6"}
3434
#gerber-types = { path = "../gerber-types" }
3535

3636
# errors

src/parser.rs

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,7 @@ use crate::gerber_types::{
1010
};
1111
use crate::util::{partial_coordinates_from_gerber, partial_coordinates_offset_from_gerber};
1212
use crate::ParseError;
13-
use gerber_types::{
14-
ApertureBlock, AxisSelect, CommentContent, ComponentCharacteristics, ComponentDrill,
15-
ComponentMounting, ComponentOutline, CopperType, DrillFunction, DrillRouteType,
16-
ExtendedPosition, GenerationSoftware, GerberDate, IPC4761ViaProtection, Ident, ImageMirroring,
17-
ImageName, ImageOffset, ImagePolarity, ImageRotation, ImageScaling, Mirroring, Net,
18-
NonPlatedDrill, ObjectAttribute, Pin, PlatedDrill, Position, Profile, Rotation, Scaling,
19-
StandardComment, ThermalPrimitive, Uuid, VariableDefinition,
20-
};
13+
use gerber_types::{ApertureBlock, AttributeDeletionCriterion, AxisSelect, CommentContent, ComponentCharacteristics, ComponentDrill, ComponentMounting, ComponentOutline, CoordinateMode, CopperType, DrillFunction, DrillRouteType, ExtendedPosition, GenerationSoftware, GerberDate, IPC4761ViaProtection, Ident, ImageMirroring, ImageName, ImageOffset, ImagePolarity, ImageRotation, ImageScaling, Mirroring, Net, NonPlatedDrill, ObjectAttribute, Pin, PlatedDrill, Position, Profile, Rotation, Scaling, StandardComment, ThermalPrimitive, Uuid, VariableDefinition, ZeroOmission};
2114
use lazy_regex::*;
2215
use regex::Regex;
2316
use std::collections::HashMap;
@@ -45,7 +38,7 @@ static RE_LOAD_MIRRORING: Lazy<Regex> = lazy_regex!(r"%LM(?P<mirroring>N|X|Y|XY)
4538
// Note: scaling cannot be negative, or 0.
4639
static RE_LOAD_SCALING: Lazy<Regex> = lazy_regex!(r"%LS(?P<value>[0-9]+(?:\.[0-9]*)?)\*%");
4740
static RE_LOAD_ROTATION: Lazy<Regex> = lazy_regex!(r"%LR(?P<value>[+-]?[0-9]+(?:\.[0-9]*)?)\*%");
48-
static RE_FORMAT_SPEC: Lazy<Regex> = lazy_regex!(r"%FSLAX(.*)Y(.*)\*%");
41+
static RE_FORMAT_SPEC: Lazy<Regex> = lazy_regex!(r"%FS[LT][AI]X(.*)Y(.*)\*%");
4942

5043
/// https://regex101.com/r/YNnrmK/1
5144
static RE_APERTURE: Lazy<Regex> =
@@ -70,13 +63,17 @@ static RE_MACRO_VARIABLE: Lazy<Regex> =
7063
struct ParserContext<T: Read> {
7164
line_number: usize,
7265
lines: Lines<BufReader<T>>,
66+
aperture_attributes: HashMap<String, ApertureAttribute>,
67+
object_attributes: HashMap<String, ObjectAttribute>,
7368
}
7469

7570
impl<T: Read> ParserContext<T> {
7671
pub fn new(lines: Lines<BufReader<T>>) -> ParserContext<T> {
7772
ParserContext {
7873
line_number: 0,
7974
lines,
75+
aperture_attributes: HashMap::new(),
76+
object_attributes: HashMap::new(),
8077
}
8178
}
8279

@@ -233,7 +230,7 @@ fn parse_line<T: Read>(
233230
}
234231
'4' => {
235232
// G04
236-
commands.push(parse_comment(line))
233+
commands.push(parse_comment(line, parser_context))
237234
}
238235
_ => commands.push(Err(ContentError::UnknownCommand {})),
239236
}
@@ -351,15 +348,22 @@ fn parse_line<T: Read>(
351348
linechars = trim_attr_line(linechars)?;
352349

353350
parse_aperture_attribute(linechars.clone())
354-
.map(|attr| ExtendedCode::ApertureAttribute(attr).into())
351+
.map(|(name, attr)| {
352+
parser_context.aperture_attributes.insert(name, attr.clone());
353+
ExtendedCode::ApertureAttribute(attr).into()
354+
})
355355
}
356356
'O' => {
357357
linechars = trim_attr_line(linechars)?;
358-
359358
parse_object_attribute(linechars.clone())
360-
.map(|attr| ExtendedCode::ObjectAttribute(attr).into())
359+
.map(|(name, attr)| {
360+
parser_context.object_attributes.insert(name, attr.clone());
361+
ExtendedCode::ObjectAttribute(attr).into()
362+
})
361363
}
362-
'D' => parse_delete_attribute(linechars.clone()),
364+
'D' => parse_delete_attribute(linechars.clone(), parser_context)
365+
.map(ExtendedCode::DeleteAttribute)
366+
.map(Command::ExtendedCode),
363367
_ => Err(ContentError::UnknownCommand {}),
364368
},
365369
'S' => match linechars.next().ok_or(ContentError::UnknownCommand {})? {
@@ -702,7 +706,7 @@ fn parse_load_rotation(line: &str) -> Result<Command, ContentError> {
702706
///
703707
/// String: `G04 (String)*`
704708
/// Standard: `G04 #@! (Attribute)*`
705-
fn parse_comment(line: &str) -> Result<Command, ContentError> {
709+
fn parse_comment<T: Read>(line: &str, parser_context: &mut ParserContext<T>) -> Result<Command, ContentError> {
706710
match RE_COMMENT.captures(line) {
707711
Some(regmatch) => {
708712
let string_content = regmatch
@@ -724,9 +728,17 @@ fn parse_comment(line: &str) -> Result<Command, ContentError> {
724728
'F' => parse_file_attribute(comment_chars.clone())
725729
.map(StandardComment::FileAttribute),
726730
'A' => parse_aperture_attribute(comment_chars.clone())
727-
.map(StandardComment::ApertureAttribute),
731+
.map(|(name, attr)|{
732+
parser_context.aperture_attributes.insert(name, attr.clone());
733+
StandardComment::ApertureAttribute(attr)
734+
}),
728735
'O' => parse_object_attribute(comment_chars.clone())
729-
.map(StandardComment::ObjectAttribute),
736+
.map(|(name, attr)|{
737+
parser_context.object_attributes.insert(name, attr.clone());
738+
StandardComment::ObjectAttribute(attr)
739+
}),
740+
'D' => parse_comment_delete_attribute(comment_chars.clone(), parser_context)
741+
.map(StandardComment::DeleteAttribute),
730742
_ => Err(ContentError::UnknownCommand {}),
731743
},
732744
_ => Err(ContentError::UnknownCommand {}),
@@ -1202,13 +1214,34 @@ fn parse_units(line: &str, gerber_doc: &GerberDoc) -> Result<Unit, ContentError>
12021214
}
12031215

12041216
/// parse a Gerber format spec statement (e.g. '%FSLAX23Y23*%')
1217+
/// FS<L,T><A|I>X<int><dec>Y<int><dec>*%
1218+
/// <L,T> = Leading or Trailing (omitted = Leading)
1219+
/// <A|I> = Absolute or Incremental (omitted = Absolute)
1220+
/// <int> = number of integer digits (1-6)
12051221
fn parse_format_spec(line: &str, gerber_doc: &GerberDoc) -> Result<CoordinateFormat, ContentError> {
12061222
// Ensure that FS was not set before, which would imply two FS statements in the same doc
12071223
if gerber_doc.format_specification.is_some() {
12081224
Err(ContentError::TriedToFormatTwice {})
12091225
} else {
12101226
match RE_FORMAT_SPEC.captures(line) {
12111227
Some(regmatch) => {
1228+
let fs_str = regmatch.get(0)
1229+
.ok_or(ContentError::MissingRegexCapture {
1230+
regex: RE_FORMAT_SPEC.clone(),
1231+
capture_index: 0,
1232+
})?
1233+
.as_str();
1234+
1235+
let zero_omission = match fs_str.chars().nth(3) {
1236+
Some('T') => ZeroOmission::Trailing,
1237+
_ => ZeroOmission::Leading,
1238+
};
1239+
1240+
let mode = match fs_str.chars().nth(4) {
1241+
Some('I') => CoordinateMode::Incremental,
1242+
_ => CoordinateMode::Absolute,
1243+
};
1244+
12121245
let mut fs_chars = regmatch
12131246
.get(1)
12141247
.ok_or(ContentError::MissingRegexCapture {
@@ -1235,8 +1268,9 @@ fn parse_format_spec(line: &str, gerber_doc: &GerberDoc) -> Result<CoordinateFor
12351268
});
12361269
}
12371270

1238-
Ok(CoordinateFormat::new(integer, decimal))
1271+
Ok(CoordinateFormat::new(zero_omission, mode, integer, decimal))
12391272
}
1273+
12401274
None => Err(ContentError::NoRegexMatch {
12411275
regex: RE_FORMAT_SPEC.clone(),
12421276
}),
@@ -1492,31 +1526,47 @@ fn parse_command(command_str: &str) -> Result<Command, ContentError> {
14921526
}
14931527
}
14941528

1529+
1530+
fn format_coordinate_number(coord: &str, fs: &CoordinateFormat) -> Result<i64, ContentError> {
1531+
let len = fs.integer + fs.decimal;
1532+
Ok(match fs.zero_omission {
1533+
ZeroOmission::Leading => {
1534+
parse_coord::<i64>(coord)?
1535+
}
1536+
ZeroOmission::Trailing => {
1537+
let len = if coord.starts_with("-") { len + 1 } else { len };
1538+
let formated = format!("{:0<width$}", coord, width = len as usize);
1539+
parse_coord::<i64>(formated.as_str())?
1540+
}
1541+
})
1542+
}
1543+
14951544
// parse a Gerber interpolation command (e.g. 'X2000Y40000I300J50000D01*')
14961545
fn parse_interpolation(line: &str, gerber_doc: &GerberDoc) -> Result<Command, ContentError> {
1546+
14971547
match RE_INTERPOLATION.captures(line) {
14981548
Some(regmatch) => {
1549+
let fs = gerber_doc
1550+
.format_specification
1551+
.ok_or(ContentError::OperationBeforeFormat {})?;
1552+
14991553
let x_coord = regmatch
15001554
.get(1)
1501-
.map(|x| parse_coord::<i64>(x.as_str()))
1555+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15021556
.transpose()?;
15031557
let y_coord = regmatch
15041558
.get(2)
1505-
.map(|y| parse_coord::<i64>(y.as_str()))
1559+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15061560
.transpose()?;
15071561
let i_coord = regmatch
15081562
.get(3)
1509-
.map(|i| parse_coord::<i64>(i.as_str()))
1563+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15101564
.transpose()?;
15111565
let j_coord = regmatch
15121566
.get(4)
1513-
.map(|i| parse_coord::<i64>(i.as_str()))
1567+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15141568
.transpose()?;
15151569

1516-
let fs = gerber_doc
1517-
.format_specification
1518-
.ok_or(ContentError::OperationBeforeFormat {})?;
1519-
15201570
let coordinates =
15211571
partial_coordinates_from_gerber(x_coord, y_coord, fs).map_err(|error| {
15221572
ContentError::CoordinateFormatMismatch {
@@ -1553,19 +1603,19 @@ fn parse_move_or_flash(
15531603
) -> Result<Command, ContentError> {
15541604
match RE_MOVE_OR_FLASH.captures(line) {
15551605
Some(regmatch) => {
1606+
let fs = gerber_doc
1607+
.format_specification
1608+
.ok_or(ContentError::OperationBeforeFormat {})?;
1609+
15561610
let x_coord = regmatch
15571611
.get(1)
1558-
.map(|x| parse_coord::<i64>(x.as_str()))
1612+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15591613
.transpose()?;
15601614
let y_coord = regmatch
15611615
.get(2)
1562-
.map(|y| parse_coord::<i64>(y.as_str()))
1616+
.map(|x| format_coordinate_number(x.as_str(), &fs))
15631617
.transpose()?;
15641618

1565-
let fs = gerber_doc
1566-
.format_specification
1567-
.ok_or(ContentError::OperationBeforeFormat {})?;
1568-
15691619
let coords =
15701620
partial_coordinates_from_gerber(x_coord, y_coord, fs).map_err(|error| {
15711621
ContentError::CoordinateFormatMismatch {
@@ -1946,7 +1996,7 @@ fn split_first_str<'a>(slice: &'a [&'a str]) -> (&'a str, &'a [&'a str], usize)
19461996
/// Parse an Aperture Attribute (%TA.<AttributeName>[,<AttributeValue>]*%) into Command
19471997
///
19481998
/// ⚠️ This parsing statement needs a lot of tests and validation!
1949-
fn parse_aperture_attribute(line: Chars) -> Result<ApertureAttribute, ContentError> {
1999+
fn parse_aperture_attribute(partial_line: Chars) -> Result<(String, ApertureAttribute), ContentError> {
19502000
use ContentError::UnsupportedApertureAttribute;
19512001

19522002
build_enum_map!(IPC_MAP, IPC4761ViaProtection);
@@ -1978,13 +2028,13 @@ fn parse_aperture_attribute(line: Chars) -> Result<ApertureAttribute, ContentErr
19782028
})
19792029
}
19802030

1981-
let raw_line = line.as_str().to_string();
1982-
let attr_args = attr_args(line);
2031+
let raw_line = partial_line.as_str().to_string();
2032+
let attr_args = attr_args(partial_line);
19832033

19842034
log::trace!("TA ARGS: {:?}", attr_args);
19852035

19862036
let (first, remaining_args, remaining_len) = split_first_str(&attr_args);
1987-
match (first, remaining_args, remaining_len) {
2037+
let attr = match (first, remaining_args, remaining_len) {
19882038
(".AperFunction", remaining_args, len) if len >= 1 => {
19892039
let (first, remaining_args, remaining_len) = split_first_str(remaining_args);
19902040
Ok(ApertureAttribute::ApertureFunction(
@@ -2088,13 +2138,15 @@ fn parse_aperture_attribute(line: Chars) -> Result<ApertureAttribute, ContentErr
20882138
name: arg.to_string(),
20892139
values: args.iter().map(|v| v.to_string()).collect(),
20902140
}),
2091-
}
2141+
};
2142+
2143+
attr.map(|it|(first.to_string(), it))
20922144
}
20932145

20942146
/// Parse an Object Attribute (%TO.<AttributeName>[,<AttributeValue>]*%) into Command
20952147
///
20962148
/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage!
2097-
fn parse_object_attribute(line: Chars) -> Result<ObjectAttribute, ContentError> {
2149+
fn parse_object_attribute(partial_line: Chars) -> Result<(String, ObjectAttribute), ContentError> {
20982150
macro_rules! parse_cc_decimal {
20992151
($cc:ident, $value:expr) => {{
21002152
let decimal = $value
@@ -2113,13 +2165,13 @@ fn parse_object_attribute(line: Chars) -> Result<ObjectAttribute, ContentError>
21132165
}};
21142166
}
21152167

2116-
let attr_args = attr_args(line);
2168+
let attr_args = attr_args(partial_line);
21172169

21182170
log::trace!("TO ARGS: {:?}", attr_args);
21192171

21202172
let (first, remaining_args, remaining_len) = split_first_str(&attr_args);
21212173

2122-
match (first, remaining_args, remaining_len) {
2174+
let attr = match (first, remaining_args, remaining_len) {
21232175
(".N", args, len) if len >= 1 => {
21242176
// See 2024.05 - 5.6.13 ".N (Net)" "
21252177
let first = args.first().unwrap();
@@ -2180,21 +2232,37 @@ fn parse_object_attribute(line: Chars) -> Result<ObjectAttribute, ContentError>
21802232
name: arg.to_string(),
21812233
values: args.iter().map(|v| v.to_string()).collect(),
21822234
}),
2183-
}
2235+
};
2236+
2237+
attr.map(|it|(first.to_string(), it))
21842238
}
21852239

2186-
fn parse_delete_attribute(line: Chars) -> Result<Command, ContentError> {
2187-
let raw_line = line.as_str().to_string();
2240+
fn parse_delete_attribute<T: Read>(line: Chars, parser_context: &mut ParserContext<T>) -> Result<AttributeDeletionCriterion, ContentError> {
21882241
let line = trim_attr_line(line)?;
2189-
let attr_args = attr_args(line);
2242+
let value = line.as_str().to_string();
21902243

2191-
if attr_args.len() == 1 {
2192-
Ok(ExtendedCode::DeleteAttribute(attr_args[0].to_string()).into())
2193-
} else {
2194-
Err(ContentError::InvalidDeleteAttribute {
2195-
delete_attribute: raw_line,
2196-
})
2244+
parse_delete_attribute_inner(parser_context, value)
2245+
}
2246+
2247+
fn parse_comment_delete_attribute<T: Read>(comment: Chars, parser_context: &mut ParserContext<T>) -> Result<AttributeDeletionCriterion, ContentError> {
2248+
let value = comment.as_str().trim().to_string();
2249+
parse_delete_attribute_inner(parser_context, value)
2250+
}
2251+
2252+
fn parse_delete_attribute_inner<T: Read>(parser_context: &mut ParserContext<T>, value: String) -> Result<AttributeDeletionCriterion, ContentError> {
2253+
if value.is_empty() {
2254+
return Ok(AttributeDeletionCriterion::AllApertureAndObjectAttributes)
2255+
}
2256+
if parser_context.aperture_attributes.contains_key(&value) {
2257+
parser_context.aperture_attributes.remove(&value);
2258+
return Ok(AttributeDeletionCriterion::SingleApertureAttribute(value))
21972259
}
2260+
if parser_context.object_attributes.contains_key(&value) {
2261+
parser_context.object_attributes.remove(&value);
2262+
return Ok(AttributeDeletionCriterion::SingleObjectAttribute(value))
2263+
}
2264+
2265+
Err(ContentError::InvalidDeleteAttribute { delete_attribute: value })
21982266
}
21992267

22002268
/// Split the line by commas and convert to a vector of strings
@@ -2303,7 +2371,7 @@ fn parse_macro_integer(value: &str) -> Result<MacroInteger, ContentError> {
23032371
}
23042372
}
23052373

2306-
fn attr_args(partial_line: Chars) -> Vec<&str> {
2374+
fn attr_args(partial_line: Chars<'_>) -> Vec<&str> {
23072375
partial_line
23082376
.as_str()
23092377
.split(',')
@@ -2322,7 +2390,6 @@ fn trim_attr_line(mut partial_line: Chars) -> Result<Chars, ContentError> {
23222390
}),
23232391
}
23242392
}
2325-
23262393
#[cfg(test)]
23272394
mod attr_args_tests {
23282395
use super::*;

0 commit comments

Comments
 (0)