Skip to content

Commit

Permalink
Add schema reading function
Browse files Browse the repository at this point in the history
  • Loading branch information
MaximeKjaer committed Aug 29, 2019
1 parent 48bcc46 commit a3a7e1b
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 0 deletions.
131 changes: 131 additions & 0 deletions src/schema/readSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { SugarSyntax } from "../parser/Sugar";
import { Cardinality } from "./Cardinality";
import {
ArgDefinition,
BlockDefinition,
BodyPropDefinitions,
InlineDefinition,
RootDefinition,
SchemaDefinition
} from "./SchemaDefinition";
import * as SchemaSchema from "./schemaSchemaInterface";

export function readSchema(root: SchemaSchema.Root): SchemaDefinition {
return {
root: convertRoot(root.root),
blocks: Object.fromEntries(
root.blocks.map(block => [block.name.join(""), convertBlock(block)])
),
inline: Object.fromEntries(root.inline.map(inline => [inline.name, convertInline(inline)]))
};
}

function convertRoot(root: SchemaSchema.BlockRoot): RootDefinition {
const res: RootDefinition = {
rawBody: false,
props: {
body: convertBody(root.body)
}
};
if (root.defaultTag) res.defaultTag = root.defaultTag.name.join("");
return res;
}

function convertBlock(block: SchemaSchema.BlockBlock): BlockDefinition {
const res: BlockDefinition = {
rawBody: false,
props: {}
};
if (block.head) res.props.head = convertHead(block.head);
if (block.body) res.props.body = convertBody(block.body);
if (block.defaultTag) res.defaultTag = block.defaultTag.name.join("");
return res;
}

function convertHead(head: SchemaSchema.BlockHead): ArgDefinition {
// TODO Temporary until we have a more powerful schema:
if (head.type.length === 0) throw new Error("Head should have at least one type");
if (head.type.length > 1) throw new Error("Head should have at most one type");

const type = head.type[0];
const name = type.propName.join("");

if (type.$tag === "hashml") {
return {
raw: false,
name,
content: convertItems(type.content)
};
} else {
return {
raw: true,
name,
type: type.$tag
};
}
}

function convertBody(body: SchemaSchema.BlockBody): BodyPropDefinitions {
return Object.fromEntries(
body.props.map(prop => [prop.name, convertPropContent(prop.content)])
);
}

function convertItems(items: SchemaSchema.BlockItem[]): string[] {
return items.map(item => item.target.join(""));
}

function convertPropContent(
propContent: SchemaSchema.BlockProp["content"]
): { [tag: string]: Cardinality } {
return Object.fromEntries(
propContent.map(content => [content.target.join(""), convertCardinality(content)])
);
}

function convertCardinality(
cardinality:
| SchemaSchema.BlockOne
| SchemaSchema.BlockOneOrMore
| SchemaSchema.BlockOptional
| SchemaSchema.BlockZeroOrMore
): Cardinality {
switch (cardinality.$tag) {
case "one":
return { min: 1, max: 1 };
case "oneOrMore":
return { min: 1, max: Infinity };
case "optional":
return { min: 0, max: 1 };
case "zeroOrMore":
return { min: 0, max: Infinity };
}
}

function convertInline(inline: SchemaSchema.BlockInline): InlineDefinition {
const res: InlineDefinition = {
args: convertArgs(inline.args)
};
if (inline.sugar) res.sugar = convertSugar(inline.sugar);
return res;
}

function convertArgs(args: SchemaSchema.BlockArgs): ArgDefinition[] {
return args.args.map(arg => {
const name = arg.propName.join("");
if (arg.$tag === "hashml") {
return { raw: false, name, content: convertItems(arg.content) };
} else {
return { raw: true, name, type: arg.$tag };
}
});
}

function convertSugar(sugar: SchemaSchema.BlockSugar): SugarSyntax {
const res: SugarSyntax = {
start: sugar.start.token,
end: sugar.end.token
};
if (sugar.separator) res.separator = sugar.separator.token;
return res;
}
132 changes: 132 additions & 0 deletions test/_resources/input/schema_schema.hm
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#root
#body
#prop root
#one root
#prop blocks
#zeroOrMore block
#prop inline
#zeroOrMore inline

#block root
#body
#prop defaultTag
#optional default
#prop body
#one body

#block block
#head
#hashml name
#body
#prop defaultTag
#optional default
#prop head
#optional head
#prop body
#optional body

#block default
#head
#hashml name

#block head
#body
#prop type
#optional hashml
#optional string
#optional date
#optional url

#block body
#body
#prop props
#zeroOrMore prop

#block prop
#default item
#head
#hashml name
#body
#prop content
#zeroOrMore optional
#zeroOrMore one
#zeroOrMore oneOrMore
#zeroOrMore zeroOrMore

#block optional
#head
#hashml target

#block one
#head
#hashml target

#block oneOrMore
#head
#hashml target

#block zeroOrMore
#head
#hashml target

#block inline
#head
#hashml name
#body
#prop args
#one args
#prop sugar
#optional sugar

#block args
#body
#prop args
#zeroOrMore hashml
#zeroOrMore string
#zeroOrMore date
#zeroOrMore url

#block hashml
#default item
#head
#hashml propName
#body
#prop content
#zeroOrMore item

#block item
#head
#hashml target

#block string
#head
#hashml propName

#block date
#head
#hashml propName

#block url
#head
#hashml propName

#block sugar
#body
#prop start
#one start
#prop separator
#optional separator
#prop end
#one end

#block start
#head
#string token

#block separator
#head
#string token

#block end
#head
#string token
30 changes: 30 additions & 0 deletions test/schema/readSchemaTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { assert } from "chai";
import { Schema, schemaSchema } from "../../src";
import { readSchema } from "../../src/schema/readSchema";
import * as SchemaSchema from "../../src/schema/schemaSchemaInterface";
import { File, makeTestParser, resourceFile } from "../utils";

function parseSchemaFile(file: File): SchemaSchema.Root {
const parse = makeTestParser(schemaSchema);
const [errors, tree] = parse(file.read());
assert.isEmpty(errors, errors.join());
return (tree as unknown) as SchemaSchema.Root;
}

describe("readSchema()", () => {
describe("schema_schema.hm", () => {
const schemaSchemaFile = resourceFile("input", "schema_schema.hm");

it("produces a valid Schema", () => {
const root = parseSchemaFile(schemaSchemaFile);
const schemaDef = readSchema(root);
assert.doesNotThrow(() => new Schema(schemaDef));
});

it("reproduces schemaSchema exactly", () => {
const root = parseSchemaFile(schemaSchemaFile);
const schemaDef = readSchema(root);
assert.deepStrictEqual(schemaDef, schemaSchema);
});
});
});

0 comments on commit a3a7e1b

Please sign in to comment.