Skip to content

Commit

Permalink
Add tokenizer and AST
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath committed Jul 26, 2020
1 parent 8cdb6f7 commit 5c0699c
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 203 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## [Unreleased]
### Added
- Tokenizer and AST

## 0.0.0+dev.0 - 2020-07-24
### Added
- Basic design draft
Expand Down
6 changes: 1 addition & 5 deletions lib/jessie.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
/// JSONPath for Dart
library jessie;

export 'package:jessie/src/field.dart';
export 'package:jessie/src/filter.dart';
export 'package:jessie/src/index.dart';
export 'package:jessie/src/neutral.dart';
export 'package:jessie/src/tokenizer.dart';
export 'package:jessie/src/json_path.dart';
38 changes: 38 additions & 0 deletions lib/src/ast.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Node {
Node(this.value);

static Node build(List<String> tokens) {
final root = Node(r'$');
if (tokens.isEmpty) {
return root;
}
final reversed = [...tokens.reversed];
if (reversed.last == r'$') {
reversed.removeLast();
}
final stack = <Node>[root];
while (reversed.isNotEmpty) {
final token = reversed.removeLast();
if (token == '[') {
stack.add(Node(token));
continue;
}
if (token == ']' || token == ')') {
final search = token == ']' ? '[' : '(';
final children = <Node>[];
while (stack.last.value != search) {
children.add(stack.removeLast());
}
final brackets = stack.removeLast();
brackets.children.addAll(children);
stack.last.children.add(brackets);
continue;
}
stack.last.children.add(Node(token));
}
return stack.last;
}

final String value;
final children = <Node>[];
}
7 changes: 4 additions & 3 deletions lib/src/field.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'package:jessie/src/filter.dart';
import 'package:jessie/src/match.dart';

class Field extends Filter {
Field(this.name);

final String name;

@override
Iterable call(Iterable nodes) => nodes
.where((node) => node is Map && node.containsKey(name))
.map((node) => node[name]);
Iterable<PathMatch> call(Iterable<PathMatch> matches) => matches
.where((m) => m.value is Map && m.value.containsKey(name))
.map((m) => PathMatch(m.value[name], m.path + toString()));

@override
String toString() => "['$name']";
Expand Down
9 changes: 6 additions & 3 deletions lib/src/filter.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:jessie/src/match.dart';

abstract class Filter {
const Filter();

/// Applies this JSONPath to the [nodes]
Iterable call(Iterable nodes);
/// Applies this filter to the [matches]
Iterable<PathMatch> call(Iterable<PathMatch> matches);

/// The string expression without leading `$`
@override
Expand All @@ -23,7 +25,8 @@ class _Chain extends Filter {
final Filter second;

@override
Iterable call(Iterable nodes) => second(first(nodes));
Iterable<PathMatch> call(Iterable<PathMatch> matches) =>
second(first(matches));

@override
String toString() => '$first$second';
Expand Down
7 changes: 4 additions & 3 deletions lib/src/index.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'package:jessie/src/filter.dart';
import 'package:jessie/src/match.dart';

class Index extends Filter {
Index(this.index);

final int index;

@override
Iterable call(Iterable nodes) => nodes
.where((node) => node is List && node.length > index + 1)
.map((node) => node[index]);
Iterable<PathMatch> call(Iterable<PathMatch> matches) => matches
.where((m) => m.value is List && m.value.length > index + 1)
.map((m) => PathMatch(m.value[index], m.path + toString()));

@override
String toString() => '[$index]';
Expand Down
27 changes: 27 additions & 0 deletions lib/src/json_path.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'package:jessie/src/ast.dart';
import 'package:jessie/src/filter.dart';
import 'package:jessie/src/match.dart';
import 'package:jessie/src/root.dart';
import 'package:jessie/src/state.dart';
import 'package:jessie/src/tokenize.dart';

class JsonPath {
factory JsonPath(String expression) {
State state = Ready(Root());
for (final node in Node.build(tokenize(expression)).children) {
state = state.process(node);
}
return JsonPath._(state.filter);
}

JsonPath._(this._filter);

final Filter _filter;

/// Filters the given [json].
/// Returns an Iterable of all elements found
Iterable<PathMatch> filter(json) => _filter.call([PathMatch(json, '')]);

@override
String toString() => _filter.toString();
}
7 changes: 7 additions & 0 deletions lib/src/match.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class PathMatch<T> {
PathMatch(this.value, this.path);

final T value;

final String path;
}
11 changes: 0 additions & 11 deletions lib/src/neutral.dart

This file was deleted.

13 changes: 13 additions & 0 deletions lib/src/root.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:jessie/src/filter.dart';
import 'package:jessie/src/match.dart';

class Root extends Filter {
const Root();

@override
Iterable<PathMatch> call(Iterable<PathMatch> matches) =>
matches.map((m) => PathMatch(m.value, toString()));

@override
String toString() => r'$';
}
40 changes: 40 additions & 0 deletions lib/src/state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:jessie/src/ast.dart';
import 'package:jessie/src/field.dart';
import 'package:jessie/src/filter.dart';
import 'package:jessie/src/index.dart';

abstract class State {
State process(Node node);

Filter get filter;
}

class Ready implements State {
Ready(this.filter);

@override
final Filter filter;

@override
State process(Node node) {
if (node.value == '[') {
return Ready(filter.then(Index(int.parse(node.children.first.value))));
}
if (node.value == '.') {
return AwaitingField(filter);
}
throw StateError('Got ${node.value} in $this');
}
}

class AwaitingField implements State {
AwaitingField(this.filter);

@override
final Filter filter;

@override
State process(Node node) {
return Ready(filter.then(Field(node.value)));
}
}
49 changes: 49 additions & 0 deletions lib/src/tokenize.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
List<String> tokenize(String expr) {
final tokens = <String>[];
var pos = 0;
while (pos < expr.length) {
var token = '';
while (pos < expr.length) {
if (_singles.contains(expr[pos])) {
if (token.isNotEmpty) {
break;
}
if (expr.length > pos + 1) {
final nextTwoChars = expr.substring(pos, pos + 1);
if (_doubles.contains(nextTwoChars)) {
token = nextTwoChars;
pos += 2;
break;
}
}
token = expr[pos++];
break;
}
token += expr[pos++];
}
tokens.add(token);
}
return tokens;
}

const _singles = [
r'$',
'[',
']',
'(',
')',
'?',
'.',
'*',
'@',
'<',
'>',
'-',
'=',
'&',
'|',
':',
' '
];

const _doubles = ['..', '&&', '||'];
Loading

0 comments on commit 5c0699c

Please sign in to comment.