#!/usr/bin/env python # coding=utf-8 # # Copyright (C) 2022 Jonathan Neuhauser (jonathan.neuhauser@outlook.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # """Grammar for parsing HPGL input files""" import pyparsing as pp from hpgl_input_sm import HPGLStateMachine pp.ParserElement.enablePackrat() pp.enable_all_warnings() integer = pp.pyparsing_common.integer sinteger = pp.pyparsing_common.signed_integer flt = pp.pyparsing_common.number comma = pp.Optional(pp.Suppress(",")) cpair = flt("X*") + comma + flt("Y*") def build_vector_parsers(stm: HPGLStateMachine): """Parsers of the vector group""" # Lines line_command = pp.Group( ( (pp.Literal("PA") | "PR" | "PD" | "PU")("key") + pp.Opt( cpair + pp.ZeroOrMore(comma + cpair) + pp.Opt(comma + sinteger("leftover*")) ) ).add_parse_action(stm.line_command) ) # Circles and arcs ci_command = pp.Group( ( "CI" + sinteger("Radius") + pp.Opt(comma + flt("Chord_angle")) ).add_parse_action(stm.ci_command) ) arc_command = pp.Group( ( (pp.Literal("AA") | "AR")("key") + cpair + comma + sinteger("sweep") + pp.Opt(comma + flt("Chord_angle*")) ).add_parse_action(stm.arc_command) ) at_command = pp.Group( ( "AT" + cpair + comma + cpair + pp.Opt(comma + flt("Chord_angle*")) ).add_parse_action(stm.at_command) ) # Beziers bezier = cpair + comma + cpair + comma + cpair bezier_command = pp.Group( ( (pp.Literal("BR") | "BZ")("key") + bezier("B*") + pp.ZeroOrMore(comma + bezier("B*")) ).add_parse_action(stm.bezier_command) ) # Polyline encoded pe_command = pp.Group( ( "PE" + pp.Regex("[^;]*").setWhitespaceChars("")("data") + pp.Suppress(";") ).add_parse_action(stm.polyline_encoded) ) return ( line_command | ci_command | arc_command | bezier_command | at_command | pe_command ) def build_polygon_parsers(stm: HPGLStateMachine): """Build parser object from the polygon group""" pm_command = pp.Group( ("PM" + pp.Opt(pp.Word("012", exact=1), default="0")("value")).add_parse_action( stm.pm_command ) ) rectangle_command = pp.Group( ((pp.Literal("EA") | "ER" | "RA" | "RR")("key") + cpair).add_parse_action( stm.rectangle_command ) ) polygon_command = pp.Group( ( ( pp.Literal("FP")("key") + pp.Opt(pp.Word("01", exact=1), default="0")("fillmode") ) | pp.Literal("EP")("key") ).add_parse_action(stm.edge_fill_polygon) ) wedge_command = pp.Group( ( (pp.Literal("EW") | "WG")("key") + sinteger("radius") + comma + flt("start_angle") + comma + flt("sweep") + pp.Opt(comma + flt("Chord_angle*")) ).add_parse_action(stm.wedge_command) ) return pm_command | rectangle_command | polygon_command | wedge_command def build_linefill_parsers(stm: HPGLStateMachine): """Build parsers from the Line and Fill Attributes Group""" kind = pp.Word("123", exact=1)("kind*") la_command = pp.Group( "LA" + pp.Opt(kind + comma + integer("value*")) + (comma + kind + comma + integer("value*")) * (0, 2) ) lt_command = pp.Group( "LT" + pp.Opt( integer("linetype") + pp.Opt(comma + flt("pattern_length") + pp.Opt(comma + flt("mode"))) ) ) pw_command = pp.Group("PW" + pp.Opt(flt("width") + pp.Opt(comma + integer("pen")))) # maybe we can combine those two? ft_command = pp.Group( "FT" + pp.Opt( (pp.Literal("1") | "2" | "3" | "4" | "10" | "11" | "21" | "22")("type") + pp.Opt(comma + flt("option1") + pp.Opt(comma + flt("option2"))) ) ) tr_command = pp.Group("TR" + pp.Opt(pp.Literal("0") | "1", "1")("transparency")) ul_command = pp.Group( "UL" + pp.Opt(integer("index") + (comma + flt("gap*")) * (0, 20)) ) wu_command = pp.Group("WU" + pp.Opt(pp.Word("01", exact=1)("units"))) sp_command = pp.Group("SP" + pp.Opt(integer("pen"), 0)) return ( sp_command | la_command | lt_command | pw_command | ft_command | tr_command | ul_command | wu_command ).add_parse_action(stm.style_command) def build_configuration_parser(stm: HPGLStateMachine): """Commands of the configurtion group""" co_command = pp.Literal("CO") + pp.Suppress('"') + ... + pp.Suppress('"') in_command = pp.Literal("IN").add_parse_action(stm.initialize) ip_command = pp.Group( (pp.Literal("IP") | "IR")("key") + pp.Opt(cpair + pp.Opt(comma + cpair)) ) ro_command = pp.Group( "RO" + pp.Optional(pp.Literal("0") | "90" | "180" | "270", "0")("angle") ) iw_command = pp.Group("IW" + pp.Opt(cpair + comma + cpair)) sc_command = pp.Group( pp.Literal("SC") + ( pp.Optional( flt("Xmin") + comma + flt("Xmax") + comma + flt("Ymin") + comma + flt("Ymax") + pp.Optional( (comma + (pp.Literal("0") | "2")("type")) | ( comma + pp.Literal("1")("type") + pp.Optional(comma + flt("left") + comma + flt("bottom")) ) ) ) ) ) return (in_command | co_command) | ( ip_command | sc_command | ro_command | iw_command ).add_parse_action(stm.transform_command) def build_parser(stm: HPGLStateMachine): """Assemble the command groups""" command = ( build_configuration_parser(stm) | build_vector_parsers(stm) | build_polygon_parsers(stm) | build_linefill_parsers(stm) # Commands we don't understand | pp.Group(pp.Word(pp.alphas, exact=2) + pp.Opt(pp.Word(pp.nums + ",. ")))("u*") ) # The parse actions of the documents are final cleanup document = pp.ZeroOrMore(command + pp.Opt(pp.Suppress(";"))).add_parse_action( stm.finalize_path ) pp.autoname_elements() return document