Skip to content

Commit

Permalink
Fun types (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath authored Dec 30, 2023
1 parent 995f1b7 commit 85ef756
Show file tree
Hide file tree
Showing 18 changed files with 153 additions and 104 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.0] - 2023-12-29
### Changed
- Renamed `Nodes` to `NodeList`
- Bumped the CTS to the latest version

## [0.6.6] - 2023-09-23
### Fixed
- Logical expressions should be allowed in function arguments
Expand Down Expand Up @@ -176,6 +181,7 @@ Previously, no modification would be made and no errors/exceptions thrown.
### Added
- Basic design draft

[0.7.0]: https://github.com/f3ath/jessie/compare/0.6.6...0.7.0
[0.6.6]: https://github.com/f3ath/jessie/compare/0.6.5...0.6.6
[0.6.5]: https://github.com/f3ath/jessie/compare/0.6.4...0.6.5
[0.6.4]: https://github.com/f3ath/jessie/compare/0.6.3...0.6.4
Expand Down
16 changes: 14 additions & 2 deletions lib/src/expression/nodes.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import 'package:json_path/src/node.dart';
import 'package:maybe_just_nothing/maybe_just_nothing.dart';

typedef Nodes = Iterable<Node>;
typedef NodeList = Iterable<Node>;

extension NodesExt on Nodes {
class SingularNodeList with NodeList {
SingularNodeList(this._nodes);

SingularNodeList.from(Node? node)
: this(node != null ? [node] : const Iterable.empty());

final NodeList _nodes;

@override
Iterator<Node<Object?>> get iterator => _nodes.iterator;
}

extension NodeListExt on NodeList {
Maybe get asValue => length == 1 ? Just(single.value) : const Nothing();

bool get asLogical => isNotEmpty;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/fun/extra/siblings.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import 'package:json_path/fun_sdk.dart';

/// Returns all siblings of the given nodes.
class Siblings implements Fun1<Nodes, Nodes> {
class Siblings implements Fun1<NodeList, NodeList> {
const Siblings();

@override
final name = 'siblings';

@override
Nodes call(Nodes nodes) => nodes
NodeList call(NodeList nodes) => nodes
.expand((node) => node.parent?.children.where((it) => node != it) ?? []);
}
4 changes: 2 additions & 2 deletions lib/src/fun/fun.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract interface class Fun {
/// The return type [R] and the argument type [T] must be one of the following:
/// - [bool]
/// - [Maybe]
/// - [Nodes]
/// - [NodeList]
abstract interface class Fun1<R extends Object, T extends Object> extends Fun {
/// Applies the given arguments.
/// This method MUST throw an [Exception] on invalid args.
Expand All @@ -19,7 +19,7 @@ abstract interface class Fun1<R extends Object, T extends Object> extends Fun {
/// The return type [R] and the argument types [T1], [T2] must be one of the following:
/// - [bool]
/// - [Maybe]
/// - [Nodes]
/// - [NodeList]
abstract interface class Fun2<R extends Object, T1 extends Object,
T2 extends Object> extends Fun {
/// Applies the given arguments.
Expand Down
77 changes: 44 additions & 33 deletions lib/src/fun/fun_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,67 +22,78 @@ class FunFactory {
final _fun1 = <String, Fun1>{};
final _fun2 = <String, Fun2>{};

/// Returns a function to use in comparable context.
Expression<Maybe> comparable(FunCall call) => any<Maybe>(call);
/// Returns a value-type function to use in comparable context.
Expression<Maybe> value(FunCall call) => _any<Maybe>(call);

/// Returns a function to use in logical context.
Expression<bool> logical(FunCall call) => any<bool>(call);
/// Returns a logical-type function to use in logical context.
Expression<bool> logical(FunCall call) => _any<bool>(call);

/// Returns a nodes-type function.
Expression<NodeList> nodes(FunCall call) => _any<NodeList>(call);

/// Returns a function to use as an argument for another function.
Expression<T> any<T extends Object>(FunCall call) {
Expression<T> _any<T extends Object>(FunCall call) {
final name = call.name;
final args = call.args;
try {
if (args.length == 1) return any1<T>(name, args[0]);
if (args.length == 2) return any2<T>(name, args[0], args[1]);
} on TypeError catch (e) {
throw FormatException('Invalid argument: $e');
if (args.length == 1) return _any1<T>(name, args[0]);
if (args.length == 2) return _any2<T>(name, args[0], args[1]);
} on StateError catch (e) {
throw FormatException(e.message);
}
throw Exception('Type mismatch');
}

Expression<T> any1<T extends Object>(String name, Expression a0) {
final f = _get1<T>(name);
final cast0 = cast(value: f is Fun1<T, Maybe>, logical: f is Fun1<T, bool>);
return a0.map(cast0).map(f.call);
Expression<T> _any1<T extends Object>(String name, Expression a0) {
final f = _getFun1<T>(name);
final cast0 = cast(a0,
value: f is Fun1<T, Maybe>,
logical: f is Fun1<T, bool>,
nodes: f is Fun1<T, NodeList>);
return cast0.map(f.call);
}

Expression<T> any2<T extends Object>(
Expression<T> _any2<T extends Object>(
String name, Expression a0, Expression a1) {
final f = _get2<T>(name);
final f = _getFun2<T>(name);
final cast0 = cast(
value: f is Fun2<T, Maybe, Object>,
logical: f is Fun2<T, bool, Object>);
a0,
value: f is Fun2<T, Maybe, Object>,
logical: f is Fun2<T, bool, Object>,
nodes: f is Fun2<T, NodeList, Object>,
);
final cast1 = cast(
value: f is Fun2<T, Object, Maybe>,
logical: f is Fun2<T, Object, bool>);
return a0.map(cast0).merge(a1.map(cast1), f.call);
a1,
value: f is Fun2<T, Object, Maybe>,
logical: f is Fun2<T, Object, bool>,
nodes: f is Fun2<T, Object, NodeList>,
);
return cast0.merge(cast1, f.call);
}

Fun1<T, Object> _get1<T extends Object>(String name) {
Fun1<T, Object> _getFun1<T extends Object>(String name) {
final f = _fun1[name];
if (f is Fun1<T, Object>) return f;
throw StateError('Function "$name" of 1 argument is not found');
}

Fun2<T, Object, Object> _get2<T extends Object>(String name) {
Fun2<T, Object, Object> _getFun2<T extends Object>(String name) {
final f = _fun2[name];
if (f is Fun2<T, Object, Object>) return f;
throw StateError('Function "$name" of 2 arguments is not found');
}

static Object Function(Object) cast(
{required bool value, required bool logical}) {
if (value) return _value;
if (logical) return _logical;
return _nodes;
static Expression cast(Expression arg,
{required bool value, required bool logical, required bool nodes}) {
if (value) {
if (arg is Expression<Maybe>) return arg;
if (arg is Expression<SingularNodeList>) return arg.map((v) => v.asValue);
}
if (logical) {
if (arg is Expression<bool>) return arg;
if (arg is Expression<NodeList>) return arg.map((v) => v.asLogical);
}
if (nodes && arg is Expression<NodeList>) return arg;
throw Exception('Arg type mismatch');
}

static Maybe _value(v) => (v is Maybe) ? v : _nodes(v).asValue;

static bool _logical(v) => (v is bool) ? v : _nodes(v).asLogical;

static Nodes _nodes(v) => v as Nodes;
}
10 changes: 5 additions & 5 deletions lib/src/fun/fun_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ class FunValidator {
if (f is Fun1) {
if (f is! Fun1<bool, Object> &&
f is! Fun1<Maybe, Object> &&
f is! Fun1<Nodes, Object>) {
f is! Fun1<NodeList, Object>) {
yield 'Invalid return type in function ${f.name}';
}
if (f is! Fun1<Object, Maybe> &&
f is! Fun1<Object, Nodes> &&
f is! Fun1<Object, NodeList> &&
f is! Fun1<Object, bool>) {
yield 'Invalid type of the first argument in function ${f.name}';
}
} else if (f is Fun2) {
if (f is! Fun2<bool, Object, Object> &&
f is! Fun2<Maybe, Object, Object> &&
f is! Fun2<Nodes, Object, Object>) {
f is! Fun2<NodeList, Object, Object>) {
yield 'Invalid return type in function ${f.name}';
}
if (f is! Fun2<Object, bool, Object> &&
f is! Fun2<Object, Maybe, Object> &&
f is! Fun2<Object, Nodes, Object>) {
f is! Fun2<Object, NodeList, Object>) {
yield 'Invalid type of the first argument in function ${f.name}';
}
if (f is! Fun2<Object, Object, bool> &&
f is! Fun2<Object, Object, Maybe> &&
f is! Fun2<Object, Object, Nodes>) {
f is! Fun2<Object, Object, NodeList>) {
yield 'Invalid type of the second argument in function ${f.name}';
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions lib/src/fun/standard/count.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import 'package:maybe_just_nothing/maybe_just_nothing.dart';

/// The standard `count()` function which returns the number of nodes in a node list.
/// See https://ietf-wg-jsonpath.github.io/draft-ietf-jsonpath-base/draft-ietf-jsonpath-base.html#name-count-function-extension
class Count implements Fun1<Maybe, Nodes> {
class Count implements Fun1<Maybe, NodeList> {
const Count();

@override
final name = 'count';

@override
Maybe call(Nodes nodes) => Just(nodes.length);
Maybe call(NodeList nodes) => Just(nodes.length);
}
4 changes: 2 additions & 2 deletions lib/src/fun/standard/value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import 'package:json_path/fun_sdk.dart';

/// The standard `value()` function.
/// See https://ietf-wg-jsonpath.github.io/draft-ietf-jsonpath-base/draft-ietf-jsonpath-base.html#name-value-function-extension
class Value implements Fun1<Maybe, Nodes> {
class Value implements Fun1<Maybe, NodeList> {
const Value();

@override
final name = 'value';

@override
Maybe call(Nodes arg) => arg.asValue;
Maybe call(NodeList arg) => arg.asValue;
}
7 changes: 3 additions & 4 deletions lib/src/grammar/array_index_selector.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:json_path/fun_sdk.dart';
import 'package:json_path/src/selector.dart';

Selector arrayIndexSelector(int offset) => (node) sync* {
final element = node.element(offset);
if (element != null) yield element;
};
SingularSelector arrayIndexSelector(int offset) =>
(node) => SingularNodeList.from(node.element(offset));
7 changes: 3 additions & 4 deletions lib/src/grammar/child_selector.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:json_path/fun_sdk.dart';
import 'package:json_path/src/selector.dart';

Selector childSelector(String key) => (node) sync* {
final child = node.child(key);
if (child != null) yield child;
};
SingularSelector childSelector(String key) =>
(node) => SingularNodeList.from(node.child(key));
Loading

0 comments on commit 85ef756

Please sign in to comment.