Skip to content

Instantly share code, notes, and snippets.

@Yuyz0112
Last active March 7, 2020 07:14
Show Gist options
  • Save Yuyz0112/84d974a1830577230b87e181d05cb9b0 to your computer and use it in GitHub Desktop.
Save Yuyz0112/84d974a1830577230b87e181d05cb9b0 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
function lastOf(arr) {
return arr[arr.length - 1];
}
function getCurrentStatement(ast) {
if (ast.body.length === 0) {
throw new Error("No statement found");
}
return lastOf(ast.body);
}
function getCurrentFrom(ast) {
const from = getCurrentStatement(ast).from;
if (!from) {
throw new Error("No from identifier found");
}
return from;
}
function getCurrentWhere(ast) {
const { where } = getCurrentStatement(ast);
if (!where) {
throw new Error("No where expression found");
}
return where;
}
function getCurrentBinaryExpression(ast) {
const where = getCurrentWhere(ast);
if (where.type === "BinaryExpression") {
return where;
}
return where.right;
}
function getCurrentChar(lines, position) {
return getLine(lines, position)[position.column] || "";
}
function getNextChar(lines, position) {
var _a;
const newPosition = getNextPosition(lines, position);
return ((_a = getLine(lines, newPosition)) === null || _a === void 0 ? void 0 : _a[newPosition.column]) || "";
}
function noNext(lines, position) {
return (position.line === lines.length &&
position.column === getLine(lines, position).length);
}
function isEOL(lines, position) {
var _a;
return ((_a = getLine(lines, position)) === null || _a === void 0 ? void 0 : _a.length) === position.column;
}
function getNextPosition(lines, position) {
if (isEOL(lines, position)) {
return {
line: position.line + 1,
column: 0
};
}
return {
line: position.line,
column: position.column + 1
};
}
function getNextNonSpacePosition(lines, position) {
let nextPosition = getNextPosition(lines, position);
while (!noNext(lines, nextPosition) &&
getCurrentChar(lines, nextPosition) === " ") {
nextPosition = getNextPosition(lines, nextPosition);
}
return nextPosition;
}
function getLine(lines, position) {
return lines[position.line - 1];
}
function getTypeOfValue(ast, meta) {
var _a, _b;
const from = getCurrentFrom(ast);
const left = getCurrentBinaryExpression(ast).left;
const typeOfValue = (_b = (_a = meta
.find(m => m.type === from.name)) === null || _a === void 0 ? void 0 : _a.fields.find(f => f.name === left.name)) === null || _b === void 0 ? void 0 : _b.typeOfValue;
if (!typeOfValue) {
throw new Error("No type of value found");
}
return typeOfValue;
}
function getCurrentArrayExpression(ast) {
const { right } = getCurrentBinaryExpression(ast);
if ((right === null || right === void 0 ? void 0 : right.type) !== "ArrayExpression") {
throw new Error("Current BinaryExpression right is not ArrayExpression");
}
return right;
}
function getCurrentMember(ast) {
const arrayExpression = getCurrentArrayExpression(ast);
return lastOf(arrayExpression.members);
}
function literalRawToValue(literal, typedIdentifier) {
switch (typedIdentifier.typeOfValue) {
case "boolean":
if (literal.raw === "true") {
return true;
}
else if (literal.raw === "false") {
return false;
}
else {
throw new Error(`Invalid boolean value ${literal.raw}`);
}
case "enum":
return literal.raw;
case "number": {
const value = parseFloat(literal.raw);
if (Number.isNaN(value)) {
throw new Error(`Invalid number value ${literal.raw}`);
}
return value;
}
case "string":
if (!/^'.+'$/.test(literal.raw)) {
throw new Error("String value should be single-quoted");
}
return literal.raw.slice(1, literal.raw.length - 1);
default:
throw new Error(`Unknown type of value "${typedIdentifier.typeOfValue}"`);
}
}
function visit(ast, onVist) {
function walk(node, parent) {
onVist(node, parent);
switch (node.type) {
case "Program":
node.body.forEach(c => walk(c, node));
break;
case "Statement":
node.from && walk(node.from, node);
node.where && walk(node.where, node);
break;
case "BinaryExpression":
case "LogicalExpression":
node.operator && walk(node.operator, node);
node.left && walk(node.left, node);
node.right && walk(node.right, node);
break;
case "ArrayExpression":
node.members.forEach(c => walk(c, node));
break;
default:
break;
}
}
walk(ast);
}
function getNodeAtPos(ast, position) {
function isInNode(n, p) {
return isStrictThan({ start: p, end: p }, n.loc);
}
function isStrictThan(loc1, loc2) {
const afterStart = loc1.start.line > loc2.start.line ||
(loc1.start.line === loc2.start.line &&
loc1.start.column >= loc2.start.column);
const beforeEnd = loc1.end.line < loc2.end.line ||
(loc1.end.line === loc2.end.line && loc1.end.column <= loc2.end.column);
return afterStart && beforeEnd;
}
let node;
visit(ast, currentNode => {
const moreStrict = node ? isStrictThan(currentNode.loc, node.loc) : true;
if (isInNode(currentNode, position) && moreStrict) {
node = currentNode;
}
});
if (!node) {
throw new Error("position out of range");
}
return node;
}
function printNode(node, source) {
const { start, end } = node.loc;
let line = start.line;
const output = [];
while (line <= end.line) {
const currentLine = getLine(source, { column: 0, line });
const startIdx = line === start.line ? start.column : 0;
const endIdx = line === end.line ? end.column : currentLine.length;
output.push(currentLine.slice(startIdx, endIdx));
line++;
}
return output.join("\r\n");
}
function getParent(ast, node) {
let parent = undefined;
visit(ast, (current, currentParent) => {
if (current === node) {
parent = currentParent;
}
});
return parent;
}
const meta = [
{
type: "Vm",
fields: [
{
name: "name",
typeOfValue: "string"
},
{
name: "vcpu",
typeOfValue: "number"
},
{
name: "status",
typeOfValue: "enum",
members: ["DELETED", "RUNNING", "STOPPED", "SUSPENDED", "UNKNOWN"]
}
]
}
];
const defaultProgram = {
loc: {
start: {
line: 1,
column: 0
},
end: {
line: 1,
column: 0
}
},
type: "Program",
body: []
};
const defaultContext = {
lines: [""],
position: {
line: 1,
column: 0
},
ast: defaultProgram,
meta,
onError() { }
};
const machine = Machine({
id: "parser",
context: defaultContext,
initial: "program",
states: {
program: {
entry: ["startProgram"],
exit: ["endProgram"],
initial: "statement",
states: {
statement: {
entry: ["createStatement"],
exit: ["endStatement"],
initial: "from",
states: {
from: {
entry: ["createFrom"],
exit: ["next", "endFrom", "next"],
on: {
"": [
// { target: "#parser.end", cond: "noNext" },
{ target: "where", cond: "nextIsSpace" },
{
internal: true,
actions: ["updateFrom", "next"],
cond: "hasNext"
}
]
}
},
where: {
id: "where",
initial: "binary_expression",
states: {
binary_expression: {
entry: ["createBinaryExpression"],
exit: ["endBinaryExpression"],
initial: "left",
states: {
left: {
entry: ["createBinaryLeft"],
exit: ["next", "endBinaryLeft", "next"],
on: {
"": [
// { target: "#parser.end", cond: "noNext" },
{
target: "operator",
cond: "nextIsSpace"
},
{
internal: true,
actions: ["updateBinaryLeft", "next"],
cond: "hasNext"
}
]
}
},
operator: {
entry: ["createBinaryOperator"],
exit: ["next", "endBinaryOperator", "next"],
on: {
"": [
// { target: "#parser.end", cond: "noNext" },
{
target: "right",
cond: "nextIsSpace"
},
{
internal: true,
actions: ["updateBinaryOperator", "next"],
cond: "hasNext"
}
]
}
},
right: {
initial: "unknown",
states: {
unknown: {
on: {
"": [
{
target: "multiple",
cond: "currentIsLeftParentheses"
},
{ target: "single" }
]
}
},
single: {
entry: ["createBinaryRightLiteral"],
exit: ["next", "next"],
on: {
"": [
// TODO: to end when has value
// { target: "#parser.end", cond: "noNext" },
{
target: "#where.logical_expression",
cond: "nextIsSpace"
},
{
internal: true,
actions: ["updateBinaryRightLiteral", "next"],
cond: "hasNext"
}
]
}
},
multiple: {
entry: ["createBinaryRightArrayExpression", "next"],
exit: ["endBinaryRightArrayExpression", "next"],
initial: "member",
states: {
member: {
entry: ["createMember"],
exit: ["next", "endMember", "next"],
on: {
"": [
// { target: "#parser.end", cond: "noNext" },
{
target: "#where.logical_expression",
cond: "nextIsRightParenthesesWithSpace"
},
{
target: "#parser.end",
cond: "nextIsRightParentheses"
},
{
// not internal, create a new member
target: "member",
cond: "nextIsComma"
},
{
internal: true,
cond: "nextIsSpace",
actions: ["next"]
},
{
internal: true,
actions: ["updateMember", "next"],
cond: "hasNext"
}
]
}
}
}
}
}
}
}
},
logical_expression: {
entry: ["createLogicalExpression"],
exit: ["next", "endLogicalExpression", "next"],
on: {
"": [
// { target: "#parser.end", cond: "noNext" },
{ target: "binary_expression", cond: "nextIsSpace" },
{
internal: true,
actions: ["updateLogicalExpression", "next"],
cond: "hasNext"
}
]
}
}
}
}
}
}
}
},
end: {
type: "final"
}
}
}, {
actions: {
next: assign({
position: context => {
const { lines, position } = context;
return getNextPosition(lines, position);
}
}),
nextNonSpace: assign({
position: context => {
const { lines, position } = context;
return getNextNonSpacePosition(lines, position);
}
}),
startProgram: assign({
ast: context => {
const { ast, position } = context;
ast.loc.start = position;
return ast;
}
}),
endProgram: assign({
ast: context => {
const { ast, position } = context;
ast.loc.end = position;
return ast;
}
}),
createStatement: assign({
ast: context => {
const { ast, position } = context;
const statement = {
type: "Statement",
loc: {
start: position,
end: position
},
from: null,
where: null
};
ast.body.push(statement);
return ast;
}
}),
endStatement: assign({
ast: context => {
const { ast, position } = context;
const statement = getCurrentStatement(ast);
statement.loc.end = position;
return ast;
}
}),
createFrom: assign({
ast: context => {
const { ast, position, lines } = context;
const from = {
type: "Identifier",
loc: {
start: position,
end: position
},
name: getCurrentChar(lines, position)
};
const statement = getCurrentStatement(ast);
statement.from = from;
return ast;
}
}),
endFrom: assign({
ast: context => {
const { ast, position } = context;
const from = getCurrentFrom(ast);
from.loc.end = position;
return ast;
}
}),
updateFrom: assign({
ast: context => {
const { ast, position, lines } = context;
const from = getCurrentFrom(ast);
const char = getNextChar(lines, position);
from.name += char;
from.loc.end = getNextPosition(lines, position);
return ast;
}
}),
createBinaryExpression: assign({
ast: context => {
const { ast, position } = context;
const statement = getCurrentStatement(ast);
if (statement.where === null) {
statement.where = {
type: "BinaryExpression",
loc: {
start: position,
end: position
},
left: null,
operator: null,
right: null
};
}
return ast;
}
}),
endBinaryExpression: assign({
ast: context => {
const { ast, position } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.loc.end = position;
return ast;
}
}),
createBinaryLeft: assign({
ast: context => {
const { ast, position, lines } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.left = {
type: "TypedIdentifier",
loc: {
start: position,
end: position
},
name: getCurrentChar(lines, position),
typeOfValue: "unknown"
};
return ast;
}
}),
updateBinaryLeft: assign({
ast: context => {
const { ast, position, lines } = context;
const left = getCurrentBinaryExpression(ast).left;
const char = getNextChar(lines, position);
left.name += char;
left.loc.end = getNextPosition(lines, position);
return ast;
}
}),
endBinaryLeft: assign({
ast: context => {
const { ast, position, meta } = context;
const left = getCurrentBinaryExpression(ast).left;
left.loc.end = position;
try {
left.typeOfValue = getTypeOfValue(ast, meta);
}
catch (error) {
context.onError(error);
}
return ast;
}
}),
createBinaryOperator: assign({
ast: context => {
const { ast, position, lines } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.operator = {
type: "BinaryOperator",
loc: {
start: position,
end: position
},
value: getCurrentChar(lines, position)
};
return ast;
}
}),
updateBinaryOperator: assign({
ast: context => {
const { ast, position, lines } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
const char = getNextChar(lines, position);
binaryExpression.operator.value = (binaryExpression.operator.value +
char);
binaryExpression.operator.loc.end = getNextPosition(lines, position);
return ast;
}
}),
endBinaryOperator: assign({
ast: context => {
const { ast, position } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.operator.loc.end = position;
return ast;
}
}),
createBinaryRightLiteral: assign({
ast: context => {
const { ast, position, lines } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.right = {
type: "Literal",
loc: {
start: position,
end: position
},
raw: getCurrentChar(lines, position),
value: ""
};
try {
binaryExpression.right.value = literalRawToValue(binaryExpression.right, binaryExpression.left);
}
catch (error) {
context.onError(error);
}
return ast;
}
}),
updateBinaryRightLiteral: assign({
ast: context => {
const { ast, position, lines } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
const right = binaryExpression.right;
const char = getNextChar(lines, position);
right.raw += char;
// TODO: check me
right.loc.end = getNextPosition(lines, position);
try {
right.value = literalRawToValue(right, binaryExpression.left);
}
catch (error) {
context.onError(error);
}
return ast;
}
}),
createBinaryRightArrayExpression: assign({
ast: context => {
const { ast, position } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.right = {
type: "ArrayExpression",
loc: {
start: position,
end: position
},
members: []
};
return ast;
}
}),
endBinaryRightArrayExpression: assign({
ast: context => {
const { ast, position } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
binaryExpression.right.loc.end = position;
return ast;
}
}),
createMember: assign({
ast: context => {
const { ast, position, lines } = context;
const arrayExpression = getCurrentArrayExpression(ast);
arrayExpression.members.push({
type: "Literal",
loc: {
start: position,
end: position
},
raw: getCurrentChar(lines, position),
value: ""
});
return ast;
}
}),
updateMember: assign({
ast: context => {
const { ast, position, lines } = context;
const member = getCurrentMember(ast);
const char = getNextChar(lines, position);
member.raw += char;
const binaryExpression = getCurrentBinaryExpression(ast);
try {
member.value = literalRawToValue(member, binaryExpression.left);
}
catch (error) {
context.onError(error);
}
member.loc.end = getNextPosition(lines, position);
return ast;
}
}),
endMember: assign({
ast: context => {
const { ast, position } = context;
const binaryExpression = getCurrentBinaryExpression(ast);
const member = getCurrentMember(ast);
member.loc.end = position;
try {
member.value = literalRawToValue(member, binaryExpression.left);
}
catch (error) {
context.onError(error);
}
return ast;
}
}),
createLogicalExpression: assign({
ast: context => {
const { ast, position, lines } = context;
const statement = getCurrentStatement(ast);
statement.where = {
type: "LogicalExpression",
loc: statement.where.loc,
operator: {
type: "LogicalOperator",
loc: {
start: position,
end: position
},
value: getCurrentChar(lines, position)
},
left: statement.where,
right: {
type: "BinaryExpression",
loc: {
start: position,
end: position
},
left: null,
operator: null,
right: null
}
};
return ast;
}
}),
updateLogicalExpression: assign({
ast: context => {
const { ast, position, lines } = context;
const where = getCurrentWhere(ast);
where.operator.value = (where.operator.value +
getNextChar(lines, position));
where.operator.loc.end = getNextPosition(lines, position);
return ast;
}
}),
endLogicalExpression: assign({
ast: context => {
const { ast, position } = context;
const where = getCurrentWhere(ast);
where.operator.loc.end = position;
where.loc.end = position;
return ast;
}
})
},
guards: {
noNext: context => {
const { position, lines } = context;
return noNext(lines, position);
},
hasNext: context => {
const { position, lines } = context;
return !noNext(lines, position);
},
nextIsSpace: context => {
const { position, lines } = context;
// if (noNext(lines, position)) {
// return false;
// }
const char = getNextChar(lines, position);
return char === " ";
},
currentIsLeftParentheses: context => {
const { position, lines } = context;
const char = getCurrentChar(lines, position);
return char === "(";
},
nextIsComma: context => {
const { position, lines } = context;
// if (noNext(lines, position)) {
// return false;
// }
const char = getNextChar(lines, position);
return char === ",";
},
nextIsRightParentheses: context => {
const { position, lines } = context;
// if (noNext(lines, position)) {
// return false;
// }
const char = getNextChar(lines, position);
return char === ")";
},
nextIsRightParenthesesWithSpace: context => {
const { position, lines } = context;
// if (noNext(lines, position)) {
// return false;
// }
const char = getNextChar(lines, position);
const nextToChar = getNextChar(lines, getNextPosition(lines, position));
return char === ")" && nextToChar === " ";
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment