Introduce Walker class for walking the ifql AST

Co-authored-by: Brandon Farmer <bthesorceror@gmail.com>
Co-authored-by: Andrew Watkins <andrew.watkinz@gmail.com>
pull/3033/head
Andrew Watkins 2018-03-27 15:28:42 -07:00 committed by Brandon Farmer
parent ad7d96d697
commit b2e70f396f
8 changed files with 943 additions and 1 deletions

63
ui/src/ifql/ast/walker.ts Normal file
View File

@ -0,0 +1,63 @@
// Texas Ranger
import {get} from 'lodash'
interface Expression {
expression: object
}
interface AST {
body: Expression[]
}
export default class Walker {
private ast: AST
constructor(ast) {
this.ast = ast
}
public get functions() {
return this.buildFuncNodes(this.walk(this.baseExpression))
}
private reduceArgs = args => {
return args.reduce(
(acc, arg) => [...acc, ...this.getProperties(arg.properties)],
[]
)
}
private walk = currentNode => {
let name
let args
if (currentNode.call) {
name = currentNode.call.callee.name
args = currentNode.call.arguments
return [...this.walk(currentNode.argument), {name, args}]
}
name = currentNode.callee.name
args = currentNode.arguments
return [{name, args}]
}
private buildFuncNodes = nodes => {
return nodes.map(node => {
return {
name: node.name,
arguments: this.reduceArgs(node.args),
}
})
}
private getProperties = props => {
return props.map(prop => ({
name: prop.key.name,
value: get(prop, 'value.value', get(prop, 'value.location.source', '')),
}))
}
private get baseExpression() {
return this.ast.body[0].expression
}
}

View File

@ -0,0 +1,99 @@
import React, {PureComponent} from 'react'
const obj = {
type: 'CallExpression',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 21,
},
source: 'from(db: "telegraf")',
},
callee: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 5,
},
source: 'from',
},
name: 'from',
},
arguments: [
{
type: 'ObjectExpression',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
properties: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 8,
},
source: 'db',
},
name: 'db',
},
value: {
type: 'StringLiteral',
location: {
start: {
line: 1,
column: 10,
},
end: {
line: 1,
column: 20,
},
source: '"telegraf"',
},
value: 'telegraf',
},
},
],
},
],
}
interface FromNode {}
interface Props {}
class From extends PureComponent<Props> {}

View File

@ -3,6 +3,7 @@ import FuncsButton from 'src/ifql/components/FuncsButton'
interface Props {
funcs: string[]
ast: object
}
const TimeMachine: SFC<Props> = ({funcs}) => {

View File

@ -0,0 +1,155 @@
export const ast = {
type: 'File',
start: 0,
end: 22,
loc: {
start: {
line: 1,
column: 0,
},
end: {
line: 1,
column: 22,
},
},
program: {
type: 'Program',
start: 0,
end: 22,
loc: {
start: {
line: 1,
column: 0,
},
end: {
line: 1,
column: 22,
},
},
sourceType: 'module',
body: [
{
type: 'ExpressionStatement',
start: 0,
end: 22,
loc: {
start: {
line: 1,
column: 0,
},
end: {
line: 1,
column: 22,
},
},
expression: {
type: 'CallExpression',
start: 0,
end: 22,
loc: {
start: {
line: 1,
column: 0,
},
end: {
line: 1,
column: 22,
},
},
callee: {
type: 'Identifier',
start: 0,
end: 4,
loc: {
start: {
line: 1,
column: 0,
},
end: {
line: 1,
column: 4,
},
identifierName: 'from',
},
name: 'from',
},
arguments: [
{
type: 'ObjectExpression',
start: 5,
end: 21,
loc: {
start: {
line: 1,
column: 5,
},
end: {
line: 1,
column: 21,
},
},
properties: [
{
type: 'ObjectProperty',
start: 6,
end: 20,
loc: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
},
method: false,
shorthand: false,
computed: false,
key: {
type: 'Identifier',
start: 6,
end: 8,
loc: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 8,
},
identifierName: 'db',
},
name: 'db',
},
value: {
type: 'StringLiteral',
start: 10,
end: 20,
loc: {
start: {
line: 1,
column: 10,
},
end: {
line: 1,
column: 20,
},
},
extra: {
rawValue: 'telegraf',
raw: 'telegraf',
},
value: 'telegraf',
},
},
],
},
],
},
},
],
directives: [],
},
}

View File

@ -5,6 +5,7 @@ import {connect} from 'react-redux'
import TimeMachine from 'src/ifql/components/TimeMachine'
import {getSuggestions} from 'src/ifql/apis'
import {ast} from 'src/ifql/constants'
interface Links {
self: string
@ -51,7 +52,7 @@ export class IFQLPage extends PureComponent<Props, State> {
</div>
<div className="page-contents">
<div className="container-fluid">
<TimeMachine funcs={this.state.funcs} />
<TimeMachine funcs={this.state.funcs} ast={ast} />
</div>
</div>
</div>

453
ui/test/ifql/ast/complex.ts Normal file
View File

@ -0,0 +1,453 @@
export default {
type: 'Program',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 91,
},
source:
'from(db: "telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu") |> range(start: -1m)',
},
body: [
{
type: 'ExpressionStatement',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 91,
},
source:
'from(db: "telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu") |> range(start: -1m)',
},
expression: {
type: 'PipeExpression',
location: {
start: {
line: 1,
column: 71,
},
end: {
line: 1,
column: 91,
},
source: '|> range(start: -1m)',
},
argument: {
type: 'PipeExpression',
location: {
start: {
line: 1,
column: 22,
},
end: {
line: 1,
column: 70,
},
source: '|> filter(fn: (r) => r["_measurement"] == "cpu")',
},
argument: {
type: 'CallExpression',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 21,
},
source: 'from(db: "telegraf")',
},
callee: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 5,
},
source: 'from',
},
name: 'from',
},
arguments: [
{
type: 'ObjectExpression',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
properties: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 8,
},
source: 'db',
},
name: 'db',
},
value: {
type: 'StringLiteral',
location: {
start: {
line: 1,
column: 10,
},
end: {
line: 1,
column: 20,
},
source: '"telegraf"',
},
value: 'telegraf',
},
},
],
},
],
},
call: {
type: 'CallExpression',
location: {
start: {
line: 1,
column: 25,
},
end: {
line: 1,
column: 70,
},
source: 'filter(fn: (r) => r["_measurement"] == "cpu")',
},
callee: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 25,
},
end: {
line: 1,
column: 31,
},
source: 'filter',
},
name: 'filter',
},
arguments: [
{
type: 'ObjectExpression',
location: {
start: {
line: 1,
column: 32,
},
end: {
line: 1,
column: 69,
},
source: 'fn: (r) => r["_measurement"] == "cpu"',
},
properties: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 32,
},
end: {
line: 1,
column: 69,
},
source: 'fn: (r) => r["_measurement"] == "cpu"',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 32,
},
end: {
line: 1,
column: 34,
},
source: 'fn',
},
name: 'fn',
},
value: {
type: 'ArrowFunctionExpression',
location: {
start: {
line: 1,
column: 36,
},
end: {
line: 1,
column: 69,
},
source: '(r) => r["_measurement"] == "cpu"',
},
params: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 37,
},
end: {
line: 1,
column: 38,
},
source: 'r',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 37,
},
end: {
line: 1,
column: 38,
},
source: 'r',
},
name: 'r',
},
value: null,
},
],
body: {
type: 'BinaryExpression',
location: {
start: {
line: 1,
column: 43,
},
end: {
line: 1,
column: 69,
},
source: 'r["_measurement"] == "cpu"',
},
operator: '==',
left: {
type: 'MemberExpression',
location: {
start: {
line: 1,
column: 43,
},
end: {
line: 1,
column: 61,
},
source: 'r["_measurement"] ',
},
object: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 43,
},
end: {
line: 1,
column: 44,
},
source: 'r',
},
name: 'r',
},
property: {
type: 'StringLiteral',
location: {
start: {
line: 1,
column: 45,
},
end: {
line: 1,
column: 59,
},
source: '"_measurement"',
},
value: '_measurement',
},
},
right: {
type: 'StringLiteral',
location: {
start: {
line: 1,
column: 64,
},
end: {
line: 1,
column: 69,
},
source: '"cpu"',
},
value: 'cpu',
},
},
},
},
],
},
],
},
},
call: {
type: 'CallExpression',
location: {
start: {
line: 1,
column: 74,
},
end: {
line: 1,
column: 91,
},
source: 'range(start: -1m)',
},
callee: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 74,
},
end: {
line: 1,
column: 79,
},
source: 'range',
},
name: 'range',
},
arguments: [
{
type: 'ObjectExpression',
location: {
start: {
line: 1,
column: 80,
},
end: {
line: 1,
column: 90,
},
source: 'start: -1m',
},
properties: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 80,
},
end: {
line: 1,
column: 90,
},
source: 'start: -1m',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 80,
},
end: {
line: 1,
column: 85,
},
source: 'start',
},
name: 'start',
},
value: {
type: 'UnaryExpression',
location: {
start: {
line: 1,
column: 87,
},
end: {
line: 1,
column: 90,
},
source: '-1m',
},
operator: '-',
argument: {
type: 'DurationLiteral',
location: {
start: {
line: 1,
column: 88,
},
end: {
line: 1,
column: 90,
},
source: '1m',
},
value: '1m0s',
},
},
},
],
},
],
},
},
},
],
}

121
ui/test/ifql/ast/from.ts Normal file
View File

@ -0,0 +1,121 @@
export default {
type: 'Program',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 21,
},
source: 'from(db: "telegraf")',
},
body: [
{
type: 'ExpressionStatement',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 21,
},
source: 'from(db: "telegraf")',
},
expression: {
type: 'CallExpression',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 21,
},
source: 'from(db: "telegraf")',
},
callee: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 5,
},
source: 'from',
},
name: 'from',
},
arguments: [
{
type: 'ObjectExpression',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
properties: [
{
type: 'Property',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 20,
},
source: 'db: "telegraf"',
},
key: {
type: 'Identifier',
location: {
start: {
line: 1,
column: 6,
},
end: {
line: 1,
column: 8,
},
source: 'db',
},
name: 'db',
},
value: {
type: 'StringLiteral',
location: {
start: {
line: 1,
column: 10,
},
end: {
line: 1,
column: 20,
},
source: '"telegraf"',
},
value: 'telegraf',
},
},
],
},
],
},
},
],
}

View File

@ -0,0 +1,49 @@
import Walker from 'src/ifql/ast/walker'
import From from 'test/ifql/ast/from'
import Complex from 'test/ifql/ast/complex'
describe('IFQL.AST.Walker', () => {
describe('Walker#functions', () => {
describe('simple example', () => {
it('returns a flattened ordered list of from and its arguments', () => {
const walker = new Walker(From)
expect(walker.functions).toEqual([
{
name: 'from',
arguments: [
{
name: 'db',
value: 'telegraf',
},
],
},
])
})
})
describe('complex example', () => {
it('returns a flattened ordered list of all funcs and their arguments', () => {
const walker = new Walker(Complex)
expect(walker.functions).toEqual([
{
name: 'from',
arguments: [{name: 'db', value: 'telegraf'}],
},
{
name: 'filter',
arguments: [
{
name: 'fn',
value: '(r) => r["_measurement"] == "cpu"',
},
],
},
{
name: 'range',
arguments: [{name: 'start', value: '-1m'}],
},
])
})
})
})
})