diff --git a/docs/Languages.md b/docs/Languages.md index d5a02920a9..dd7c78dea8 100644 --- a/docs/Languages.md +++ b/docs/Languages.md @@ -144,6 +144,7 @@ - OCL (`ocl`) - OpenEdge ABL (`openedge`) - OpenType Feature File (`opentype_feature_file`) +- P4 (`p4`) - Pascal (`pascal`) - Perl (`perl`) - PHP (`php`) diff --git a/lib/rouge/demos/p4 b/lib/rouge/demos/p4 new file mode 100644 index 0000000000..ed4992b075 --- /dev/null +++ b/lib/rouge/demos/p4 @@ -0,0 +1,99 @@ +#include +#include + +const bit<16> TYPE_IPV4 = 0x800; +typedef bit<9> egressSpec_t; +typedef bit<48> macAddr_t; +typedef bit<32> ip4Addr_t; + +header ethernet_t { + macAddr_t dstAddr; + macAddr_t srcAddr; + bit<16> etherType; +} + +header ipv4_t { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + ip4Addr_t srcAddr; + ip4Addr_t dstAddr; +} + +struct metadata { /* empty */ } + +struct headers { + ethernet_t ethernet; + ipv4_t ipv4; +} + +parser MyParser(packet_in packet, + out headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + + state start { transition parse_ethernet; } + + state parse_ethernet { + packet.extract(hdr.ethernet); + transition select(hdr.ethernet.etherType) { + TYPE_IPV4: parse_ipv4; + default: accept; + } + } + + state parse_ipv4 { + packet.extract(hdr.ipv4); + transition accept; + } + +} + +control MyIngress(inout headers hdr, + inout metadata meta, + inout standard_metadata_t standard_metadata) { + action drop() { + mark_to_drop(standard_metadata); + } + + action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { + standard_metadata.egress_spec = port; + hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; + hdr.ethernet.dstAddr = dstAddr; + hdr.ipv4.ttl = hdr.ipv4.ttl - 1; + } + + table ipv4_lpm { + key = { hdr.ipv4.dstAddr: lpm; } + actions = { ipv4_forward; drop; NoAction; } + size = 1024; + default_action = drop(); + } + + apply { + if (hdr.ipv4.isValid()) { + ipv4_lpm.apply(); + } + } +} + + +control MyDeparser(packet_out packet, in headers hdr) { + apply { + packet.emit(hdr.ethernet); + packet.emit(hdr.ipv4); + } +} + +V1Switch( +MyParser(), +MyIngress(), +MyDeparser() +) main; diff --git a/lib/rouge/lexers/p4.rb b/lib/rouge/lexers/p4.rb new file mode 100644 index 0000000000..87e656f13f --- /dev/null +++ b/lib/rouge/lexers/p4.rb @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +module Rouge + module Lexers + class P4 < RegexLexer + tag 'p4' + title 'P4' + desc 'The P4 programming language' + filenames '*.p4' + mimetypes 'text/x-p4' + + def self.keywords + @keywords ||= %w( + abstract action actions apply const default default_action else enum + entries extern exit if in inout key list out package packet_in + packet_out return size select switch this transition tuple type + typedef + ) + end + + def self.operators + @operators ||= %w( + \|\+\| \|-\| \? \& \&\&\& < > << >> \* \| ~ \^ - \+ / + \# \. = != <= >= \+\+ + ) + end + + def self.decls + @decls ||= %w( + control header header_union parser state struct table + value_set + ) + end + + def self.builtins + @builtins ||= %w( + bit bool error extract int isValid setValid setInvalid match_kind + string varbit verify void + ) + end + + state :whitespace do + rule %r/\s+/m, Text + end + + state :comment do + rule %r((//).*$\n?), Comment::Single + rule %r/\/\*(?:(?!\*\/).)*\*\//m, Comment::Multiline + end + + state :number do + rule %r/([0-9]+[sw])?0[oO][0-7_]+/, Num + rule %r/([0-9]+[sw])?0[xX][0-9a-fA-F_]+/, Num + rule %r/([0-9]+[sw])?0[bB][01_]+/, Num + rule %r/([0-9]+[sw])?0[dD][0-9_]+/, Num + rule %r/([0-9]+[sw])?[0-9_]+/, Num + end + + id = /[\p{XID_Start}_]\p{XID_Continue}*/ + string_element = /\\"|[^"]/x + + state :root do + mixin :whitespace + mixin :comment + + rule %r/#\s*#{id}/, Comment::Preproc + rule %r/\b(?:#{P4.keywords.join('|')})\b/, Keyword + rule %r/\b(?:#{P4.decls.join('|')})\b/, Keyword::Declaration + rule %r/\b(?:#{P4.builtins.join('|')})\b/, Name::Builtin + rule %r/\b#{id}_[th]\b/x, Name::Class + rule %r/(?:#{P4.operators.join('|')})/x, Operator + rule %r/[(){}\[\]<>,:;\.]/, Punctuation + mixin :number + rule %r/@#{id}/x, Name::Label + rule %r/#{id}/x, Text + rule %r/"(?: #{string_element} )*"/x, Str::String + end + end + end +end diff --git a/spec/lexers/p4_spec.rb b/spec/lexers/p4_spec.rb new file mode 100644 index 0000000000..d04479ebe2 --- /dev/null +++ b/spec/lexers/p4_spec.rb @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Lexers::P4 do + let(:subject) { Rouge::Lexers::P4.new } + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'foo.p4' + end + + it 'guesses by mimetype' do + assert_guess :mimetype => 'text/x-p4' + end + end +end diff --git a/spec/visual/samples/p4 b/spec/visual/samples/p4 new file mode 100644 index 0000000000..bf280e9a82 --- /dev/null +++ b/spec/visual/samples/p4 @@ -0,0 +1,354 @@ +// A line comment. + +/* +A multiline comment. +*/ + +// String literals + +"simple string" +"string \" with \" embedded \" quotes" +"string with embedded +line terminator" + +// Integer literals + +32w255 +32w0d255 +32w0xFF +32s0xFF +8w0b10101010 +8w0b_1010_1010 +8w170 +8s0b1010_1010 +16w0377 +16w0o377 + +// Structs and headers + +header h1_t { + bit<8> f1; + bit<8> f2; +} +struct s1_t { + h1_t h1a; + bit<3> a; + bit<7> b; +} +struct s2_t { + h1_t h1b; + s1_t s1; + bit<5> c; +} + +// Externs +extern void f(inout bit x, in bit y); +extern bit g(inout bit z); + +// File "very_simple_switch_model.p4" +// Very Simple Switch P4 declaration +// core library needed for packet_in and packet_out definitions +# include +/* Various constants and structure declarations */ +/* ports are represented using 4-bit values */ +typedef bit<4> PortId; +/* only 8 ports are "real" */ +const PortId REAL_PORT_COUNT = 4w8; // 4w8 is the number 8 in 4 bits +/* metadata accompanying an input packet */ +struct InControl { + PortId inputPort; +} +/* special input port values */ +const PortId RECIRCULATE_IN_PORT = 0xD; +const PortId CPU_IN_PORT = 0xE; +/* metadata that must be computed for outgoing packets */ +struct OutControl { + PortId outputPort; +} +/* special output port values for outgoing packet */ +const PortId DROP_PORT = 0xF; +const PortId CPU_OUT_PORT = 0xE; +const PortId RECIRCULATE_OUT_PORT = 0xD; +/* Prototypes for all programmable blocks */ +/** + * Programmable parser. + * @param type of headers; defined by user + * @param b input packet + * @param parsedHeaders headers constructed by parser + */ +parser Parser(packet_in b, + out H parsedHeaders); +/** + * Match-action pipeline + * @param type of input and output headers + * @param headers headers received from the parser and sent to the deparser + * @param parseError error that may have surfaced during parsing + * @param inCtrl information from architecture, accompanying input packet + * @param outCtrl information for architecture, accompanying output packet + */ +control Pipe(inout H headers, + in error parseError,// parser error + in InControl inCtrl,// input port + out OutControl outCtrl); // output port +/** + * VSS deparser. + * @param type of headers; defined by user + * @param b output packet + * @param outputHeaders headers for output packet + */ +control Deparser(inout H outputHeaders, + packet_out b); +/** + * Top-level package declaration - must be instantiated by user. + * The arguments to the package indicate blocks that + * must be instantiated by the user. + * @param user-defined type of the headers processed. + */ +package VSS(Parser p, + Pipe map, + Deparser d); +// Architecture-specific objects that can be instantiated +// Checksum unit +extern Checksum16 { + Checksum16(); // constructor + void clear(); // prepare unit for computation + void update(in T data); // add data to checksum + void remove(in T data); // remove data from existing checksum + bit<16> get(); // get the checksum for the data added since last clear +} + +// Include P4 core library +# include + +// Include very simple switch architecture declarations +# include "very_simple_switch_model.p4" + +// This program processes packets comprising an Ethernet and an IPv4 +// header, and it forwards packets using the destination IP address + +typedef bit<48> EthernetAddress; +typedef bit<32> IPv4Address; + +// Standard Ethernet header +header Ethernet_h { + EthernetAddress dstAddr; + EthernetAddress srcAddr; + bit<16> etherType; +} + +// IPv4 header (without options) +header IPv4_h { + bit<4> version; + bit<4> ihl; + bit<8> diffserv; + bit<16> totalLen; + bit<16> identification; + bit<3> flags; + bit<13> fragOffset; + bit<8> ttl; + bit<8> protocol; + bit<16> hdrChecksum; + IPv4Address srcAddr; + IPv4Address dstAddr; +} + +// Structure of parsed headers +struct Parsed_packet { + Ethernet_h ethernet; + IPv4_h ip; +} + +// Parser section + +// User-defined errors that may be signaled during parsing +error { + IPv4OptionsNotSupported, + IPv4IncorrectVersion, + IPv4ChecksumError +} + +parser TopParser(packet_in b, out Parsed_packet p) { + Checksum16() ck; // instantiate checksum unit + + state start { + b.extract(p.ethernet); + transition select(p.ethernet.etherType) { + 0x0800: parse_ipv4; + // no default rule: all other packets rejected + } + } + + state parse_ipv4 { + b.extract(p.ip); + verify(p.ip.version == 4w4, error.IPv4IncorrectVersion); + verify(p.ip.ihl == 4w5, error.IPv4OptionsNotSupported); + ck.clear(); + ck.update(p.ip); + // Verify that packet checksum is zero + verify(ck.get() == 16w0, error.IPv4ChecksumError); + transition accept; + } +} + +// Match-action pipeline section + +control TopPipe(inout Parsed_packet headers, + in error parseError, // parser error + in InControl inCtrl, // input port + out OutControl outCtrl) { + IPv4Address nextHop; // local variable + + /** + * Indicates that a packet is dropped by setting the + * output port to the DROP_PORT + */ + action Drop_action() { + outCtrl.outputPort = DROP_PORT; + } + + /** + * Set the next hop and the output port. + * Decrements ipv4 ttl field. + * @param ipv4_dest ipv4 address of next hop + * @param port output port + */ + action Set_nhop(IPv4Address ipv4_dest, PortId port) { + nextHop = ipv4_dest; + headers.ip.ttl = headers.ip.ttl - 1; + outCtrl.outputPort = port; + } + + /** + * Computes address of next IPv4 hop and output port + * based on the IPv4 destination of the current packet. + * Decrements packet IPv4 TTL. + * @param nextHop IPv4 address of next hop + */ + table ipv4_match { + key = { headers.ip.dstAddr: lpm; } // longest-prefix match + actions = { + Drop_action; + Set_nhop; + } + size = 1024; + default_action = Drop_action; + } + + /** + * Send the packet to the CPU port + */ + action Send_to_cpu() { + outCtrl.outputPort = CPU_OUT_PORT; + } + + /** + * Check packet TTL and send to CPU if expired. + */ + table check_ttl { + key = { headers.ip.ttl: exact; } + actions = { Send_to_cpu; NoAction; } + const default_action = NoAction; // defined in core.p4 + } + + /** + * Set the destination MAC address of the packet + * @param dmac destination MAC address. + */ + action Set_dmac(EthernetAddress dmac) { + headers.ethernet.dstAddr = dmac; + } + + /** + * Set the destination Ethernet address of the packet + * based on the next hop IP address. + * @param nextHop IPv4 address of next hop. + */ + table dmac { + key = { nextHop: exact; } + actions = { + Drop_action; + Set_dmac; + } + size = 1024; + default_action = Drop_action; + } + + /** + * Set the source MAC address. + * @param smac: source MAC address to use + */ + action Set_smac(EthernetAddress smac) { + headers.ethernet.srcAddr = smac; + } + + /** + * Set the source mac address based on the output port. + */ + table smac { + key = { outCtrl.outputPort: exact; } + actions = { + Drop_action; + Set_smac; + } + size = 16; + default_action = Drop_action; + } + + apply { + if (parseError != error.NoError) { + Drop_action(); // invoke drop directly + return; + } + + ipv4_match.apply(); // Match result will go into nextHop + if (outCtrl.outputPort == DROP_PORT) return; + + check_ttl.apply(); + if (outCtrl.outputPort == CPU_OUT_PORT) return; + + dmac.apply(); + if (outCtrl.outputPort == DROP_PORT) return; + + smac.apply(); + } +} + +// deparser section +control TopDeparser(inout Parsed_packet p, packet_out b) { + Checksum16() ck; + apply { + b.emit(p.ethernet); + if (p.ip.isValid()) { + ck.clear(); // prepare checksum unit + p.ip.hdrChecksum = 16w0; // clear checksum + ck.update(p.ip); // compute new checksum. + p.ip.hdrChecksum = ck.get(); + } + b.emit(p.ip); + } +} + +// Instantiate the top-level VSS package +VSS(TopParser(), + TopPipe(), + TopDeparser()) main; + +extern void h(in bit<32> a, in bool b = true); // default value + +// function calls +h(10); // same as h(10, true); +h(a = 10); // same as h(10, true); +h(a = 10, b = true); + +struct Empty {} +control nothing(inout Empty h, inout Empty m) { + apply {} +} + +parser parserProto(packet_in p, out H h, inout M m); +control controlProto(inout H h, inout M m); + +package pack(@optional parserProto _parser, // optional parameter + controlProto _control = nothing()); // default parameter value + +pack() main; // No value for _parser, _control is an instance of nothing()