diff --git a/.gitignore b/.gitignore index 669d022..3d419c3 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,7 @@ target/ # Others *.idea/ *.vagrant/ + +*dumped.out + +tmp/ \ No newline at end of file diff --git a/netasm/back_ends/soft_switch/datapath.py b/netasm/back_ends/soft_switch/datapath.py index a3006c4..049dacf 100644 --- a/netasm/back_ends/soft_switch/datapath.py +++ b/netasm/back_ends/soft_switch/datapath.py @@ -2,7 +2,7 @@ # ## # ## https://github.com/NetASM/NetASM-python # ## -# ## File:# +# ## File: # ## datapath.py # ## # ## Project: @@ -40,9 +40,7 @@ ./pox.py --no-openflow datapath --address=localhost ''' -import sys from ast import literal_eval -from importlib import import_module from Queue import Queue from threading import Thread import logging @@ -60,6 +58,7 @@ from netasm.netasm.core.syntax import * from netasm.netasm.core.common import bitmap_to_ports, ports_to_bitmap from netasm.netasm import validate, execute, optimize, cost +from netasm.front_ends.netasm_dsl import parser class OpenFlowWorker(BackoffWorker): @@ -160,51 +159,48 @@ def up(event): _MAX_PORTS = 64 +_parser = parser.Parser() -def load_policy(switch, policy_name): + +def load_policy(switch, policy_file): if switch.policy: switch.policy.stop() - switch.policy_name = '' + switch.policy_file = '' switch.policy = None try: - if policy_name in sys.modules: - del sys.modules[policy_name] - module = import_module(policy_name) + fd = open(policy_file) + policy, errors_cnt = _parser.parse(fd.read()) + fd.close() + + if errors_cnt > 0: + raise TypeError('%s errors found' % errors_cnt) except ImportError, e: - raise RuntimeError('Must be a valid python module\n' + - 'e.g, full module name,\n' + - ' no .py suffix,\n' + - ' located on the system PYTHONPATH\n' + - '\n' + - 'Exception message for ImportError was:' + e.message) - main = module.main - - if main: - policy = main() - if isinstance(policy, Policy): - try: - validate.type_check.type_check_Policy(policy, _MAX_PORTS) - print "Policy [%s] (type check): passed!" % policy_name - except Exception, e: - print "Policy [%s] (type check): failed... " % policy_name + e.message - switch.policy_name = '' - switch.policy = None - - area, latency = cost.cost_Policy(policy) - print "policy [%s] (original cost): Area=%s, Latency=%s" % (policy_name, area, latency) - - policy = optimize.optimize_Policy(policy) - area, latency = cost.cost_Policy(policy) - print "Policy [%s] (optimize cost): Area=%s, Latency=%s" % (policy_name, area, latency) - - switch.policy_name = policy_name - switch.policy = execute.Execute(policy) - switch.policy.start() - else: - raise RuntimeError("Invalid policy: %s" % (policy_name, )) + raise RuntimeError('Must be a valid policy file\n' + + 'Exception message was:' + e.message) + + if isinstance(policy, Policy): + try: + validate.type_check.type_check_Policy(policy, _MAX_PORTS) + print "Policy [%s] (type check): passed!" % policy_file + except Exception, e: + switch.policy_file = '' + switch.policy = None + raise RuntimeError('Policy [%s] (type check): failed... \n' % policy_file + + 'Exception message was:' + e.message) + + area, latency = cost.cost_Policy(policy) + print "policy [%s] (original cost): Area=%s, Latency=%s" % (policy_file, area, latency) + + policy = optimize.optimize_Policy(policy) + area, latency = cost.cost_Policy(policy) + print "Policy [%s] (optimize cost): Area=%s, Latency=%s" % (policy_file, area, latency) + + switch.policy_file = policy_file + switch.policy = execute.Execute(policy) + switch.policy.start() else: - raise RuntimeError("Invalid policy: %s" % (policy_name, )) + raise RuntimeError("Invalid policy: %s" % policy_file) def _do_ctl(event): @@ -251,16 +247,16 @@ def ra(low, high=None): elif event.first == 'set-policy': ra(2) switch = _switches[event.args[0]] - policy_name = event.args[1] + policy_file = event.args[1] - load_policy(switch, policy_name) + load_policy(switch, policy_file) elif event.first == 'clr-policy': ra(1) switch = _switches[event.args[0]] if switch.policy: switch.policy.stop() - switch.policy_name = '' + switch.policy_file = '' switch.policy = None elif event.first == "add-table-entry": ra(4) @@ -302,7 +298,7 @@ def ra(low, high=None): ra(0) s = [] for switch in _switches.values(): - s.append("Switch %s (%s)" % (switch.name, switch.policy_name)) + s.append("Switch %s (%s)" % (switch.name, switch.policy_file)) for no, p in switch.ports.iteritems(): s.append(" %3s %s" % (no, p.name)) return "\n".join(s) @@ -372,9 +368,9 @@ def __init__(self, **kw): ports = kw.pop('ports', []) kw['ports'] = [] - self.policy_name = kw.pop("policy", '') - if self.policy_name: - load_policy(self, self.policy_name) + self.policy_file = kw.pop("policy", '') + if self.policy_file: + load_policy(self, self.policy_file) super(ProgSwitch, self).__init__(**kw) @@ -480,12 +476,13 @@ def _tx_vendor(self, vendor): def _handle_out_message(self, message, connection): if message['operation'] == 'set-policy': - policy_name = message['data'] + policy_file = message['data'] - load_policy(self, policy_name) + load_policy(self, policy_file) elif message['operation'] == 'clr-policy': if self.policy: self.policy.stop() + self.policy_file = '' self.policy = None elif message['operation'] == 'add-table-entry': t_name = message['data'][0] diff --git a/netasm/examples/front_ends/__init__.py b/netasm/examples/front_ends/__init__.py new file mode 100644 index 0000000..e317e67 --- /dev/null +++ b/netasm/examples/front_ends/__init__.py @@ -0,0 +1 @@ +__author__ = 'shahbaz' diff --git a/netasm/examples/front_ends/netasm_dsl/__init__.py b/netasm/examples/front_ends/netasm_dsl/__init__.py new file mode 100644 index 0000000..e317e67 --- /dev/null +++ b/netasm/examples/front_ends/netasm_dsl/__init__.py @@ -0,0 +1 @@ +__author__ = 'shahbaz' diff --git a/netasm/examples/front_ends/netasm_dsl/standalone/__init__.py b/netasm/examples/front_ends/netasm_dsl/standalone/__init__.py new file mode 100644 index 0000000..e317e67 --- /dev/null +++ b/netasm/examples/front_ends/netasm_dsl/standalone/__init__.py @@ -0,0 +1 @@ +__author__ = 'shahbaz' diff --git a/netasm/examples/front_ends/netasm_dsl/standalone/decrement_loop.netasm b/netasm/examples/front_ends/netasm_dsl/standalone/decrement_loop.netasm new file mode 100644 index 0000000..9e0ceb8 --- /dev/null +++ b/netasm/examples/front_ends/netasm_dsl/standalone/decrement_loop.netasm @@ -0,0 +1,49 @@ +# ################################################################################- +# ## +# ## https://github.com/NetASM/NetASM-python +# ## +# ## File: +# ## decrement_loop.netasm +# ## +# ## Project: +# ## NetASM: A Network Assembly Language for Programmable Dataplanes +# ## +# ## Author: +# ## Muhammad Shahbaz +# ## +# ## Copyright notice: +# ## Copyright (C) 2014 Princeton University +# ## Network Operations and Internet Security Lab +# ## +# ## Licence: +# ## This file is a part of the NetASM development base package. +# ## +# ## This file is free code: you can redistribute it and/or modify it under +# ## the terms of the GNU Lesser General Public License version 2.1 as +# ## published by the Free Software Foundation. +# ## +# ## This package 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 +# ## Lesser General Public License for more details. +# ## +# ## You should have received a copy of the GNU Lesser General Public +# ## License along with the NetASM source package. If not, see +# ## http://www.gnu.org/licenses/. + +.code ( + .fields () + .instrs ( + ADD reg0 4'd16 + LD reg0 4'd4 + LBL "LBL_0" + BR reg0 Eq 4'd0 "LBL_HLT" + OP reg0 reg0 Sub 4'd1 + JMP "LBL_0" + LBL "LBL_HLT" + LD outport_bitmap reg0 + HLT + ) +) + +# TODO: This has some issue with type checking \ No newline at end of file diff --git a/netasm/netasm/core/parse.py b/netasm/examples/front_ends/netasm_dsl/standalone/hub.netasm similarity index 88% rename from netasm/netasm/core/parse.py rename to netasm/examples/front_ends/netasm_dsl/standalone/hub.netasm index 99223a7..3e1ef57 100644 --- a/netasm/netasm/core/parse.py +++ b/netasm/examples/front_ends/netasm_dsl/standalone/hub.netasm @@ -1,9 +1,9 @@ -# ################################################################################ +# ################################################################################- # ## # ## https://github.com/NetASM/NetASM-python # ## # ## File: -# ## parse.py +# ## hub.netasm # ## # ## Project: # ## NetASM: A Network Assembly Language for Programmable Dataplanes @@ -31,6 +31,10 @@ # ## License along with the NetASM source package. If not, see # ## http://www.gnu.org/licenses/. -__author__ = 'shahbaz' - -raise NotImplementedError() \ No newline at end of file +.code ( + .fields () + .instrs ( + OP outport_bitmap inport_bitmap Xor 16'hFFFF + HLT + ) +) \ No newline at end of file diff --git a/netasm/examples/front_ends/netasm_dsl/standalone/learning_switch.netasm b/netasm/examples/front_ends/netasm_dsl/standalone/learning_switch.netasm new file mode 100644 index 0000000..b3ebb64 --- /dev/null +++ b/netasm/examples/front_ends/netasm_dsl/standalone/learning_switch.netasm @@ -0,0 +1,147 @@ +# ################################################################################ +# ## +# ## https://github.com/NetASM/NetASM-python +# ## +# ## File: +# ## learning_switch.netasm +# ## +# ## Project: +# ## NetASM: A Network Assembly Language for Programmable Dataplanes +# ## +# ## Author: +# ## Muhammad Shahbaz +# ## +# ## Copyright notice: +# ## Copyright (C) 2014 Princeton University +# ## Network Operations and Internet Security Lab +# ## +# ## Licence: +# ## This file is a part of the NetASM development base package. +# ## +# ## This file is free code: you can redistribute it and/or modify it under +# ## the terms of the GNU Lesser General Public License version 2.1 as +# ## published by the Free Software Foundation. +# ## +# ## This package 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 +# ## Lesser General Public License for more details. +# ## +# ## You should have received a copy of the GNU Lesser General Public +# ## License along with the NetASM source package. If not, see +# ## http://www.gnu.org/licenses/. + +# NOTE: We are not considering loop avoidance but we do take into account host migration in this example + +.decls ( + # Ethernet address table + eth_match_table = ([(eth_addr 16'd48 Binary)] 16'd16 CAM) + eth_params_table = ([(outport_bitmap 16'd3)] 16'd16 RAM) + + # Index table + index_table = ([(index 16'd16)] 1'd1 RAM) +) + +.code ( + .fields () + + .instrs( + ################## + ## Parse packet ## + ################## + + # Add ethernet header fields + ADD eth_dst 16'd48 + ADD eth_src 16'd48 + ADD eth_type 16'd16 + + # Load fields with default values + LD eth_dst 48'd0 + LD eth_src 48'd0 + LD eth_type 16'd0 + + # Parser packet + # load ethernet header fields from the packet + LD eth_dst 16'd0::l + LD eth_src 16'd48::l + LD eth_type 16'd96::l + + ######################## + ## Lookup MAC address ## + ######################## + + # Add index field + ADD index 16'd16 + + ATM ( + .code ( + .fields (index eth_dst eth_src) + .instrs ( + # Lookup in the match table and store the matched index + LKt index eth_match_table [eth_dst] + BR index Neq 16'd-1 "LBL_LKP_0" + + # Case: there is no match in the match table + # Broadcast the packet + OP outport_bitmap inport_bitmap Xor 16'hFFFF + JMP "LBL_LRN" + + # Case: there is a match in the l2 match table + LBL "LBL_LKP_0" + + # Load output port from the parameters table + LDt [outport_bitmap] eth_params_table index + + ####################### + ## Learn MAC address ## + ####################### + LBL "LBL_LRN" + + # Lookup in the match table and store the matched index + LKt index eth_match_table [eth_src] + BR index Neq 16'd-1 "LBL_LRN_0" + + # Case: there is no match in the match table + # Read the running index from the index table + LDt [index] index_table 1'd0 + + # Store eth_src in the eth_match_table + STt eth_match_table index [(eth_src 48'hFFFFFFFFFFFF)] + + # Store inport_bitmap in the eth_params_table + STt eth_params_table index [inport_bitmap] + + # Increment the running index + OP index index Add 16'd1 + + # Check if the index is less than the MAC_TABLE_SIZE + BR index Lt 16'd16 "LBL_LRN_1" + + # Reset the running index + LD index 16'd0 + + # Store the running index back in the table + LBL "LBL_LRN_1" + + STt index_table 1'd0 [index] + JMP "LBL_HLT" + + # Store the current inport_bitmap in the eth_params_table + LBL "LBL_LRN_0" + + STt eth_params_table index [inport_bitmap] + + # Halt + LBL "LBL_HLT" + HLT + ) + ) + ) + + ########## + ## Halt ## + ########## + LBL "LBL_HLT" + HLT + ) +) \ No newline at end of file diff --git a/netasm/netasm/parse.py b/netasm/examples/front_ends/netasm_dsl/standalone/pass_through_2ports.netasm similarity index 82% rename from netasm/netasm/parse.py rename to netasm/examples/front_ends/netasm_dsl/standalone/pass_through_2ports.netasm index ca1295e..d09d430 100644 --- a/netasm/netasm/parse.py +++ b/netasm/examples/front_ends/netasm_dsl/standalone/pass_through_2ports.netasm @@ -3,7 +3,7 @@ # ## https://github.com/NetASM/NetASM-python # ## # ## File: -# ## parse.py +# ## pass_through_2ports.netasm # ## # ## Project: # ## NetASM: A Network Assembly Language for Programmable Dataplanes @@ -31,6 +31,17 @@ # ## License along with the NetASM source package. If not, see # ## http://www.gnu.org/licenses/. -__author__ = 'shahbaz' +.code ( + .fields() + .instrs( + BR inport_bitmap Eq 2'd2 "LBL_1" + LD outport_bitmap 2'd2 + JMP "LBL_HLT" -from netasm.netasm.core.parse import * \ No newline at end of file + LBL "LBL_1" + LD outport_bitmap 1'd1 + + LBL "LBL_HLT" + HLT + ) +) \ No newline at end of file diff --git a/netasm/examples/netasm/standalone/decrement_loop.py b/netasm/examples/netasm/standalone/decrement_loop.py index 73d6861..be70f41 100644 --- a/netasm/examples/netasm/standalone/decrement_loop.py +++ b/netasm/examples/netasm/standalone/decrement_loop.py @@ -5,7 +5,7 @@ # ## https://github.com/NetASM/NetASM-python # ## # ## File: -# ## hub.py +# ## decrement_loop.py # ## # ## Project: # ## NetASM: A Network Assembly Language for Programmable Dataplanes diff --git a/netasm/front_ends/__init__.py b/netasm/front_ends/__init__.py new file mode 100644 index 0000000..e317e67 --- /dev/null +++ b/netasm/front_ends/__init__.py @@ -0,0 +1 @@ +__author__ = 'shahbaz' diff --git a/netasm/front_ends/netasm_dsl/__init__.py b/netasm/front_ends/netasm_dsl/__init__.py new file mode 100644 index 0000000..bb2763c --- /dev/null +++ b/netasm/front_ends/netasm_dsl/__init__.py @@ -0,0 +1 @@ +__author__ = 'shahbaz' \ No newline at end of file diff --git a/netasm/front_ends/netasm_dsl/dumper.py b/netasm/front_ends/netasm_dsl/dumper.py new file mode 100644 index 0000000..d2a4e08 --- /dev/null +++ b/netasm/front_ends/netasm_dsl/dumper.py @@ -0,0 +1,66 @@ +# ################################################################################ +# ## +# ## https://github.com/NetASM/NetASM-python +# ## +# ## File: +# ## dumper.py +# ## +# ## Project: +# ## NetASM: A Network Assembly Language for Programmable Dataplanes +# ## +# ## Author: +# ## Muhammad Shahbaz +# ## +# ## Copyright notice: +# ## Copyright (C) 2014 Princeton University +# ## Network Operations and Internet Security Lab +# ## +# ## Licence: +# ## This file is a part of the NetASM development base package. +# ## +# ## This file is free code: you can redistribute it and/or modify it under +# ## the terms of the GNU Lesser General Public License version 2.1 as +# ## published by the Free Software Foundation. +# ## +# ## This package 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 +# ## Lesser General Public License for more details. +# ## +# ## You should have received a copy of the GNU Lesser General Public +# ## License along with the NetASM source package. If not, see +# ## http://www.gnu.org/licenses/. + +__author__ = 'shahbaz' + +from optparse import OptionParser +from parser import Parser + + +def main(): + op = OptionParser() + op.add_option('--ifile', action="store", dest="ifile") + op.add_option('--ofile', action="store", dest="ofile") + + op.set_defaults(ofile='./dumped.out') + options, args = op.parse_args() + + if not options.ifile: + print '--ifile flag not specified.' + exit(1) + + ifile = open(options.ifile) + ofile = open(options.ofile, 'w') + + parser = Parser() + policy, errors_cnt = parser.parse(ifile.read()) + + ofile.write(str(policy)) + ofile.write('\n') + ofile.write('Total errors: ' + str(errors_cnt)) + + ifile.close() + ofile.close() + + +main() diff --git a/netasm/front_ends/netasm_dsl/lexer.py b/netasm/front_ends/netasm_dsl/lexer.py new file mode 100644 index 0000000..4f932b3 --- /dev/null +++ b/netasm/front_ends/netasm_dsl/lexer.py @@ -0,0 +1,185 @@ +# ################################################################################ +# ## +# ## https://github.com/NetASM/NetASM-python +# ## +# ## File: +# ## lexer.py +# ## +# ## Project: +# ## NetASM: A Network Assembly Language for Programmable Dataplanes +# ## +# ## Author: +# ## Muhammad Shahbaz +# ## +# ## Copyright notice: +# ## Copyright (C) 2014 Princeton University +# ## Network Operations and Internet Security Lab +# ## +# ## Licence: +# ## This file is a part of the NetASM development base package. +# ## +# ## This file is free code: you can redistribute it and/or modify it under +# ## the terms of the GNU Lesser General Public License version 2.1 as +# ## published by the Free Software Foundation. +# ## +# ## This package 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 +# ## Lesser General Public License for more details. +# ## +# ## You should have received a copy of the GNU Lesser General Public +# ## License along with the NetASM source package. If not, see +# ## http://www.gnu.org/licenses/. + +__author__ = 'shahbaz' + +from ply import lex +from ply.lex import TOKEN + + +class Lexer: + def __init__(self): + self.lexer = None + + keywords = ( + # Operators + 'Add', 'Sub', 'Mul', 'Div', 'And', 'Or', 'Xor', + + # Comparators + 'Eq', 'Neq', 'Lt', 'Gt', 'Le', 'Ge', + + # Instructions + 'ID', 'DRP', 'CTR', 'ADD', 'RMV', + 'LD', 'ST', 'OP', 'PUSH', 'POP', + 'BR', 'JMP', 'LBL', 'LDt', 'STt', + 'INCt', 'LKt', 'CRC', 'HSH', 'HLT', + 'CNC', 'ATM', 'SEQ', + + # Match types + 'Binary', 'Ternary', + + # Table types + 'CAM', 'RAM', 'HASH' + ) + + keywords_map = {} + for keyword in keywords: + keywords_map[keyword] = keyword + + tokens = ( + # Identifiers and strings + 'IDN', 'STR', + + # Numbers and (Hexa)Decimal + 'NUM', 'DEC', 'HEX', + + # Delimeters + 'LPAREN', 'RPAREN', # () + 'LBRACKET', 'RBRACKET', # [] + + # Assignment + 'EQUAL', + + # Location + 'LOC', + + # Special + 'S_DECLS', 'S_CODE', 'S_FIELDS', 'S_INSTRS' + ) + + tokens += keywords + + t_ignore = ' \t' + + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + + t_EQUAL = r'=' + + t_LOC = r'::[lL]' + + t_S_DECLS = r'\.decls' + t_S_CODE = r'\.code' + t_S_FIELDS = r'\.fields' + t_S_INSTRS = r'\.instrs' + + t_NUM = r'[0-9]+' + t_DEC = r"'[dD][-+]?[0-9]+" + t_HEX = r"'[hH][0-9a-fA-F]+" + + identifier = r'[a-zA-Z_][0-9a-zA-Z_]*' + + @TOKEN(identifier) + def t_IDN(self, t): + t.type = self.keywords_map.get(t.value, "IDN") + return t + + t_STR = '\"[^\"]*\"' + + comment = r'[#][^\n]*' + + @TOKEN(comment) + def t_comment(self, t): + pass + + newline = r'\n+' + + @TOKEN(newline) + def t_newline(self, t): + t.lexer.lineno += len(t.value) + + def t_error(self, t): + print "Illegal character '%s'" % t.value[0] + t.lexer.skip(1) + + def warning(self, t): + print "Illegal character '%s'" % t.value[0] + t.lexer.skip(1) + + def reset_lineno(self): + self.lexer.lineno = 1 + + def get_lineno(self): + return self.lexer.lineno + + def input(self, text): + self.lexer.input(text) + + def token(self): + t = self.lexer.token() + return t + + def find_tok_column(self, token): + last_cr = self.lexer.lexdata.rfind('\n', 0, token.lexpos) + return token.lexpos - last_cr + + def build(self, **kwargs): + self.lexer = lex.lex(module=self, **kwargs) + + +if __name__ == '__main__': + lexer = Lexer() + lexer.build() + lexer.input(''' +.code ( + .fields () + .instrs ( + ADD reg0 16 + LD reg0 16'd-4 + LBL "LBL_0" + BR reg0 Eq 16'd0 "LBL_HLT" + OP reg0 reg0 Sub 'h1 + JMP "LBL_0" + LBL "LBL_HLT" + LD outport_bitmap reg0 + HLT + ) +) + ''') + + while True: + t = lexer.token() + if not t: break + print t diff --git a/netasm/front_ends/netasm_dsl/netasm_dsl.bnf b/netasm/front_ends/netasm_dsl/netasm_dsl.bnf new file mode 100644 index 0000000..9ec8a8e --- /dev/null +++ b/netasm/front_ends/netasm_dsl/netasm_dsl.bnf @@ -0,0 +1,155 @@ +(*############################################################################# +# +# https://github.com/NetASM/NetASM-python +# +# File: +# netasm_dsl.bnf +# +# Project: +# NetASM: A Network Assembly Language for Programmable Dataplanes +# +# Author: +# Muhammad Shahbaz +# +# Copyright notice: +# Copyright (C) 2014 Princeton University +# Network Operations and Internet Security Lab +# +# Licence: +# This file is a part of the NetASM development base package. +# +# This file is free code: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License version 2.1 as +# published by the Free Software Foundation. +# +# This package 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with the NetASM source package. If not, see +# http://www.gnu.org/licenses/. +*) + +(* NetASM Domain-Specific Language *) + + ::= [0-9]+ + + ::= '[dD][-+]?[0-9]+ + + ::= '[hH][0-9a-fA-F]+ + + ::= + ||= + + ::= + + ::= [a-zA-Z_][0-9a-zA-Z_]* + + = "[^"]*" + + ::= + + ::= + + ::= * + + ::= + +