@tracespace/parser
Version:
Gerber and NC drill file parser
189 lines (162 loc) • 4.82 kB
text/typescript
// gerber aperture macro syntax
import * as Lexer from '../lexer'
import * as Tree from '../tree'
import {MacroValue} from '../types'
import {SyntaxRule, MatchState} from './types'
import {token, notToken, zeroOrMore, oneOrMore} from './rules'
import {tokensToPosition} from './map-tokens'
import {matchSyntax} from './match-syntax'
const macroComment: SyntaxRule<Tree.MacroBlock> = {
rules: [
token(Lexer.NUMBER, '0'),
zeroOrMore([notToken(Lexer.ASTERISK)]),
token(Lexer.ASTERISK),
],
createNodes: createMacroComment,
}
const macroVariable: SyntaxRule<Tree.MacroBlock> = {
rules: [
token(Lexer.GERBER_MACRO_VARIABLE),
token(Lexer.EQUALS),
oneOrMore([
token(Lexer.NUMBER),
token(Lexer.OPERATOR),
token(Lexer.GERBER_MACRO_VARIABLE),
token(Lexer.COORD_CHAR, 'X'),
]),
token(Lexer.ASTERISK),
],
createNodes: createMacroVariable,
}
const macroPrimitive: SyntaxRule<Tree.MacroBlock> = {
rules: [
token(Lexer.NUMBER),
token(Lexer.COMMA),
oneOrMore([
token(Lexer.COMMA),
token(Lexer.NUMBER),
token(Lexer.OPERATOR),
token(Lexer.GERBER_MACRO_VARIABLE),
token(Lexer.COORD_CHAR, 'X'),
]),
token(Lexer.ASTERISK),
],
createNodes: createMacroPrimitive,
}
function createMacroComment(tokens: Lexer.Token[]): Tree.MacroComment[] {
const comment = tokens
.slice(1, -1)
.map(t => t.text)
.join('')
.trim()
return [
{type: Tree.MACRO_COMMENT, position: tokensToPosition(tokens), comment},
]
}
function createMacroPrimitive(tokens: Lexer.Token[]): Tree.MacroPrimitive[] {
const code = tokens[0].value
const modifiers = tokens
.slice(2, -1)
.reduce<Lexer.Token[][]>(
(groups, token) => {
const current = groups[groups.length - 1]
if (token.type !== Lexer.COMMA) {
current.push(token)
} else {
groups.push([])
}
return groups
},
[[]]
)
.map(parseMacroExpression)
return [
{
type: Tree.MACRO_PRIMITIVE,
position: tokensToPosition(tokens),
code,
modifiers,
},
]
}
function createMacroVariable(tokens: Lexer.Token[]): Tree.MacroVariable[] {
const name = tokens[0].value
const value = parseMacroExpression(tokens.slice(2, -1))
return [
{
type: Tree.MACRO_VARIABLE,
position: tokensToPosition(tokens),
name,
value,
},
]
}
function parseMacroExpression(tokens: Lexer.Token[]): MacroValue {
const toParse = tokens.map<Lexer.Token>(token => {
return token.type === Lexer.COORD_CHAR
? {...token, type: Lexer.OPERATOR, value: 'x'}
: token
})
return parseAddition()
function peekNextToken(): Lexer.Token | null {
return toParse[0] ?? null
}
// parse numbers, variables, and parenthesis
function parsePrimary(): MacroValue {
const token = toParse.shift() as Lexer.Token
if (token.type === Lexer.NUMBER) return Number(token.value)
if (token.type === Lexer.GERBER_MACRO_VARIABLE) return token.value
// else, we've got a parentheses group, so parse it and consume the ")"
const expression = parseAddition()
toParse.shift()
return expression
}
// parse multiplication and division operations
function parseMultiplication(): MacroValue {
let expression = parsePrimary()
let nextToken = peekNextToken()
while (
nextToken?.type === Lexer.OPERATOR &&
(nextToken.value === 'x' || nextToken.value === '/')
) {
toParse.shift()
expression = {
left: expression,
right: parsePrimary(),
operator: nextToken.value,
}
nextToken = peekNextToken()
}
return expression
}
function parseAddition(): MacroValue {
let expression = parseMultiplication()
let nextToken = peekNextToken()
while (
(nextToken?.type === Lexer.OPERATOR &&
(nextToken.value === '+' || nextToken.value === '-')) ||
nextToken?.type === Lexer.NUMBER
) {
let operator: '+' | '-' = '+'
if (nextToken.type === Lexer.OPERATOR) {
toParse.shift()
operator = nextToken.value as '+' | '-'
}
const right = parseMultiplication()
expression = {left: expression, right, operator}
nextToken = peekNextToken()
}
return expression
}
}
const MACRO_GRAMMAR = [macroPrimitive, macroVariable, macroComment]
export function parseMacroBlocks(tokens: Lexer.Token[]): Tree.MacroBlock[] {
let matchState: MatchState<Tree.MacroBlock> | null = null
const blocks: Tree.MacroBlock[] = []
tokens.forEach(token => {
matchState = matchSyntax(matchState, token, MACRO_GRAMMAR)
if (matchState.nodes) blocks.push(...matchState.nodes)
if (matchState.candidates.length === 0) matchState = null
})
return blocks
}