Skip to content

Commit 8c5faee

Browse files
committed
Add parsing of FS command
1 parent fe1aa00 commit 8c5faee

File tree

3 files changed

+206
-2
lines changed

3 files changed

+206
-2
lines changed

cpp/src/GerberParserCppModule.cxx

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export namespace gerber {
5858
}
5959
};
6060

61+
// G-codes
62+
6163
class G01 : public Command {
6264
public:
6365
std::string getNodeName() const override {
@@ -169,6 +171,106 @@ export namespace gerber {
169171
}
170172
};
171173

174+
// Properties
175+
176+
class Zeros {
177+
public:
178+
enum Enum : uint8_t {
179+
SKIP_LEADING,
180+
SKIP_TRAILING
181+
};
182+
183+
Enum value;
184+
185+
private:
186+
Zeros() = delete;
187+
188+
public:
189+
Zeros(Enum value) :
190+
value(value) {}
191+
192+
static Enum from_string(const std::string_view& str) {
193+
if (str == "L") {
194+
return Enum::SKIP_LEADING;
195+
} else if (str == "T") {
196+
return Enum::SKIP_TRAILING;
197+
}
198+
throw std::invalid_argument("Invalid zeros");
199+
}
200+
201+
bool operator==(const Zeros& other) const {
202+
return value == other.value;
203+
}
204+
205+
bool operator==(const Enum& other) const {
206+
return value == other;
207+
}
208+
};
209+
210+
class CoordinateNotation {
211+
public:
212+
enum Enum : uint8_t {
213+
ABSOLUTE,
214+
INCREMENTAL
215+
};
216+
217+
Enum value;
218+
219+
private:
220+
CoordinateNotation() = delete;
221+
222+
public:
223+
CoordinateNotation(Enum value) :
224+
value(value) {}
225+
226+
static CoordinateNotation from_string(const std::string_view& str) {
227+
if (str == "A") {
228+
return Enum::ABSOLUTE;
229+
} else if (str == "I") {
230+
return Enum::INCREMENTAL;
231+
}
232+
throw std::invalid_argument("Invalid coordinate notation");
233+
}
234+
235+
bool operator==(const CoordinateNotation& other) const {
236+
return value == other.value;
237+
}
238+
239+
bool operator==(const Enum& other) const {
240+
return value == other;
241+
}
242+
};
243+
244+
class FS : public ExtendedCommand {
245+
public:
246+
Zeros zeros;
247+
CoordinateNotation coordinate_mode;
248+
249+
int x_integral;
250+
int x_decimal;
251+
252+
int y_integral;
253+
int y_decimal;
254+
255+
public:
256+
FS(const std::string_view& zeros,
257+
const std::string_view& coordinate_mode,
258+
int x_integral,
259+
int x_decimal,
260+
int y_integral,
261+
int y_decimal) :
262+
zeros(Zeros::from_string(zeros)),
263+
coordinate_mode(CoordinateNotation::from_string(coordinate_mode)),
264+
x_integral(x_integral),
265+
x_decimal(x_decimal),
266+
y_integral(y_integral),
267+
y_decimal(y_decimal) {}
268+
269+
std::string getNodeName() const override {
270+
return "FS";
271+
}
272+
};
273+
172274
class SyntaxError : public std::runtime_error {
173275
public:
174276
explicit SyntaxError(const std::string& message) :
@@ -181,15 +283,19 @@ export namespace gerber {
181283
std::string_view full_source;
182284
location_t global_index;
183285
// Regular expressions cache
286+
// G-codes
184287
std::regex g_code_regex;
185288
std::regex g04_regex;
289+
// Properties
290+
std::regex fs_regex;
186291

187292
Parser() :
188293
commands(0),
189294
full_source(""),
190295
global_index(0),
191296
g_code_regex("^[Gg]0*([1-9][0-9]*)\\*"),
192-
g04_regex("^[Gg]0*4([^%*]+)\\*") {}
297+
g04_regex("^[Gg]0*4([^%*]+)\\*"),
298+
fs_regex("^%FS([TL])([IA])X([0-9])([0-9])Y([0-9])([0-9])\\*%") {}
193299

194300
~Parser() {}
195301

@@ -205,14 +311,17 @@ export namespace gerber {
205311
global_index = 0;
206312

207313
while (global_index < (full_source.size() - 1)) {
208-
global_index += parse_global(source, global_index);
314+
global_index += parse_global(full_source, global_index);
209315
}
210316

211317
return File(std::move(commands));
212318
}
213319

214320
location_t parse_global(const std::string_view& source, const location_t& index) {
215321
const std::string_view sub_source{source.begin() + index, source.end()};
322+
if (sub_source.empty()) {
323+
return 0;
324+
}
216325

217326
switch (sub_source[0]) {
218327
case 'G':
@@ -225,6 +334,10 @@ export namespace gerber {
225334
return 1;
226335
break;
227336

337+
case '%':
338+
return parse_extended_command(sub_source, index);
339+
break;
340+
228341
default:
229342
break;
230343
}
@@ -324,5 +437,50 @@ export namespace gerber {
324437

325438
throw_syntax_error();
326439
}
440+
441+
offset_t parse_extended_command(const std::string_view& source, const location_t& index) {
442+
// Shortest possible extended command is probably %TD*%, so 5 chars at least.
443+
if (source.length() < 5) {
444+
throw_syntax_error();
445+
}
446+
447+
switch (source[1]) {
448+
case 'F':
449+
return parse_fs_command(source, index);
450+
break;
451+
452+
default:
453+
break;
454+
}
455+
456+
throw_syntax_error();
457+
}
458+
459+
offset_t parse_fs_command(const std::string_view& source, const location_t& index) {
460+
if (source.length() < 13) {
461+
throw_syntax_error();
462+
}
463+
std::cmatch match;
464+
465+
const auto result = std::regex_search(
466+
source.data(),
467+
source.data() + source.size(),
468+
match,
469+
fs_regex,
470+
std::regex_constants::match_continuous
471+
);
472+
if (result && match.size() == 7) {
473+
commands.push_back(std::make_shared<FS>(
474+
match[1].str(),
475+
match[2].str(),
476+
std::stoi(match[3].str()),
477+
std::stoi(match[4].str()),
478+
std::stoi(match[5].str()),
479+
std::stoi(match[6].str())
480+
));
481+
return match.length();
482+
}
483+
throw_syntax_error();
484+
}
327485
};
328486
} // namespace gerber

cpp/src/PythonModule.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <stdexcept>
12
import GerberParserCppModule;
23

34
#include <pybind11/pybind11.h>
@@ -17,6 +18,7 @@ PYBIND11_MODULE(gerber_parser, m) {
1718

1819
py::class_<gbr::Command>(m, "Command").def(py::init<>());
1920

21+
// G-codes
2022
py::class_<gbr::G01, std::shared_ptr<gbr::G01>>(m, "G01")
2123
.def(py::init<>())
2224
.def("__str__", &gbr::G01::getNodeName)
@@ -115,5 +117,28 @@ PYBIND11_MODULE(gerber_parser, m) {
115117
return visitor.attr("on_g91")(self);
116118
});
117119

120+
// Properties
121+
py::class_<gbr::FS, std::shared_ptr<gbr::FS>>(m, "FS")
122+
.def(py::init<const std::string_view&, const std::string_view&, int, int, int, int>())
123+
.def("__str__", &gbr::FS::getNodeName)
124+
.def_property_readonly(
125+
"zeros",
126+
[](const gbr::FS& self) {
127+
py::object pygerber_gerber_enums =
128+
py::module_::import("pygerber.gerber.ast.nodes.enums");
129+
130+
switch (self.zeros.value) {
131+
case gbr::Zeros::SKIP_LEADING:
132+
return pygerber_gerber_enums.attr("Zeros").attr("SKIP_LEADING");
133+
case gbr::Zeros::SKIP_TRAILING:
134+
return pygerber_gerber_enums.attr("Zeros").attr("SKIP_TRAILING");
135+
}
136+
throw std::runtime_error("Invalid 'zeros' value");
137+
}
138+
)
139+
.def("visit", [](py::object self, py::object visitor) {
140+
return visitor.attr("on_fs")(self);
141+
});
142+
118143
py::class_<gbr::Parser>(m, "GerberParser").def(py::init<>()).def("parse", &gbr::Parser::parse);
119144
}

cpp/test/main.test.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,25 @@ TEST_CASE("Parse G04 with content", "[g_codes]") {
6161

6262
REQUIRE(nodes.size() == 1);
6363
REQUIRE(nodes[0]->getNodeName() == "G04");
64+
}
65+
66+
TEST_CASE("Parse FS", "[properties]") {
67+
gerber::Parser parser;
68+
auto gerber_source = "%FSLAX26Y26*%";
69+
auto result = parser.parse(gerber_source);
70+
const auto& nodes = result.getNodes();
71+
72+
REQUIRE(nodes.size() == 1);
73+
auto fs = std::dynamic_pointer_cast<gerber::FS>(nodes[0]);
74+
75+
REQUIRE(fs->getNodeName() == "FS");
76+
77+
REQUIRE(fs->zeros == gerber::Zeros::SKIP_LEADING);
78+
REQUIRE(fs->coordinate_mode == gerber::CoordinateNotation::ABSOLUTE);
79+
80+
REQUIRE(fs->x_integral == 2);
81+
REQUIRE(fs->x_decimal == 6);
82+
83+
REQUIRE(fs->y_integral == 2);
84+
REQUIRE(fs->y_decimal == 6);
6485
}

0 commit comments

Comments
 (0)