Last active
March 7, 2020 07:14
-
-
Save Yuyz0112/84d974a1830577230b87e181d05cb9b0 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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