@tracespace/parser
Version:
Gerber and NC drill file parser
1,836 lines (1,610 loc) • 61.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.TracespaceParser = {}));
}(this, (function (exports) { 'use strict';
// common constants
// filetype constants
var GERBER = 'gerber';
var DRILL = 'drill'; // units constants
var MM = 'mm';
var IN = 'in'; // format constants
var LEADING = 'leading';
var TRAILING = 'trailing';
var ABSOLUTE = 'absolute';
var INCREMENTAL = 'incremental'; // tool constants
var CIRCLE = 'circle';
var RECTANGLE = 'rectangle';
var OBROUND = 'obround';
var POLYGON = 'polygon';
var MACRO_SHAPE = 'macroShape'; // macro primitive codes
var MACRO_CIRCLE = '1';
var MACRO_VECTOR_LINE = '20';
var MACRO_CENTER_LINE = '21';
var MACRO_OUTLINE = '4';
var MACRO_POLYGON = '5';
var MACRO_MOIRE = '6';
var MACRO_THERMAL = '7'; // drawing constants
var SHAPE = 'shape';
var MOVE = 'move';
var SEGMENT = 'segment';
var SLOT = 'slot'; // interpolation / routing constants
var LINE = 'line';
var CW_ARC = 'cwArc';
var CCW_ARC = 'ccwArc'; // quadrant mode
var SINGLE = 'single';
var MULTI = 'multi'; // load polarity
var DARK = 'dark';
var CLEAR = 'clear';
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var moo = createCommonjsModule(function (module) {
(function(root, factory) {
if ( module.exports) {
module.exports = factory();
} else {
root.moo = factory();
}
}(commonjsGlobal, function() {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var toString = Object.prototype.toString;
var hasSticky = typeof new RegExp().sticky === 'boolean';
/***************************************************************************/
function isRegExp(o) { return o && toString.call(o) === '[object RegExp]' }
function isObject(o) { return o && typeof o === 'object' && !isRegExp(o) && !Array.isArray(o) }
function reEscape(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
}
function reGroups(s) {
var re = new RegExp('|' + s);
return re.exec('').length - 1
}
function reCapture(s) {
return '(' + s + ')'
}
function reUnion(regexps) {
if (!regexps.length) return '(?!)'
var source = regexps.map(function(s) {
return "(?:" + s + ")"
}).join('|');
return "(?:" + source + ")"
}
function regexpOrLiteral(obj) {
if (typeof obj === 'string') {
return '(?:' + reEscape(obj) + ')'
} else if (isRegExp(obj)) {
// TODO: consider /u support
if (obj.ignoreCase) throw new Error('RegExp /i flag not allowed')
if (obj.global) throw new Error('RegExp /g flag is implied')
if (obj.sticky) throw new Error('RegExp /y flag is implied')
if (obj.multiline) throw new Error('RegExp /m flag is implied')
return obj.source
} else {
throw new Error('Not a pattern: ' + obj)
}
}
function objectToRules(object) {
var keys = Object.getOwnPropertyNames(object);
var result = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var thing = object[key];
var rules = [].concat(thing);
if (key === 'include') {
for (var j = 0; j < rules.length; j++) {
result.push({include: rules[j]});
}
continue
}
var match = [];
rules.forEach(function(rule) {
if (isObject(rule)) {
if (match.length) result.push(ruleOptions(key, match));
result.push(ruleOptions(key, rule));
match = [];
} else {
match.push(rule);
}
});
if (match.length) result.push(ruleOptions(key, match));
}
return result
}
function arrayToRules(array) {
var result = [];
for (var i = 0; i < array.length; i++) {
var obj = array[i];
if (obj.include) {
var include = [].concat(obj.include);
for (var j = 0; j < include.length; j++) {
result.push({include: include[j]});
}
continue
}
if (!obj.type) {
throw new Error('Rule has no type: ' + JSON.stringify(obj))
}
result.push(ruleOptions(obj.type, obj));
}
return result
}
function ruleOptions(type, obj) {
if (!isObject(obj)) {
obj = { match: obj };
}
if (obj.include) {
throw new Error('Matching rules cannot also include states')
}
// nb. error and fallback imply lineBreaks
var options = {
defaultType: type,
lineBreaks: !!obj.error || !!obj.fallback,
pop: false,
next: null,
push: null,
error: false,
fallback: false,
value: null,
type: null,
shouldThrow: false,
};
// Avoid Object.assign(), so we support IE9+
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
options[key] = obj[key];
}
}
// type transform cannot be a string
if (typeof options.type === 'string' && type !== options.type) {
throw new Error("Type transform cannot be a string (type '" + options.type + "' for token '" + type + "')")
}
// convert to array
var match = options.match;
options.match = Array.isArray(match) ? match : match ? [match] : [];
options.match.sort(function(a, b) {
return isRegExp(a) && isRegExp(b) ? 0
: isRegExp(b) ? -1 : isRegExp(a) ? +1 : b.length - a.length
});
return options
}
function toRules(spec) {
return Array.isArray(spec) ? arrayToRules(spec) : objectToRules(spec)
}
var defaultErrorRule = ruleOptions('error', {lineBreaks: true, shouldThrow: true});
function compileRules(rules, hasStates) {
var errorRule = null;
var fast = Object.create(null);
var fastAllowed = true;
var unicodeFlag = null;
var groups = [];
var parts = [];
// If there is a fallback rule, then disable fast matching
for (var i = 0; i < rules.length; i++) {
if (rules[i].fallback) {
fastAllowed = false;
}
}
for (var i = 0; i < rules.length; i++) {
var options = rules[i];
if (options.include) {
// all valid inclusions are removed by states() preprocessor
throw new Error('Inheritance is not allowed in stateless lexers')
}
if (options.error || options.fallback) {
// errorRule can only be set once
if (errorRule) {
if (!options.fallback === !errorRule.fallback) {
throw new Error("Multiple " + (options.fallback ? "fallback" : "error") + " rules not allowed (for token '" + options.defaultType + "')")
} else {
throw new Error("fallback and error are mutually exclusive (for token '" + options.defaultType + "')")
}
}
errorRule = options;
}
var match = options.match.slice();
if (fastAllowed) {
while (match.length && typeof match[0] === 'string' && match[0].length === 1) {
var word = match.shift();
fast[word.charCodeAt(0)] = options;
}
}
// Warn about inappropriate state-switching options
if (options.pop || options.push || options.next) {
if (!hasStates) {
throw new Error("State-switching options are not allowed in stateless lexers (for token '" + options.defaultType + "')")
}
if (options.fallback) {
throw new Error("State-switching options are not allowed on fallback tokens (for token '" + options.defaultType + "')")
}
}
// Only rules with a .match are included in the RegExp
if (match.length === 0) {
continue
}
fastAllowed = false;
groups.push(options);
// Check unicode flag is used everywhere or nowhere
for (var j = 0; j < match.length; j++) {
var obj = match[j];
if (!isRegExp(obj)) {
continue
}
if (unicodeFlag === null) {
unicodeFlag = obj.unicode;
} else if (unicodeFlag !== obj.unicode && options.fallback === false) {
throw new Error('If one rule is /u then all must be')
}
}
// convert to RegExp
var pat = reUnion(match.map(regexpOrLiteral));
// validate
var regexp = new RegExp(pat);
if (regexp.test("")) {
throw new Error("RegExp matches empty string: " + regexp)
}
var groupCount = reGroups(pat);
if (groupCount > 0) {
throw new Error("RegExp has capture groups: " + regexp + "\nUse (?: … ) instead")
}
// try and detect rules matching newlines
if (!options.lineBreaks && regexp.test('\n')) {
throw new Error('Rule should declare lineBreaks: ' + regexp)
}
// store regex
parts.push(reCapture(pat));
}
// If there's no fallback rule, use the sticky flag so we only look for
// matches at the current index.
//
// If we don't support the sticky flag, then fake it using an irrefutable
// match (i.e. an empty pattern).
var fallbackRule = errorRule && errorRule.fallback;
var flags = hasSticky && !fallbackRule ? 'ym' : 'gm';
var suffix = hasSticky || fallbackRule ? '' : '|';
if (unicodeFlag === true) flags += "u";
var combined = new RegExp(reUnion(parts) + suffix, flags);
return {regexp: combined, groups: groups, fast: fast, error: errorRule || defaultErrorRule}
}
function compile(rules) {
var result = compileRules(toRules(rules));
return new Lexer({start: result}, 'start')
}
function checkStateGroup(g, name, map) {
var state = g && (g.push || g.next);
if (state && !map[state]) {
throw new Error("Missing state '" + state + "' (in token '" + g.defaultType + "' of state '" + name + "')")
}
if (g && g.pop && +g.pop !== 1) {
throw new Error("pop must be 1 (in token '" + g.defaultType + "' of state '" + name + "')")
}
}
function compileStates(states, start) {
var all = states.$all ? toRules(states.$all) : [];
delete states.$all;
var keys = Object.getOwnPropertyNames(states);
if (!start) start = keys[0];
var ruleMap = Object.create(null);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
ruleMap[key] = toRules(states[key]).concat(all);
}
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var rules = ruleMap[key];
var included = Object.create(null);
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (!rule.include) continue
var splice = [j, 1];
if (rule.include !== key && !included[rule.include]) {
included[rule.include] = true;
var newRules = ruleMap[rule.include];
if (!newRules) {
throw new Error("Cannot include nonexistent state '" + rule.include + "' (in state '" + key + "')")
}
for (var k = 0; k < newRules.length; k++) {
var newRule = newRules[k];
if (rules.indexOf(newRule) !== -1) continue
splice.push(newRule);
}
}
rules.splice.apply(rules, splice);
j--;
}
}
var map = Object.create(null);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
map[key] = compileRules(ruleMap[key], true);
}
for (var i = 0; i < keys.length; i++) {
var name = keys[i];
var state = map[name];
var groups = state.groups;
for (var j = 0; j < groups.length; j++) {
checkStateGroup(groups[j], name, map);
}
var fastKeys = Object.getOwnPropertyNames(state.fast);
for (var j = 0; j < fastKeys.length; j++) {
checkStateGroup(state.fast[fastKeys[j]], name, map);
}
}
return new Lexer(map, start)
}
function keywordTransform(map) {
var reverseMap = Object.create(null);
var byLength = Object.create(null);
var types = Object.getOwnPropertyNames(map);
for (var i = 0; i < types.length; i++) {
var tokenType = types[i];
var item = map[tokenType];
var keywordList = Array.isArray(item) ? item : [item];
keywordList.forEach(function(keyword) {
(byLength[keyword.length] = byLength[keyword.length] || []).push(keyword);
if (typeof keyword !== 'string') {
throw new Error("keyword must be string (in keyword '" + tokenType + "')")
}
reverseMap[keyword] = tokenType;
});
}
// fast string lookup
// https://jsperf.com/string-lookups
function str(x) { return JSON.stringify(x) }
var source = '';
source += 'switch (value.length) {\n';
for (var length in byLength) {
var keywords = byLength[length];
source += 'case ' + length + ':\n';
source += 'switch (value) {\n';
keywords.forEach(function(keyword) {
var tokenType = reverseMap[keyword];
source += 'case ' + str(keyword) + ': return ' + str(tokenType) + '\n';
});
source += '}\n';
}
source += '}\n';
return Function('value', source) // type
}
/***************************************************************************/
var Lexer = function(states, state) {
this.startState = state;
this.states = states;
this.buffer = '';
this.stack = [];
this.reset();
};
Lexer.prototype.reset = function(data, info) {
this.buffer = data || '';
this.index = 0;
this.line = info ? info.line : 1;
this.col = info ? info.col : 1;
this.queuedToken = info ? info.queuedToken : null;
this.queuedThrow = info ? info.queuedThrow : null;
this.setState(info ? info.state : this.startState);
this.stack = info && info.stack ? info.stack.slice() : [];
return this
};
Lexer.prototype.save = function() {
return {
line: this.line,
col: this.col,
state: this.state,
stack: this.stack.slice(),
queuedToken: this.queuedToken,
queuedThrow: this.queuedThrow,
}
};
Lexer.prototype.setState = function(state) {
if (!state || this.state === state) return
this.state = state;
var info = this.states[state];
this.groups = info.groups;
this.error = info.error;
this.re = info.regexp;
this.fast = info.fast;
};
Lexer.prototype.popState = function() {
this.setState(this.stack.pop());
};
Lexer.prototype.pushState = function(state) {
this.stack.push(this.state);
this.setState(state);
};
var eat = hasSticky ? function(re, buffer) { // assume re is /y
return re.exec(buffer)
} : function(re, buffer) { // assume re is /g
var match = re.exec(buffer);
// will always match, since we used the |(?:) trick
if (match[0].length === 0) {
return null
}
return match
};
Lexer.prototype._getGroup = function(match) {
var groupCount = this.groups.length;
for (var i = 0; i < groupCount; i++) {
if (match[i + 1] !== undefined) {
return this.groups[i]
}
}
throw new Error('Cannot find token type for matched text')
};
function tokenToString() {
return this.value
}
Lexer.prototype.next = function() {
var index = this.index;
// If a fallback token matched, we don't need to re-run the RegExp
if (this.queuedGroup) {
var token = this._token(this.queuedGroup, this.queuedText, index);
this.queuedGroup = null;
this.queuedText = "";
return token
}
var buffer = this.buffer;
if (index === buffer.length) {
return // EOF
}
// Fast matching for single characters
var group = this.fast[buffer.charCodeAt(index)];
if (group) {
return this._token(group, buffer.charAt(index), index)
}
// Execute RegExp
var re = this.re;
re.lastIndex = index;
var match = eat(re, buffer);
// Error tokens match the remaining buffer
var error = this.error;
if (match == null) {
return this._token(error, buffer.slice(index, buffer.length), index)
}
var group = this._getGroup(match);
var text = match[0];
if (error.fallback && match.index !== index) {
this.queuedGroup = group;
this.queuedText = text;
// Fallback tokens contain the unmatched portion of the buffer
return this._token(error, buffer.slice(index, match.index), index)
}
return this._token(group, text, index)
};
Lexer.prototype._token = function(group, text, offset) {
// count line breaks
var lineBreaks = 0;
if (group.lineBreaks) {
var matchNL = /\n/g;
var nl = 1;
if (text === '\n') {
lineBreaks = 1;
} else {
while (matchNL.exec(text)) { lineBreaks++; nl = matchNL.lastIndex; }
}
}
var token = {
type: (typeof group.type === 'function' && group.type(text)) || group.defaultType,
value: typeof group.value === 'function' ? group.value(text) : text,
text: text,
toString: tokenToString,
offset: offset,
lineBreaks: lineBreaks,
line: this.line,
col: this.col,
};
// nb. adding more props to token object will make V8 sad!
var size = text.length;
this.index += size;
this.line += lineBreaks;
if (lineBreaks !== 0) {
this.col = size - nl + 1;
} else {
this.col += size;
}
// throw, if no rule with {error: true}
if (group.shouldThrow) {
throw new Error(this.formatError(token, "invalid syntax"))
}
if (group.pop) this.popState();
else if (group.push) this.pushState(group.push);
else if (group.next) this.setState(group.next);
return token
};
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
var LexerIterator = function(lexer) {
this.lexer = lexer;
};
LexerIterator.prototype.next = function() {
var token = this.lexer.next();
return {value: token, done: !token}
};
LexerIterator.prototype[Symbol.iterator] = function() {
return this
};
Lexer.prototype[Symbol.iterator] = function() {
return new LexerIterator(this)
};
}
Lexer.prototype.formatError = function(token, message) {
if (token == null) {
// An undefined token indicates EOF
var text = this.buffer.slice(this.index);
var token = {
text: text,
offset: this.index,
lineBreaks: text.indexOf('\n') === -1 ? 0 : 1,
line: this.line,
col: this.col,
};
}
var start = Math.max(0, token.offset - token.col + 1);
var eol = token.lineBreaks ? token.text.indexOf('\n') : token.text.length;
var firstLine = this.buffer.substring(start, token.offset + eol);
message += " at line " + token.line + " col " + token.col + ":\n\n";
message += " " + firstLine + "\n";
message += " " + Array(token.col).join(" ") + "^";
return message
};
Lexer.prototype.clone = function() {
return new Lexer(this.states, this.state)
};
Lexer.prototype.has = function(tokenType) {
return true
};
return {
compile: compile,
states: compileStates,
error: Object.freeze({error: true}),
fallback: Object.freeze({fallback: true}),
keywords: keywordTransform,
}
}));
});
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/**
* T-code token type
*
* @category Lexer
*/
var T_CODE = 'T_CODE';
/**
* G-code token type
*
* @category Lexer
*/
var G_CODE = 'G_CODE';
/**
* M-code token type
*
* @category Lexer
*/
var M_CODE = 'M_CODE';
/**
* D-code token type
*
* @category Lexer
*/
var D_CODE = 'D_CODE';
/**
* Asterisk token type
*
* @category Lexer
*/
var ASTERISK = 'ASTERISK';
/**
* Percent sign token type
*
* @category Lexer
*/
var PERCENT = 'PERCENT';
/**
* Equals sign token type
*
* @category Lexer
*/
var EQUALS = 'EQUALS';
/**
* Comma token type
*
* @category Lexer
*/
var COMMA = 'COMMA';
/**
* Arithmatic operator token type
*
* @category Lexer
*/
var OPERATOR = 'OPERATOR';
/**
* Gerber format specification token type
*
* @category Lexer
*/
var GERBER_FORMAT = 'GERBER_FORMAT';
/**
* Gerber units specification token type
*
* @category Lexer
*/
var GERBER_UNITS = 'GERBER_UNITS';
/**
* Gerber tool macro token type
*
* @category Lexer
*/
var GERBER_TOOL_MACRO = 'GERBER_TOOL_MACRO';
/**
* Gerber tool definition token type
*
* @category Lexer
*/
var GERBER_TOOL_DEF = 'GERBER_TOOL_DEF';
/**
* Gerber load polarity token type
*
* @category Lexer
*/
var GERBER_LOAD_POLARITY = 'GERBER_LOAD_POLARITY';
/**
* Gerber step repear token type
*
* @category Lexer
*/
var GERBER_STEP_REPEAT = 'GERBER_STEP_REPEAT';
/**
* Gerber macro variable token type
*
* @category Lexer
*/
var GERBER_MACRO_VARIABLE = 'GERBER_MACRO_VARIABLE';
/**
* Semicolor token type
*
* @category Lexer
*/
var SEMICOLON = 'SEMICOLON';
/**
* Drill file units token type
*
* @category Lexer
*/
var DRILL_UNITS = 'DRILL_UNITS';
/**
* Drill zero-inclusion token type
*
* @category Lexer
*/
var DRILL_ZERO_INCLUSION = 'DRILL_ZERO_INCLUSION';
/**
* Coordinate axis character token type
*
* @category Lexer
*/
var COORD_CHAR = 'COORD_CHAR';
/**
* Number token type
*
* @category Lexer
*/
var NUMBER = 'NUMBER';
/**
* Word token type
*
* @category Lexer
*/
var WORD = 'WORD';
/**
* Whitespace token type
*
* @category Lexer
*/
var WHITESPACE = 'WHITESPACE';
/**
* Newline token type
*
* @category Lexer
*/
var NEWLINE = 'NEWLINE';
/**
* Catchall token type
*
* @category Lexer
*/
var CATCHALL = 'CATCHALL';
/**
* Error token type
*
* @category Lexer
*/
var ERROR = 'ERROR';
/**
* Union of all available token types
*
* @category Lexer
*/
var _rules;
var RE_STRIP_LEADING_ZEROS = /^0*/;
var stripLeadingZeros = function stripLeadingZeros(text) {
return text.replace(RE_STRIP_LEADING_ZEROS, '');
};
var getCodeValue = function getCodeValue(text) {
return stripLeadingZeros(text.slice(1)) || '0';
};
var rules = (_rules = {}, _defineProperty(_rules, T_CODE, {
match: /T\d+/,
value: getCodeValue
}), _defineProperty(_rules, G_CODE, {
match: /G\d+/,
value: getCodeValue
}), _defineProperty(_rules, M_CODE, {
match: /M\d+/,
value: getCodeValue
}), _defineProperty(_rules, D_CODE, {
match: /D\d+/,
value: getCodeValue
}), _defineProperty(_rules, ASTERISK, '*'), _defineProperty(_rules, PERCENT, '%'), _defineProperty(_rules, EQUALS, '='), _defineProperty(_rules, GERBER_FORMAT, {
match: /FS[LTDAI]+/,
value: function value(text) {
return text.slice(2);
}
}), _defineProperty(_rules, GERBER_UNITS, {
match: /MO(?:IN|MM)/,
value: function value(text) {
return text.slice(2);
}
}), _defineProperty(_rules, GERBER_TOOL_MACRO, {
// "-" in a tool name is illegal, but some gerber writers misbehave
// https://github.com/mcous/gerber-parser/pull/13
match: /AM[a-zA-Z_.$][\w.-]*/,
value: function value(text) {
return text.slice(2);
}
}), _defineProperty(_rules, GERBER_TOOL_DEF, {
match: /ADD\d+[a-zA-Z_.$][\w.-]*/,
value: function value(text) {
return stripLeadingZeros(text.slice(3));
}
}), _defineProperty(_rules, GERBER_LOAD_POLARITY, {
match: /LP[DC]/,
value: function value(text) {
return text.slice(2);
}
}), _defineProperty(_rules, GERBER_STEP_REPEAT, 'SR'), _defineProperty(_rules, GERBER_MACRO_VARIABLE, /\$\d+/), _defineProperty(_rules, SEMICOLON, ';'), _defineProperty(_rules, DRILL_UNITS, /^(?:METRIC|INCH)/), _defineProperty(_rules, DRILL_ZERO_INCLUSION, {
match: /,(?:TZ|LZ)/,
value: function value(text) {
return text.slice(1);
}
}), _defineProperty(_rules, COORD_CHAR, /[XYIJACFSBHZN]/), _defineProperty(_rules, NUMBER, /(?:[+-])?[\d.]+/), _defineProperty(_rules, OPERATOR, ['x', '/', '+', '-', '(', ')']), _defineProperty(_rules, COMMA, ','), _defineProperty(_rules, WORD, /[a-zA-Z]+/), _defineProperty(_rules, WHITESPACE, /[ \t]+/), _defineProperty(_rules, NEWLINE, {
match: /\r?\n/,
lineBreaks: true
}), _defineProperty(_rules, CATCHALL, /[\S]/), _defineProperty(_rules, ERROR, moo.error), _rules);
// gerber and drill file lexer + tokenizer
/**
* {@linkcode Lexer} factory
*
* @example
* ```ts
* import {createLexer} from '@tracespace/parser'
*
* const lexer = createLexer()
*
* lexer.reset('G04 gerber string*\nM02*\n')
*
* Array.from(lexer).forEach(token => {
* console.log(`${token.type}: ${token.value}`)
* })
* ```
*
* @category Lexer
*/
function createLexer() {
return moo.compile(rules);
}
/**
* The lexing module of the parser. The Lexer is generated by
* {@link https://github.com/no-context/moo | Moo}, which determines its API.
*
* @category Lexer
*/
/**
* {@linkcode Root} node type
*
* @category Node
*/
var ROOT = 'root';
/**
* {@linkcode Comment} node type
*
* @category Node
*/
var COMMENT = 'comment';
/**
* {@linkcode Done} node type
*
* @category Node
*/
var DONE = 'done';
/**
* {@linkcode Units} node type
*
* @category Node
*/
var UNITS = 'units';
/**
* {@linkcode CoordinateFormat} node type
*
* @category Node
*/
var COORDINATE_FORMAT = 'coordinateFormat';
/**
* {@linkcode ToolDefinition} node type
*
* @category Node
*/
var TOOL_DEFINITION = 'toolDefinition';
/**
* {@linkcode ToolMacro} node type
*
* @category Node
*/
var TOOL_MACRO = 'toolMacro';
/**
* {@linkcode ToolChange} node type
*
* @category Node
*/
var TOOL_CHANGE = 'toolChange';
/**
* {@linkcode LoadPolarity} node type
*
* @category Node
*/
var LOAD_POLARITY = 'loadPolarity';
/**
* {@linkcode StepRepeat} node type
*
* @category Node
*/
var STEP_REPEAT = 'stepRepeat';
/**
* {@linkcode Graphic} node type
*
* @category Node
*/
var GRAPHIC = 'graphic';
/**
* {@linkcode InterpolateMode} node type
*
* @category Node
*/
var INTERPOLATE_MODE = 'interpolateMode';
/**
* {@linkcode RegionMode} node type
*
* @category Node
*/
var REGION_MODE = 'regionMode';
/**
* {@linkcode QuadrantMode} node type
*
* @category Node
*/
var QUADRANT_MODE = 'quadrantMode';
/**
* {@linkcode Unimplemented} node type
*
* @category Node
*/
var UNIMPLEMENTED = 'unimplemented';
/**
* {@linkcode MacroComment} node type
*
* @category Macro
*/
var MACRO_COMMENT = 'macroComment';
/**
* {@linkcode MacroVariable} node type
*
* @category Macro
*/
var MACRO_VARIABLE = 'macroVariable';
/**
* {@linkcode MacroPrimitive} node type
*
* @category Macro
*/
var MACRO_PRIMITIVE = 'macroPrimitive';
var SINGLE_TOKEN = 'TOKEN';
var MIN_TO_MAX = 'MIN_TO_MAX';
function token(type, value) {
return {
rule: SINGLE_TOKEN,
type: type,
value: value
};
}
function notToken(type, value) {
return {
rule: SINGLE_TOKEN,
type: type,
value: value,
negate: true
};
}
function one(match) {
return {
rule: MIN_TO_MAX,
min: 1,
max: 1,
match: match
};
}
function zeroOrOne(match) {
return {
rule: MIN_TO_MAX,
min: 0,
max: 1,
match: match
};
}
function zeroOrMore(match) {
return {
rule: MIN_TO_MAX,
min: 0,
max: Infinity,
match: match
};
}
function oneOrMore(match) {
return {
rule: MIN_TO_MAX,
min: 1,
max: Infinity,
match: match
};
}
function minToMax(min, max, match) {
return {
rule: MIN_TO_MAX,
min: min,
max: max,
match: match
};
}
function tokensToCoordinates(tokens) {
return tokens.reduce(function (coords, token, i) {
var prev = tokens[i - 1];
if (token.type === NUMBER && (prev === null || prev === void 0 ? void 0 : prev.type) === COORD_CHAR) {
coords[prev.value.toLowerCase()] = token.value;
}
return coords;
}, {});
}
function tokensToMode(tokens) {
return tokens.filter(function (t) {
return t.type === G_CODE;
}).reduce(function (m, t) {
if (t.value === '0') return MOVE;
if (t.value === '1') return LINE;
if (t.value === '2') return CW_ARC;
if (t.value === '3') return CCW_ARC;
if (t.value === '5') return DRILL;
return m;
}, null);
}
function tokensToGraphic(tokens) {
return tokens.filter(function (t) {
return t.type === D_CODE;
}).reduce(function (g, t) {
if (t.value === '1') return SEGMENT;
if (t.value === '2') return MOVE;
if (t.value === '3') return SHAPE;
return g;
}, null);
}
function tokensToString(tokens) {
return tokens.map(function (t) {
return t.value;
}).join('').trim();
}
function tokensToPosition(tokens) {
var _options$head;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var head = (_options$head = options.head) !== null && _options$head !== void 0 ? _options$head : tokens[0];
var tail = options.length ? tokens[tokens.indexOf(head) + options.length - 1] : tokens[tokens.length - 1];
return {
start: {
line: head.line,
column: head.col,
offset: head.offset
},
end: {
line: tail.line,
column: tail.col,
offset: tail.offset
}
};
}
var units = {
rules: [one([token(DRILL_UNITS), token(M_CODE, '71'), token(M_CODE, '72')]), zeroOrMore([token(COMMA), token(DRILL_ZERO_INCLUSION), token(NUMBER, /^0{1,8}\.0{1,8}$/)]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
var units = tokens[0].value === 'INCH' || tokens[0].value === '72' ? IN : MM;
var zeroSuppression = tokens.filter(function (t) {
return t.type === DRILL_ZERO_INCLUSION;
}).reduce(function (z, t) {
if (t.value === 'LZ') return TRAILING;
if (t.value === 'TZ') return LEADING;
return z;
}, null);
var format = tokens.filter(function (t) {
return t.type === NUMBER;
}).reduce(function (_, t) {
var _t$value$split = t.value.split('.'),
_t$value$split2 = _slicedToArray(_t$value$split, 2),
_t$value$split2$ = _t$value$split2[0],
integer = _t$value$split2$ === void 0 ? '' : _t$value$split2$,
_t$value$split2$2 = _t$value$split2[1],
decimal = _t$value$split2$2 === void 0 ? '' : _t$value$split2$2;
return [integer.length, decimal.length];
}, null);
var nodes = [{
type: UNITS,
position: tokensToPosition(tokens.slice(0, 2)),
units: units
}];
if (zeroSuppression || format) {
nodes.push({
type: COORDINATE_FORMAT,
position: tokensToPosition(tokens.slice(1)),
mode: null,
format: format,
zeroSuppression: zeroSuppression
});
}
return nodes;
}
};
var tool = {
rules: [token(T_CODE), minToMax(0, 12, [token(COORD_CHAR, 'C'), token(COORD_CHAR, 'F'), token(COORD_CHAR, 'S'), token(COORD_CHAR, 'B'), token(COORD_CHAR, 'H'), token(COORD_CHAR, 'Z'), token(NUMBER)]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
var code = tokens[0].value;
var position = tokensToPosition(tokens);
var _tokensToCoordinates = tokensToCoordinates(tokens.slice(1, -1)),
_tokensToCoordinates$ = _tokensToCoordinates.c,
c = _tokensToCoordinates$ === void 0 ? null : _tokensToCoordinates$;
var shape = c !== null ? {
type: CIRCLE,
diameter: Number(c)
} : null;
return shape ? [{
type: TOOL_DEFINITION,
hole: null,
position: position,
shape: shape,
code: code
}] : [{
type: TOOL_CHANGE,
position: position,
code: code
}];
}
};
var mode = {
rules: [one([token(G_CODE, '0'), token(G_CODE, '1'), token(G_CODE, '2'), token(G_CODE, '3'), token(G_CODE, '5')]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
return [{
type: INTERPOLATE_MODE,
position: tokensToPosition(tokens),
mode: tokensToMode(tokens)
}];
}
};
var operation = {
rules: [minToMax(0, 2, [token(T_CODE), token(G_CODE, '0'), token(G_CODE, '1'), token(G_CODE, '2'), token(G_CODE, '3'), token(G_CODE, '5')]), minToMax(2, 8, [token(COORD_CHAR), token(NUMBER)]), zeroOrOne([token(T_CODE)]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
var graphicTokens = tokens.filter(function (t) {
return t.type === COORD_CHAR || t.type === NUMBER;
});
var modeToken = tokens.find(function (t) {
return t.type === G_CODE;
});
var toolToken = tokens.find(function (t) {
return t.type === T_CODE;
});
var coordinates = tokensToCoordinates(graphicTokens);
var code = toolToken ? toolToken.value : null;
var mode = tokensToMode(tokens);
var graphicPosition = tokensToPosition(tokens, {
head: graphicTokens[0],
length: graphicTokens.length + 1
});
var modePosition = tokensToPosition(tokens, {
head: modeToken,
length: 2
});
var toolPosition = tokensToPosition(tokens, {
head: toolToken,
length: 2
});
var nodes = [{
type: GRAPHIC,
position: graphicPosition,
graphic: null,
coordinates: coordinates
}];
if (mode) {
nodes.unshift({
type: INTERPOLATE_MODE,
position: modePosition,
mode: mode
});
}
if (code) {
nodes.unshift({
type: TOOL_CHANGE,
position: toolPosition,
code: code
});
}
return nodes;
}
};
var slot = {
rules: [minToMax(2, 4, [token(COORD_CHAR), token(NUMBER)]), token(G_CODE, '85'), minToMax(2, 4, [token(COORD_CHAR), token(NUMBER)]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
var gCode = tokens.find(function (t) {
return t.type === G_CODE;
});
var splitIdx = gCode ? tokens.indexOf(gCode) : -1;
var start = tokensToCoordinates(tokens.slice(0, splitIdx));
var end = tokensToCoordinates(tokens.slice(splitIdx));
var coordinates = {};
Object.keys(start).forEach(function (k) {
return coordinates["".concat(k, "1")] = start[k];
});
Object.keys(end).forEach(function (k) {
return coordinates["".concat(k, "2")] = end[k];
});
return [{
type: GRAPHIC,
position: tokensToPosition(tokens),
graphic: SLOT,
coordinates: coordinates
}];
}
};
var done = {
rules: [one([token(M_CODE, '30'), token(M_CODE, '0')]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
return [{
type: DONE,
position: tokensToPosition(tokens)
}];
}
};
var comment = {
rules: [token(SEMICOLON), zeroOrMore([notToken(NEWLINE)]), token(NEWLINE)],
createNodes: function createNodes(tokens) {
return [{
type: COMMENT,
comment: tokensToString(tokens.slice(1, -1)),
position: tokensToPosition(tokens)
}];
}
};
var drillSyntax = [tool, mode, operation, slot, comment, units, done].map(function (r) {
return _extends({}, r, {
filetype: DRILL
});
});
var FULL_MATCH = 'FULL_MATCH';
var PARTIAL_MATCH = 'PARTIAL_MATCH';
var NO_MATCH = 'NO_MATCH';
function createMatchSyntax() {
for (var _len = arguments.length, grammar = new Array(_len), _key = 0; _key < _len; _key++) {
grammar[_key] = arguments[_key];
}
return function (state, token) {
return matchSyntax(state, token, grammar);
};
}
function matchSyntax(state, token, grammar) {
if (state === null) state = {
candidates: grammar,
tokens: []
};
var _state = state,
prevCandidates = _state.candidates;
var candidates = [];
var tokens = [].concat(_toConsumableArray(state.tokens), [token]);
var i;
for (i = 0; i < prevCandidates.length; i++) {
var rule = prevCandidates[i];
var result = tokenListMatches(rule.rules, tokens);
if (result === FULL_MATCH) {
var nodes = rule.createNodes(tokens);
return {
candidates: [],
tokens: tokens,
nodes: nodes,
filetype: rule.filetype
};
}
if (result === PARTIAL_MATCH) {
candidates.push(rule);
}
}
return {
candidates: candidates,
tokens: tokens
};
}
function tokenListMatches(rules, tokens) {
var i = 0;
var j = 0;
var multiMatchCount = 0;
while (i < rules.length && j < tokens.length) {
var rule = rules[i];
var _token = tokens[j];
var match = tokenMatches(rule, _token);
if (match) {
if (rule.rule === SINGLE_TOKEN || rule.rule === MIN_TO_MAX && multiMatchCount >= rule.max - 1) {
i++;
j++;
multiMatchCount = 0;
} else if (rule.rule === MIN_TO_MAX) {
j++;
multiMatchCount++;
}
} else if (rule.rule === MIN_TO_MAX && multiMatchCount >= rule.min) {
multiMatchCount = 0;
i++;
} else {
return NO_MATCH;
}
}
if (i < rules.length) return PARTIAL_MATCH;
return FULL_MATCH;
}
function tokenMatches(rule, token) {
if (rule.rule === SINGLE_TOKEN) {
var typeResult = rule.type === token.type;
var valueResult = rule.value == null || typeof rule.value === 'string' && rule.value === token.value || rule.value instanceof RegExp && rule.value.test(token.value);
var result = typeResult && valueResult;
return rule.negate ? !result : result;
}
if (Array.isArray(rule.match)) {
return rule.match.some(function (match) {
return tokenMatches(match, token);
});
}
return false;
}
var macroComment = {
rules: [token(NUMBER, '0'), zeroOrMore([notToken(ASTERISK)]), token(ASTERISK)],
createNodes: createMacroComment
};
var macroVariable = {
rules: [token(GERBER_MACRO_VARIABLE), token(EQUALS), oneOrMore([token(NUMBER), token(OPERATOR), token(GERBER_MACRO_VARIABLE), token(COORD_CHAR, 'X')]), token(ASTERISK)],
createNodes: createMacroVariable
};
var macroPrimitive = {
rules: [token(NUMBER), token(COMMA), oneOrMore([token(COMMA), token(NUMBER), token(OPERATOR), token(GERBER_MACRO_VARIABLE), token(COORD_CHAR, 'X')]), token(ASTERISK)],
createNodes: createMacroPrimitive
};
function createMacroComment(tokens) {
var comment = tokens.slice(1, -1).map(function (t) {
return t.text;
}).join('').trim();
return [{
type: MACRO_COMMENT,
position: tokensToPosition(tokens),
comment: comment
}];
}
function createMacroPrimitive(tokens) {
var code = tokens[0].value;
var modifiers = tokens.slice(2, -1).reduce(function (groups, token) {
var current = groups[groups.length - 1];
if (token.type !== COMMA) {
current.push(token);
} else {
groups.push([]);
}
return groups;
}, [[]]).map(parseMacroExpression);
return [{
type: MACRO_PRIMITIVE,
position: tokensToPosition(tokens),
code: code,
modifiers: modifiers
}];
}
function createMacroVariable(tokens) {
var name = tokens[0].value;
var value = parseMacroExpression(tokens.slice(2, -1));
return [{
type: MACRO_VARIABLE,
position: tokensToPosition(tokens),
name: name,
value: value
}];
}
function parseMacroExpression(tokens) {
var toParse = tokens.map(function (token) {
return token.type === COORD_CHAR ? _extends({}, token, {
type: OPERATOR,
value: 'x'
}) : token;
});
return parseAddition();
function peekNextToken() {
var _toParse$;
return (_toParse$ = toParse[0]) !== null && _toParse$ !== void 0 ? _toParse$ : null;
} // parse numbers, variables, and parenthesis
function parsePrimary() {
var token = toParse.shift();
if (token.type === NUMBER) return Number(token.value);
if (token.type === GERBER_MACRO_VARIABLE) return token.value; // else, we've got a parentheses group, so parse it and consume the ")"
var expression = parseAddition();
toParse.shift();
return expression;
} // parse multiplication and division operations
function parseMultiplication() {
var expression = parsePrimary();
var nextToken = peekNextToken();
while (((_nextToken = nextToken) === null || _nextToken === void 0 ? void 0 : _nextToken.type) === OPERATOR && (nextToken.value === 'x' || nextToken.value === '/')) {
var _nextToken;
toParse.shift();
expression = {
left: expression,
right: parsePrimary(),
operator: nextToken.value
};
nextToken = peekNextToken();
}
return expression;
}
function parseAddition() {
var expression = parseMultiplication();
var nextToken = peekNextToken();
while (((_nextToken2 = nextToken) === null || _nextToken2 === void 0 ? void 0 : _nextToken2.type) === OPERATOR && (nextToken.value === '+' || nextToken.value === '-') || ((_nextToken3 = nextToken) === null || _nextToken3 === void 0 ? void 0 : _nextToken3.type) === NUMBER) {
var _nextToken2, _nextToken3;
var operator = '+';
if (nextToken.type === OPERATOR) {
toParse.shift();
operator = nextToken.value;
}
var right = parseMultiplication();
expression = {
left: expression,
right: right,
operator: operator
};
nextToken = peekNextToken();
}
return expression;
}
}
var MACRO_GRAMMAR = [macroPrimitive, macroVariable, macroComment];
function parseMacroBlocks(tokens) {
var matchState = null;
var blocks = [];
tokens.forEach(function (token) {
matchState = matchSyntax(matchState, token, MACRO_GRAMMAR);
if (matchState.nodes) blocks.push.apply(blocks, _toConsumableArray(matchState.nodes));
if (matchState.candidates.length === 0) matchState = null;
});
return blocks;
}
var holeParamsToShape = function holeParamsToShape(params) {
if (params.length === 1) {
var _params = _slicedToArray(params, 1),
diameter = _params[0];
return {
type: CIRCLE,
diameter: diameter
};
}
if (params.length === 2) {
var _params2 = _slicedToArray(params, 2),
xSize = _params2[0],
ySize = _params2[1];
return {
type: RECTANGLE,
xSize: xSize,
ySize: ySize
};
}
return null;
};
var done$1 = {
rules: [one([token(M_CODE, '0'), token(M_CODE, '2')]), token(ASTERISK)],
createNodes: function createNodes(tokens) {
return [{
type: DONE,
position: tokensToPosition(tokens)
}];
}
};
var comment$1 = {
rules: [token(G_CODE, '4'), zeroOrMore([notToken(ASTERISK)]), token(ASTERISK)],
createNodes: function createNodes(tokens) {
return [{
type: COMMENT,
position: tokensToPosition(tokens),
comment: tokensToString(tokens.slice(1, -1))
}];
}
};
var format = {
rules: [token(PERCENT), token(GERBER_FORMAT), zeroOrMore([notToken(COORD_CHAR, 'X')]), token(COORD_CHAR, 'X'), token(NUMBER), token(COORD_CHAR, 'Y'), token(NUMBER), zeroOrMore([notToken(ASTERISK)]), token(ASTERISK), // including units here is invalid syntax, but Cadence Allegro does it
// https://github.com/tracespace/tracespace/issues/234
minToMax(0, 2, [token(GERBER_UNITS), token(ASTERISK)]), token(PERCENT)],
createNodes: function createNodes(tokens) {
var _coords$x;
var format = null;
var zeroSuppression = null;
var mode = null;
var coords = tokensToCoordinates(tokens);
var formatEndIdx = tokens.findIndex(function (t) {
return t.type === ASTERISK;
});
var unitsToken = tokens.find(function (t) {
return t.type === GERBER_UNITS;
});
tokens.filter(function (t) {
return t.type === GERBER_FORMAT;
}).forEach(function (t) {
if (t.value.indexOf('T') >= 0) zeroSuppression = TRAILING;
if (t.value.indexOf('L') >= 0) zeroSuppression = LEADING;
if (t.value.indexOf('I') >= 0) mode = INCREMENTAL;
if (t.value.indexOf('A') >= 0) mode = ABSOLUTE;
});
if (coords.x === coords.y && ((_coords$x = coords.x) === null || _coords$x === void 0 ? void 0 : _coords$x.length) === 2) {
var integers = Number(coords.x[0]);
var decimals = Number(coords.x[1]);
if (integers && decimals) format = [integers, decimals];
}
var nodes = [{
type: COORDINATE_FORMAT,
position: tokensToPosition(tokens.slice(1, formatEndIdx + 1)),
zeroSuppression: zeroSuppression,
format: format,
mode: mode
}];
if (unitsToken) {
nodes.push({
type: UNITS,
position: tokensToPosition(tokens.slice(1, -1), {
head: unitsToken
}),
units: unitsToken.value === 'MM' ? MM : IN
});
}
return nodes;
}
};
var units$1 = {
rules: [token(PERCENT), token(GERBER_UNITS), token(ASTERISK), token(PERCENT)],
createNodes: function createNodes(tokens) {
return [{
type: UNITS,
position: tokensToPosition(tokens.slice(1, -1)),
units: tokens[1].value === 'MM' ? MM : IN
}];
}
};
var toolMacro = {
rules: [token(PERCENT), token(GERBER_TOOL_MACRO), token(ASTERISK), zeroOrMore([notToken(PERCENT)]), token(PERCENT)],
createNodes: function createNodes(tokens) {
var name = tokens[1].value;
var position = tokensToPosition(tokens.slice(1, -1));
var blockTokens = tokens.slice(3, -1);
return [{
type: TOOL_MACRO,
position: position,
children: parseMacroBlocks(blockTokens),
name: name
}];
}
};
var toolDefinition = {
rules: [token(PERCENT), token(GERBER_TOOL_DEF), zeroOrMore([token(COMMA), token(NUMBER), token(COORD_CHAR, 'X')]), token(ASTERISK), token(PERCENT)],
createNodes: function createNodes(tokens) {