Skip to content

Instantly share code, notes, and snippets.

@kpp
Created May 18, 2019 19:57
Show Gist options
  • Save kpp/a147cb55125ff480111ab23df8f1aad2 to your computer and use it in GitHub Desktop.
Save kpp/a147cb55125ff480111ab23df8f1aad2 to your computer and use it in GitHub Desktop.
type Message =
{ kind: "request", request: string, fields: [string, string][] } |
{ kind: "response", response: string, fields: [string, string][] } |
{ kind: "event", event: string, fields: [string, string][] }
type Enum = { kind: "enum", enum: string, items: string[] }
type SpecItem = Message | Enum
function messageName(msg: Message): string {
switch(msg.kind) {
case "request":
return msg.request
case "response":
return msg.response
case "event":
return msg.event
}
}
class SpecParser {
state: SpecItem | null
result: SpecItem[]
constructor() {
this.state = null
this.result = []
}
parse(input: string): SpecItem[] {
const lines = input.split("\n")
.filter(l => l != "")
lines.forEach(line => {
this.parseLine(line)
});
this.completeItem()
return this.result
}
parseLine(line: string) {
const words = line.split(" ")
.filter(w => w != "")
if(line.charAt(0) != ' ') {
this.completeItem()
switch(words[0]) {
case "request": {
this.state = { kind: "request", request: words[1], fields: [] }
break
}
case "response": {
this.state = { kind: "response", response: words[1], fields: [] }
break
}
case "event": {
this.state = { kind: "event", event: words[1], fields: [] }
break
}
case "enum": {
this.state = { kind: "enum", enum: words[1], items: [] }
break
}
default:
throw new Error("Invalid spec item")
}
}
else if(notBlank(line)) {
if(this.state.kind != "enum") {
(this.state as Message).fields.push([words[0], words[1]])
}
else {
(this.state as Enum).items.push(words[0])
}
}
}
completeItem() {
if (this.state != null) {
this.result.push(this.state)
}
}
}
function notBlank(line: string): boolean {
return line.match(/\w+/) != null
}
function generateRustEnum(spec: SpecItem[]): string {
const messages = spec.filter(i => i.kind != "enum") as Message[]
let enums = []
messages.forEach(m => {
const name = messageName(m)
const fields = m.fields
.map(f => `${f[0]}: ${f[1]}`)
.join(', ')
enums.push(`${name} { ${fields} }`)
})
return enums.join(",\n")
}
function tsType(ty: string): string {
switch(ty) {
case "String":
return "string"
case "u32":
return "number"
case "u64":
return "number"
case "usize":
return "number"
case "bool":
return "boolean"
case "Vec<u32>":
return "number[]"
case "Vec<u8>":
return "string"
default:
return ty
}
}
function capitalKind(kind: string): string {
switch(kind) {
case "request": {
return "Request"
}
case "response": {
return "Response"
}
case "event": {
return "Event"
}
}
}
function generateTsInterfaces(spec: SpecItem[]): string {
const messages = spec.filter(i => i.kind != "enum") as Message[]
let imports = []
let interfaces = []
messages.forEach(m => {
const name = messageName(m)
const fields = m.fields
.map(f => ` "${f[0]}": ${tsType(f[1])}`)
.join("\n")
let base = capitalKind(m.kind)
imports.push(`${base}s.${name}`)
interfaces.push(`export interface ${name} extends ${base} {\
\n "${m.kind}": "${name}"\
\n${fields}\
\n}`)
})
return `${imports.join(" |\n")}\n\n${interfaces.join("\n\n")}`
}
function generateSchemas(spec: SpecItem[]) {
const enums = spec.filter(i => i.kind == "enum") as Enum[]
const messages = spec.filter(i => i.kind != "enum" && i.kind != "request") as Message[]
const refs = []
const schemas = []
messages.forEach(m => {
const name = messageName(m)
const header =
`"${name}": {\
\n "type": "object",\
\n "properties": {\
\n "${m.kind}": { "enum": ["${name}"] },`
let fieldNames = m.fields.map(f => `"${f[0]}"`)
let schemaFields = generateSchemaFields(enums, m.fields)
const footer =
` },\
\n "required": [ "${m.kind}", ${fieldNames.join(", ")} ]\
\n}`
refs.push(`{ "$ref": "#/definitions/${capitalKind(m.kind)}/definitions/${name}" }`)
schemas.push(`${header}\n${schemaFields}\n${footer}`)
})
return `${refs.join(",\n")}\n\n${schemas.join(",\n")}`
}
function generateSchemaFields(enums: Enum[], fields: [string, string][]): string {
const schemaFields = []
fields.forEach(f => {
const enu = enums.filter(e => e.enum == f[1])[0]
if(enu != undefined) {
const items = enu.items
.map(i => `"${i}"`)
.join(", ")
schemaFields.push(` "${f[0]}": { "enum": [ ${items} ] }`)
}
else {
const ty = tsType(f[1])
schemaFields.push(` "${f[0]}": { "type": "${ty}" }`)
}
});
return schemaFields.join(",\n")
}
function generateAll(source: string) {
const parser = new SpecParser()
const spec = parser.parse(source)
const rustEnum = generateRustEnum(spec)
const tsInterfaces = generateTsInterfaces(spec)
const schemas = generateSchemas(spec)
console.log(rustEnum, "\n")
console.log(tsInterfaces, "\n")
console.log(schemas)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment