From 1639235a48dbed75c2563c9a497b41c31a2a1bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Mon, 8 Aug 2022 04:39:20 +0200 Subject: Start X11 and web frontends for xC For this, we needed a wire protocol. After surveying available options, it was decided to implement an XDR-like protocol code generator in portable AWK. It now has two backends, per each of: - xF, the X11 frontend, is in C, and is meant to be the primary user interface in the future. - xP, the web frontend, relies on a protocol proxy written in Go, and is meant for use on-the-go (no pun intended). They are very much work-in-progress proofs of concept right now, and the relay protocol is certain to change. --- xC-gen-proto.awk | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 xC-gen-proto.awk (limited to 'xC-gen-proto.awk') diff --git a/xC-gen-proto.awk b/xC-gen-proto.awk new file mode 100644 index 0000000..d5f19fb --- /dev/null +++ b/xC-gen-proto.awk @@ -0,0 +1,303 @@ +# xC-gen-proto.awk: an XDR-derived code generator for network protocols. +# +# Copyright (c) 2022, Přemysl Eric Janouch +# SPDX-License-Identifier: 0BSD +# +# You may read RFC 4506 for context, however it is only a source of inspiration. +# Grammar is easy to deduce from the parser. +# +# Native types: bool, u{8,16,32,64}, i{8,16,32,64}, string +# +# Don't define any new types, unless you hate yourself, then it's okay to do so. +# Both backends are a pain in the arse, for different reasons. +# +# All numbers are encoded in big-endian byte order. +# Booleans are one byte each. +# Strings must be valid UTF-8, use u8<> to lift that restriction. +# String and array lengths are encoded as u32. +# Enumeration values automatically start at 1, and are encoded as i32. +# Any struct or union field may be a variable-length array. +# +# Message framing is done externally, but also happens to prefix u32 lengths. +# +# Usage: env LC_ALL=C awk -v prefix=Relay \ +# -f xC-gen-proto.awk < xC-proto \ +# -f xC-gen-proto-{c,go}.awk > xC-proto.{c,go} | {clang-format,gofmt} + +# --- Utilities ---------------------------------------------------------------- + +function cameltosnake(s) { + while (match(s, /[[:lower:]][[:upper:]]/)) { + s = substr(s, 1, RSTART) "_" \ + tolower(substr(s, RSTART + 1, RLENGTH - 1)) \ + substr(s, RSTART + RLENGTH) + } + return tolower(s) +} + +function snaketocamel(s) { + s = toupper(substr(s, 1, 1)) tolower(substr(s, 2)) + while (match(s, /_[[:alnum:]]/)) { + s = substr(s, 1, RSTART - 1) \ + toupper(substr(s, RSTART + 1, RLENGTH - 1)) \ + substr(s, RSTART + RLENGTH) + } + return s +} + +function decapitalize(s) { + if (match(s, /[[:upper:]][[:lower:]]/)) { + return tolower(substr(s, 1, 1)) substr(s, 2) + } + return s +} + +function indent(s) { + if (!s) + return s + + gsub(/\n/, "\n\t", s) + sub(/\t*$/, "", s) + return "\t" s +} + +function append(a, key, value) { + a[key] = a[key] value +} + +# --- Parsing ------------------------------------------------------------------ + +function fatal(message) { + print "// " FILENAME ":" FNR ": fatal error: " message + print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr" + exit 1 +} + +function skipcomment() { + do { + if (match($0, /[*][/]/)) { + $0 = substr($0, RSTART + RLENGTH) + return + } + } while (getline > 0) + fatal("unterminated block comment") +} + +function nexttoken() { + do { + if (match($0, /^[[:space:]]+/)) { + $0 = substr($0, RLENGTH + 1) + } else if (match($0, /^[/][/].*/)) { + $0 = "" + } else if (match($0, /^[/][*]/)) { + $0 = substr($0, RLENGTH + 1) + skipcomment() + } else if (match($0, /^[[:alpha:]][[:alnum:]_]*/)) { + Token = substr($0, 1, RLENGTH) + $0 = substr($0, RLENGTH + 1) + return Token + } else if (match($0, /^(0[xX][0-9a-fA-F]+|[1-9][0-9]*)/)) { + Token = substr($0, 1, RLENGTH) + $0 = substr($0, RLENGTH + 1) + return Token + } else if (/./) { + Token = substr($0, 1, 1) + $0 = substr($0, 2) + return Token + } + } while (/./ || getline > 0) + Token = "" + return Token +} + +function expect(v) { + if (!v) + fatal("broken expectations at `" Token "' before `" $0 "'") + return v +} + +function accept(what) { + if (Token != what) + return 0 + nexttoken() + return 1 +} + +function identifier( v) { + if (Token !~ /^[[:alpha:]]/) + return 0 + v = Token + nexttoken() + return v +} + +function number( v) { + if (Token !~ /^[0-9]/) + return 0 + v = Token + nexttoken() + return v +} + +function readnumber( ident) { + ident = identifier() + if (!ident) + return expect(number()) + if (!(ident in Consts)) + fatal("unknown constant: " ident) + return Consts[ident] +} + +function defconst( ident, num) { + if (!accept("const")) + return 0 + + ident = expect(identifier()) + expect(accept("=")) + num = readnumber() + if (ident in Consts) + fatal("constant redefined: " ident) + + Consts[ident] = num + codegen_constant(ident, num) + return 1 +} + +function readtype( ident) { + ident = deftype() + if (ident) + return ident + + ident = identifier() + if (!ident) + return 0 + + if (!(ident in Types)) + fatal("unknown type: " ident) + return ident +} + +function defenum( name, ident, value, cg) { + delete cg[0] + + name = expect(identifier()) + expect(accept("{")) + while (!accept("}")) { + ident = expect(identifier()) + value = value + 1 + if (accept("=")) + value = readnumber() + if (!value) + fatal("enumeration values cannot be zero") + expect(accept(",")) + append(EnumValues, name, SUBSEP ident) + if (EnumValues[name, ident]++) + fatal("duplicate enum value: " ident) + codegen_enum_value(name, ident, value, cg) + } + + Types[name] = "enum" + codegen_enum(name, cg) + return name +} + +function readfield(out, nonvoid) { + nonvoid = !accept("void") + if (nonvoid) { + out["type"] = expect(readtype()) + out["name"] = expect(identifier()) + # TODO: Consider supporting XDR's VLA length limits here. + # TODO: Consider supporting XDR's fixed-length syntax for string limits. + out["isarray"] = accept("<") && expect(accept(">")) + } + expect(accept(";")) + return nonvoid +} + +function defstruct( name, d, cg) { + delete d[0] + delete cg[0] + + name = expect(identifier()) + expect(accept("{")) + while (!accept("}")) { + if (readfield(d)) + codegen_struct_field(d, cg) + } + + Types[name] = "struct" + codegen_struct(name, cg) + return name +} + +function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i, unseen) { + delete cg[0] + delete scg[0] + delete d[0] + + name = expect(identifier()) + expect(accept("switch")) + expect(accept("(")) + tag["type"] = tagtype = expect(readtype()) + tag["name"] = expect(identifier()) + expect(accept(")")) + + if (Types[tagtype] != "enum") + fatal("not an enum type: " tagtype) + codegen_union_tag(tag, cg) + + split(EnumValues[tagtype], a, SUBSEP) + for (i in a) + unseen[a[i]]++ + + expect(accept("{")) + while (!accept("}")) { + if (accept("case")) { + if (tagvalue) + codegen_union_struct(name, tagvalue, cg, scg) + + tagvalue = expect(identifier()) + expect(accept(":")) + if (!unseen[tagvalue]--) + fatal("no such value or duplicate case: " tagtype "." tagvalue) + codegen_struct_tag(tag, scg) + } else if (tagvalue) { + if (readfield(d)) + codegen_struct_field(d, scg) + } else { + fatal("union fields must fall under a case") + } + } + if (tagvalue) + codegen_union_struct(name, tagvalue, cg, scg) + + # What remains non-zero in unseen[2..] is simply not recognized/allowed. + Types[name] = "union" + codegen_union(name, cg) + return name +} + +function deftype() { + if (accept("enum")) + return defenum() + if (accept("struct")) + return defstruct() + if (accept("union")) + return defunion() + return 0 +} + +BEGIN { + PrefixLower = "relay_" + PrefixUpper = "RELAY_" + PrefixCamel = "Relay" + + print "// Generated by xC-gen-proto.awk. DO NOT MODIFY." + codegen_begin() + + nexttoken() + while (Token != "") { + expect(defconst() || deftype()) + expect(accept(";")) + } +} -- cgit v1.2.3