commit
039fbf928b
210 changed files with 655 additions and 7486 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,5 +1,21 @@
|
|||
# Change Log
|
||||
|
||||
## 4.2.0
|
||||
|
||||
* Refactored the framework internal to use [Belatuk Common Utilities](<https://github.com/dart-backend/belatuk-common-utilities>)
|
||||
* Updated [examples] (<https://github.com/dart-backend/belatuk-examples>)
|
||||
* Updated to use `lints` linter
|
||||
|
||||
## 4.1.0
|
||||
|
||||
* Updated [website] (<https://angel3-framework.wep.app>)
|
||||
* Updated [examples] (<https://github.com/dart-backend/belatuk-examples>)
|
||||
* Fixed ORM code generator
|
||||
* Fixed Serializer code generator
|
||||
* Fixed graphQL code generator
|
||||
* Fixed CLI
|
||||
* Fixed failed test cases
|
||||
|
||||
## 4.0.0 (NNBD)
|
||||
|
||||
* Published all packages with `angel3_` prefix
|
||||
|
|
11
TODO.md
11
TODO.md
|
@ -1,16 +1,15 @@
|
|||
# Road Map
|
||||
# Development Blueprint
|
||||
|
||||
## Short Term Goal
|
||||
|
||||
* Fixing issues
|
||||
* ORM source code generator
|
||||
* OAuth2
|
||||
* Proxy
|
||||
* Update examples
|
||||
* Update User Guide
|
||||
* Update website
|
||||
* Performance testing
|
||||
|
||||
## Long Term Goal
|
||||
|
||||
* Optimise Angel3 architecture
|
||||
|
||||
### In Progress
|
||||
|
||||
* Migrate generic packages that has no dependencies on Angel3 to [Common Utilities Repository](<https://github.com/dart-backend/belatuk-common-utilities>). All of these packages operate under the hood with no impact to the application build on Angel3. Updating to newer version of Angel3 packages will automatically upgrade them.
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Change Log
|
||||
|
||||
## 2.1.2
|
||||
|
||||
* Final release. Replaced by `belatuk_body_parser` package.
|
||||
|
||||
## 2.1.1
|
||||
|
||||
* Fixed calling deprecated methods in unit test
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# Angel3 Body Parser
|
||||
|
||||
[![version](https://img.shields.io/badge/pub-v2.1.1-brightgreen)](https://pub.dartlang.org/packages/angel3_body_parser)
|
||||
[![version](https://img.shields.io/badge/pub-v2.1.2-brightgreen)](https://pub.dartlang.org/packages/angel3_body_parser)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/body_parser/LICENSE)
|
||||
|
||||
**Forked from `body_parser` to support NNBD**
|
||||
**DEPRECATED: Replaced by [`belatuk_body_parser`](https://pub.dartlang.org/packages/belatuk_body_parser) package**
|
||||
|
||||
Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external dependencies required.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: angel3_body_parser
|
||||
version: 2.1.1
|
||||
version: 2.1.2
|
||||
description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies.
|
||||
homepage: https://angel3-framework.web.app/
|
||||
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/body_parser
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Angel3 CLI
|
||||
|
||||
Moved to [`Angel3 CLI Repo`](https://github.com/dukefirehawk/angel3-cli)
|
||||
Moved to [`Angel3 CLI Repository`](https://github.com/dukefirehawk/angel3-cli)
|
||||
|
|
71
packages/code_buffer/.gitignore
vendored
71
packages/code_buffer/.gitignore
vendored
|
@ -1,71 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
.project
|
||||
.buildlog
|
||||
**/packages/
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
|
||||
## VsCode
|
||||
.vscode/
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
/out/
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
|
@ -1 +0,0 @@
|
|||
language: dart
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,11 +0,0 @@
|
|||
# 2.0.3
|
||||
* Resolved static analysis warnings
|
||||
|
||||
# 2.0.2
|
||||
* Updated README
|
||||
# 2.0.1
|
||||
* Fixed invalid homepage url in pubspec.yaml
|
||||
# 2.0.0
|
||||
* Migrated to support Dart SDK 2.12.x NNBD
|
||||
# 1.0.1
|
||||
* Added `CodeBuffer.noWhitespace()`.
|
|
@ -1,21 +0,0 @@
|
|||
MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,66 +0,0 @@
|
|||
# angel3_code_buffer
|
||||
[![version](https://img.shields.io/badge/pub-v2.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_code_buffer)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/code_buffer/LICENSE)
|
||||
|
||||
An advanced StringBuffer geared toward generating code, and source maps.
|
||||
|
||||
# Installation
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel3_code_buffer: ^2.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
Use a `CodeBuffer` just like any regular `StringBuffer`:
|
||||
|
||||
```dart
|
||||
String someFunc() {
|
||||
var buf = CodeBuffer();
|
||||
buf
|
||||
..write('hello ')
|
||||
..writeln('world!');
|
||||
return buf.toString();
|
||||
}
|
||||
```
|
||||
|
||||
However, a `CodeBuffer` supports indentation.
|
||||
|
||||
```dart
|
||||
void someOtherFunc() {
|
||||
var buf = CodeBuffer();
|
||||
// Custom options...
|
||||
var buf = CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true);
|
||||
|
||||
// Any following lines will have an incremented indentation level...
|
||||
buf.indent();
|
||||
|
||||
// And vice-versa:
|
||||
buf.outdent();
|
||||
}
|
||||
```
|
||||
|
||||
`CodeBuffer` instances keep track of every `SourceSpan` they create.
|
||||
This makes them useful for codegen tools, or to-JS compilers.
|
||||
|
||||
```dart
|
||||
void someFunc(CodeBuffer buf) {
|
||||
buf.write('hello');
|
||||
expect(buf.lastLine.text, 'hello');
|
||||
|
||||
buf.writeln('world');
|
||||
expect(buf.lastLine.lastSpan.start.column, 5);
|
||||
}
|
||||
```
|
||||
|
||||
You can copy a `CodeBuffer` into another, heeding indentation rules:
|
||||
|
||||
```dart
|
||||
void yetAnotherFunc(CodeBuffer a, CodeBuffer b) {
|
||||
b.copyInto(a);
|
||||
}
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
|
@ -1,46 +0,0 @@
|
|||
import 'package:angel3_code_buffer/angel3_code_buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Use a `CodeBuffer` just like any regular `StringBuffer`:
|
||||
String someFunc() {
|
||||
var buf = CodeBuffer();
|
||||
buf
|
||||
..write('hello ')
|
||||
..writeln('world!');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/// However, a `CodeBuffer` supports indentation.
|
||||
void someOtherFunc() {
|
||||
var buf = CodeBuffer();
|
||||
|
||||
// Custom options...
|
||||
// ignore: unused_local_variable
|
||||
var customBuf =
|
||||
CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true);
|
||||
|
||||
// Without whitespace..
|
||||
// ignore: unused_local_variable
|
||||
var minifyingBuf = CodeBuffer.noWhitespace();
|
||||
|
||||
// Any following lines will have an incremented indentation level...
|
||||
buf.indent();
|
||||
|
||||
// And vice-versa:
|
||||
buf.outdent();
|
||||
}
|
||||
|
||||
/// `CodeBuffer` instances keep track of every `SourceSpan` they create.
|
||||
//This makes them useful for codegen tools, or to-JS compilers.
|
||||
void yetAnotherOtherFunc(CodeBuffer buf) {
|
||||
buf.write('hello');
|
||||
expect(buf.lastLine!.text, 'hello');
|
||||
|
||||
buf.writeln('world');
|
||||
expect(buf.lastLine!.lastSpan!.start.column, 5);
|
||||
}
|
||||
|
||||
/// You can copy a `CodeBuffer` into another, heeding indentation rules:
|
||||
void yetEvenAnotherFunc(CodeBuffer a, CodeBuffer b) {
|
||||
b.copyInto(a);
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
|
||||
/// An advanced StringBuffer geared toward generating code, and source maps.
|
||||
class CodeBuffer implements StringBuffer {
|
||||
/// The character sequence used to represent a line break.
|
||||
final String newline;
|
||||
|
||||
/// The character sequence used to represent a space/tab.
|
||||
final String space;
|
||||
|
||||
/// The source URL to be applied to all generated [SourceSpan] instances.
|
||||
final sourceUrl;
|
||||
|
||||
/// If `true` (default: `false`), then an additional [newline] will be inserted at the end of the generated string.
|
||||
final bool trailingNewline;
|
||||
|
||||
final List<CodeBufferLine> _lines = [];
|
||||
CodeBufferLine? _currentLine, _lastLine;
|
||||
int _indentationLevel = 0;
|
||||
int _length = 0;
|
||||
|
||||
CodeBuffer(
|
||||
{this.space = ' ',
|
||||
this.newline = '\n',
|
||||
this.trailingNewline = false,
|
||||
this.sourceUrl});
|
||||
|
||||
/// Creates a [CodeBuffer] that does not emit additional whitespace.
|
||||
factory CodeBuffer.noWhitespace({sourceUrl}) => CodeBuffer(
|
||||
space: '', newline: '', trailingNewline: false, sourceUrl: sourceUrl);
|
||||
|
||||
/// The last line created within this buffer.
|
||||
CodeBufferLine? get lastLine => _lastLine;
|
||||
|
||||
/// Returns an immutable collection of the [CodeBufferLine]s within this instance.
|
||||
List<CodeBufferLine> get lines => List<CodeBufferLine>.unmodifiable(_lines);
|
||||
|
||||
@override
|
||||
bool get isEmpty => _lines.isEmpty;
|
||||
|
||||
@override
|
||||
bool get isNotEmpty => _lines.isNotEmpty;
|
||||
|
||||
@override
|
||||
int get length => _length;
|
||||
|
||||
CodeBufferLine _createLine() {
|
||||
var start = SourceLocation(
|
||||
_length,
|
||||
sourceUrl: sourceUrl,
|
||||
line: _lines.length,
|
||||
column: _indentationLevel * space.length,
|
||||
);
|
||||
var line = CodeBufferLine._(_indentationLevel, start).._end = start;
|
||||
_lines.add(_lastLine = line);
|
||||
return line;
|
||||
}
|
||||
|
||||
/// Increments the indentation level.
|
||||
void indent() {
|
||||
_indentationLevel++;
|
||||
}
|
||||
|
||||
/// Decrements the indentation level, if it is greater than `0`.
|
||||
void outdent() {
|
||||
if (_indentationLevel > 0) _indentationLevel--;
|
||||
}
|
||||
|
||||
/// Copies the contents of this [CodeBuffer] into another, preserving indentation and source mapping information.
|
||||
void copyInto(CodeBuffer other) {
|
||||
if (_lines.isEmpty) return;
|
||||
var i = 0;
|
||||
|
||||
for (var line in _lines) {
|
||||
// To compute offset:
|
||||
// 1. Find current length of other
|
||||
// 2. Add length of its newline
|
||||
// 3. Add indentation
|
||||
var column = (other._indentationLevel + line.indentationLevel) *
|
||||
other.space.length;
|
||||
var offset = other._length + other.newline.length + column;
|
||||
|
||||
// Re-compute start + end
|
||||
var start = SourceLocation(
|
||||
offset,
|
||||
sourceUrl: other.sourceUrl,
|
||||
line: other._lines.length + i,
|
||||
column: column,
|
||||
);
|
||||
|
||||
var end = SourceLocation(
|
||||
offset + line.span.length,
|
||||
sourceUrl: other.sourceUrl,
|
||||
line: start.line,
|
||||
column: column + line._buf.length,
|
||||
);
|
||||
|
||||
var clone = CodeBufferLine._(
|
||||
line.indentationLevel + other._indentationLevel, start)
|
||||
.._end = end
|
||||
.._buf.write(line._buf.toString());
|
||||
|
||||
// Adjust lastSpan
|
||||
if (line._lastSpan != null) {
|
||||
var s = line._lastSpan!.start;
|
||||
var lastSpanColumn =
|
||||
((line.indentationLevel + other._indentationLevel) *
|
||||
other.space.length) +
|
||||
line.text.indexOf(line._lastSpan!.text);
|
||||
clone._lastSpan = SourceSpan(
|
||||
SourceLocation(
|
||||
offset + s.offset,
|
||||
sourceUrl: other.sourceUrl,
|
||||
line: clone.span.start.line,
|
||||
column: lastSpanColumn,
|
||||
),
|
||||
SourceLocation(
|
||||
offset + s.offset + line._lastSpan!.length,
|
||||
sourceUrl: other.sourceUrl,
|
||||
line: clone.span.end.line,
|
||||
column: lastSpanColumn + line._lastSpan!.length,
|
||||
),
|
||||
line._lastSpan!.text,
|
||||
);
|
||||
}
|
||||
|
||||
other._lines.add(other._currentLine = other._lastLine = clone);
|
||||
|
||||
// Adjust length accordingly...
|
||||
other._length = offset + clone.span.length;
|
||||
i++;
|
||||
}
|
||||
|
||||
other.writeln();
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_lines.clear();
|
||||
_length = _indentationLevel = 0;
|
||||
_currentLine = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void writeCharCode(int charCode) {
|
||||
_currentLine ??= _createLine();
|
||||
|
||||
_currentLine!._buf.writeCharCode(charCode);
|
||||
var end = _currentLine!._end;
|
||||
_currentLine!._end = SourceLocation(
|
||||
end.offset + 1,
|
||||
sourceUrl: end.sourceUrl,
|
||||
line: end.line,
|
||||
column: end.column + 1,
|
||||
);
|
||||
_length++;
|
||||
_currentLine!._lastSpan =
|
||||
SourceSpan(end, _currentLine!._end, String.fromCharCode(charCode));
|
||||
}
|
||||
|
||||
@override
|
||||
void write(Object? obj) {
|
||||
var msg = obj.toString();
|
||||
_currentLine ??= _createLine();
|
||||
_currentLine!._buf.write(msg);
|
||||
var end = _currentLine!._end;
|
||||
_currentLine!._end = SourceLocation(
|
||||
end.offset + msg.length,
|
||||
sourceUrl: end.sourceUrl,
|
||||
line: end.line,
|
||||
column: end.column + msg.length,
|
||||
);
|
||||
_length += msg.length;
|
||||
_currentLine!._lastSpan = SourceSpan(end, _currentLine!._end, msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeln([Object? obj = '']) {
|
||||
if (obj != null && obj != '') write(obj);
|
||||
_currentLine = null;
|
||||
_length++;
|
||||
}
|
||||
|
||||
@override
|
||||
void writeAll(Iterable objects, [String separator = '']) {
|
||||
write(objects.join(separator));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var buf = StringBuffer();
|
||||
var i = 0;
|
||||
|
||||
for (var line in lines) {
|
||||
if (i++ > 0) buf.write(newline);
|
||||
for (var j = 0; j < line.indentationLevel; j++) {
|
||||
buf.write(space);
|
||||
}
|
||||
buf.write(line._buf.toString());
|
||||
}
|
||||
|
||||
if (trailingNewline == true) buf.write(newline);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a line of text within a [CodeBuffer].
|
||||
class CodeBufferLine {
|
||||
/// Mappings from one [SourceSpan] to another, to aid with generating dynamic source maps.
|
||||
final Map<SourceSpan, SourceSpan> sourceMappings = {};
|
||||
|
||||
/// The level of indentation preceding this line.
|
||||
final int indentationLevel;
|
||||
|
||||
final SourceLocation _start;
|
||||
final StringBuffer _buf = StringBuffer();
|
||||
late SourceLocation _end;
|
||||
SourceSpan? _lastSpan;
|
||||
|
||||
CodeBufferLine._(this.indentationLevel, this._start);
|
||||
|
||||
/// The [SourceSpan] corresponding to the last text written to this line.
|
||||
SourceSpan? get lastSpan => _lastSpan;
|
||||
|
||||
/// The [SourceSpan] corresponding to this entire line.
|
||||
SourceSpan get span => SourceSpan(_start, _end, _buf.toString());
|
||||
|
||||
/// The text within this line.
|
||||
String get text => _buf.toString();
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
name: angel3_code_buffer
|
||||
version: 2.0.3
|
||||
description: An advanced StringBuffer geared toward generating code, and source maps.
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/code_buffer
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
charcode: ^1.2.0
|
||||
source_span: ^1.8.1
|
||||
dev_dependencies:
|
||||
test: ^1.17.3
|
||||
pedantic: ^1.11.0
|
|
@ -1,45 +0,0 @@
|
|||
import 'package:angel3_code_buffer/angel3_code_buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
var a = CodeBuffer(), b = CodeBuffer();
|
||||
|
||||
setUp(() {
|
||||
a.writeln('outer block 1');
|
||||
b..writeln('inner block 1')..writeln('inner block 2');
|
||||
b.copyInto(a..indent());
|
||||
a
|
||||
..outdent()
|
||||
..writeln('outer block 2');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
a.clear();
|
||||
b.clear();
|
||||
});
|
||||
|
||||
test('sets correct text', () {
|
||||
expect(
|
||||
a.toString(),
|
||||
[
|
||||
'outer block 1',
|
||||
' inner block 1',
|
||||
' inner block 2',
|
||||
'outer block 2',
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
test('sets lastLine+lastSpan', () {
|
||||
var c = CodeBuffer()
|
||||
..indent()
|
||||
..write('>')
|
||||
..writeln('innermost');
|
||||
c.copyInto(a);
|
||||
expect(a.lastLine!.text, '>innermost');
|
||||
expect(a.lastLine!.span.start.column, 2);
|
||||
expect(a.lastLine!.lastSpan!.start.line, 4);
|
||||
expect(a.lastLine!.lastSpan!.start.column, 3);
|
||||
expect(a.lastLine!.lastSpan!.end.line, 4);
|
||||
expect(a.lastLine!.lastSpan!.end.column, 12);
|
||||
});
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import 'package:charcode/charcode.dart';
|
||||
import 'package:angel3_code_buffer/angel3_code_buffer.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
var buf = CodeBuffer();
|
||||
tearDown(buf.clear);
|
||||
|
||||
test('writeCharCode', () {
|
||||
buf.writeCharCode($x);
|
||||
expect(buf.lastLine!.lastSpan!.start.column, 0);
|
||||
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||
expect(buf.lastLine!.lastSpan!.end.column, 1);
|
||||
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||
});
|
||||
|
||||
test('write', () {
|
||||
buf.write('foo');
|
||||
expect(buf.lastLine!.lastSpan!.start.column, 0);
|
||||
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||
expect(buf.lastLine!.lastSpan!.end.column, 3);
|
||||
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||
});
|
||||
|
||||
test('multiple writes in one line', () {
|
||||
buf..write('foo')..write('baz');
|
||||
expect(buf.lastLine!.lastSpan!.start.column, 3);
|
||||
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||
expect(buf.lastLine!.lastSpan!.end.column, 6);
|
||||
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||
});
|
||||
|
||||
test('multiple lines', () {
|
||||
buf
|
||||
..writeln('foo')
|
||||
..write('bar')
|
||||
..write('+')
|
||||
..writeln('baz');
|
||||
expect(buf.lastLine!.lastSpan!.start.column, 4);
|
||||
expect(buf.lastLine!.lastSpan!.start.line, 1);
|
||||
expect(buf.lastLine!.lastSpan!.end.column, 7);
|
||||
expect(buf.lastLine!.lastSpan!.end.line, 1);
|
||||
});
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
import 'package:charcode/charcode.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:angel3_code_buffer/angel3_code_buffer.dart';
|
||||
|
||||
void main() {
|
||||
var buf = CodeBuffer();
|
||||
tearDown(buf.clear);
|
||||
|
||||
test('writeCharCode', () {
|
||||
buf.writeCharCode($x);
|
||||
expect(buf.toString(), 'x');
|
||||
});
|
||||
|
||||
test('write', () {
|
||||
buf.write('hello world');
|
||||
expect(buf.toString(), 'hello world');
|
||||
});
|
||||
|
||||
test('custom space', () {
|
||||
var b = CodeBuffer(space: '+')
|
||||
..writeln('foo')
|
||||
..indent()
|
||||
..writeln('baz');
|
||||
expect(b.toString(), 'foo\n+baz');
|
||||
});
|
||||
|
||||
test('custom newline', () {
|
||||
var b = CodeBuffer(newline: 'N')
|
||||
..writeln('foo')
|
||||
..indent()
|
||||
..writeln('baz');
|
||||
expect(b.toString(), 'fooN baz');
|
||||
});
|
||||
|
||||
test('trailing newline', () {
|
||||
var b = CodeBuffer(trailingNewline: true)..writeln('foo');
|
||||
expect(b.toString(), 'foo\n');
|
||||
});
|
||||
|
||||
group('multiple lines', () {
|
||||
setUp(() {
|
||||
buf..writeln('foo')..writeln('bar')..writeln('baz');
|
||||
expect(buf.lines, hasLength(3));
|
||||
expect(buf.lines[0].text, 'foo');
|
||||
expect(buf.lines[1].text, 'bar');
|
||||
expect(buf.lines[2].text, 'baz');
|
||||
});
|
||||
});
|
||||
|
||||
test('indent', () {
|
||||
buf
|
||||
..writeln('foo')
|
||||
..indent()
|
||||
..writeln('bar')
|
||||
..indent()
|
||||
..writeln('baz')
|
||||
..outdent()
|
||||
..writeln('quux')
|
||||
..outdent()
|
||||
..writeln('end');
|
||||
expect(buf.toString(), 'foo\n bar\n baz\n quux\nend');
|
||||
});
|
||||
|
||||
group('sets lastLine text', () {
|
||||
test('writeCharCode', () {
|
||||
buf.writeCharCode($x);
|
||||
expect(buf.lastLine!.text, 'x');
|
||||
});
|
||||
|
||||
test('write', () {
|
||||
buf.write('hello world');
|
||||
expect(buf.lastLine!.text, 'hello world');
|
||||
});
|
||||
});
|
||||
|
||||
group('sets lastLine lastSpan', () {
|
||||
test('writeCharCode', () {
|
||||
buf.writeCharCode($x);
|
||||
expect(buf.lastLine!.lastSpan!.text, 'x');
|
||||
});
|
||||
|
||||
test('write', () {
|
||||
buf.write('hello world');
|
||||
expect(buf.lastLine!.lastSpan!.text, 'hello world');
|
||||
});
|
||||
});
|
||||
}
|
71
packages/combinator/.gitignore
vendored
71
packages/combinator/.gitignore
vendored
|
@ -1,71 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
.project
|
||||
.buildlog
|
||||
**/packages/
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
|
||||
## VsCode
|
||||
.vscode/
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
/out/
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
|
@ -1,4 +0,0 @@
|
|||
language: dart
|
||||
dart:
|
||||
- stable
|
||||
- dev
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,20 +0,0 @@
|
|||
# 2.0.2
|
||||
* Resolve static analysis warnings
|
||||
|
||||
# 2.0.1
|
||||
* Updated README
|
||||
|
||||
# 2.0.0
|
||||
* Migrated to support Dart SDK 2.12.x NNBD
|
||||
|
||||
# 1.1.0
|
||||
* Add `tupleX` parsers. Hooray for strong typing!
|
||||
|
||||
# 1.0.0+3
|
||||
* `then` now *always* returns `dynamic`.
|
||||
|
||||
# 1.0.0+2
|
||||
* `star` now includes with a call to `opt`.
|
||||
* Added comments.
|
||||
* Enforce generics on `separatedBy`.
|
||||
* Enforce Dart 2 semantics.
|
|
@ -1,21 +0,0 @@
|
|||
MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,123 +0,0 @@
|
|||
# angel3_combinator
|
||||
[![version](https://img.shields.io/badge/pub-v2.0.2-brightgreen)](https://pub.dartlang.org/packages/angel3_combinator)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/combinator/LICENSE)
|
||||
|
||||
Packrat parser combinators that support static typing, generics, file spans, memoization, and more.
|
||||
|
||||
**RECOMMENDED:**
|
||||
Check `example/` for examples.
|
||||
The examples contain examples of using:
|
||||
* Generic typing
|
||||
* Reading `FileSpan` from `ParseResult`
|
||||
* More...
|
||||
|
||||
## Basic Usage
|
||||
```dart
|
||||
void main() {
|
||||
// Parse a Pattern (usually String or RegExp).
|
||||
var foo = match('foo');
|
||||
var number = match(RegExp(r'[0-9]+'), errorMessage: 'Expected a number.');
|
||||
|
||||
// Set a value.
|
||||
var numWithValue = number.map((r) => int.parse(r.span.text));
|
||||
|
||||
// Expect a pattern, or nothing.
|
||||
var optional = numWithValue.opt();
|
||||
|
||||
// Expect a pattern zero or more times.
|
||||
var star = optional.star();
|
||||
|
||||
// Expect one or more times.
|
||||
var plus = optional.plus();
|
||||
|
||||
// Expect an arbitrary number of times.
|
||||
var threeTimes = optional.times(3);
|
||||
|
||||
// Expect a sequence of patterns.
|
||||
var doraTheExplorer = chain([
|
||||
match('Dora').space(),
|
||||
match('the').space(),
|
||||
match('Explorer').space(),
|
||||
]);
|
||||
|
||||
// Choose exactly one of a set of patterns, whichever
|
||||
// appears first.
|
||||
var alt = any([
|
||||
match('1'),
|
||||
match('11'),
|
||||
match('111'),
|
||||
]);
|
||||
|
||||
// Choose the *longest* match for any of the given alternatives.
|
||||
var alt2 = longest([
|
||||
match('1'),
|
||||
match('11'),
|
||||
match('111'),
|
||||
]);
|
||||
|
||||
// Friendly operators
|
||||
var fooOrNumber = foo | number;
|
||||
var fooAndNumber = foo & number;
|
||||
var notFoo = ~foo;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Messages
|
||||
Parsers without descriptive error messages can lead to frustrating dead-ends
|
||||
for end-users. Fortunately, `angel3_combinator` is built with error handling in mind.
|
||||
|
||||
```dart
|
||||
void main(Parser parser) {
|
||||
// Append an arbitrary error message to a parser if it is not matched.
|
||||
var withError = parser.error(errorMessage: 'Hey!!! Wrong!!!');
|
||||
|
||||
// You can also set the severity of an error.
|
||||
var asHint = parser.error(severity: SyntaxErrorSeverity.hint);
|
||||
|
||||
// Constructs like `any`, `chain`, and `longest` support this as well.
|
||||
var foo = longest([
|
||||
parser.error(errorMessage: 'foo'),
|
||||
parser.error(errorMessage: 'bar')
|
||||
], errorMessage: 'Expected a "foo" or a "bar"');
|
||||
|
||||
// If multiple errors are present at one location,
|
||||
// it can create a lot of noise.
|
||||
//
|
||||
// Use `foldErrors` to only take one error at a given location.
|
||||
var lessNoise = parser.foldErrors();
|
||||
}
|
||||
```
|
||||
|
||||
## Whitespaces
|
||||
Handling optional whitespace is dead-easy:
|
||||
|
||||
```dart
|
||||
void main(Parser parser) {
|
||||
var optionalSpace = parser.space();
|
||||
}
|
||||
```
|
||||
|
||||
## For Programming Languages
|
||||
`angel3_combinator` was conceived to make writing parsers for complex grammars easier,
|
||||
namely programming languages. Thus, there are functions built-in to make common constructs
|
||||
easier:
|
||||
|
||||
```dart
|
||||
void main(Parser parser) {
|
||||
var array = parser
|
||||
.separatedByComma()
|
||||
.surroundedBySquareBrackets(defaultValue: []);
|
||||
|
||||
var braces = parser.surroundedByCurlyBraces();
|
||||
|
||||
var sep = parser.separatedBy(match('!').space());
|
||||
}
|
||||
```
|
||||
|
||||
## Differences between this and Petitparser
|
||||
* `angel3_combinator` makes extensive use of Dart's dynamic typing
|
||||
* `angel3_combinator` supports detailed error messages (with configurable severity)
|
||||
* `angel3_combinator` keeps track of locations (ex. `line 1: 3`)
|
|
@ -1,5 +0,0 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
#implicit-dynamic: false
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,56 +0,0 @@
|
|||
// Run this with "Basic QWxhZGRpbjpPcGVuU2VzYW1l"
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
/// Parse a part of a decoded Basic auth string.
|
||||
///
|
||||
/// Namely, the `username` or `password` in `{username}:{password}`.
|
||||
final Parser<String> string =
|
||||
match<String>(RegExp(r'[^:$]+'), errorMessage: 'Expected a string.')
|
||||
.value((r) => r.span!.text);
|
||||
|
||||
/// Transforms `{username}:{password}` to `{"username": username, "password": password}`.
|
||||
final Parser<Map<String, String>> credentials = chain<String>([
|
||||
string.opt(),
|
||||
match<String>(':'),
|
||||
string.opt(),
|
||||
]).map<Map<String, String>>(
|
||||
(r) => {'username': r.value![0], 'password': r.value![2]});
|
||||
|
||||
/// We can actually embed a parser within another parser.
|
||||
///
|
||||
/// This is used here to BASE64URL-decode a string, and then
|
||||
/// parse the decoded string.
|
||||
final Parser credentialString = match<Map<String, String>?>(
|
||||
RegExp(r'([^\n$]+)'),
|
||||
errorMessage: 'Expected a credential string.')
|
||||
.value((r) {
|
||||
var decoded = utf8.decode(base64Url.decode(r.span!.text));
|
||||
var scanner = SpanScanner(decoded);
|
||||
return credentials.parse(scanner).value;
|
||||
});
|
||||
|
||||
final Parser basic = match<Null>('Basic').space();
|
||||
|
||||
final Parser basicAuth = basic.then(credentialString).index(1);
|
||||
|
||||
void main() {
|
||||
while (true) {
|
||||
stdout.write('Enter a basic auth value: ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = basicAuth.parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
print(error.toolString);
|
||||
print(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
/// Note: This grammar does not handle precedence, for the sake of simplicity.
|
||||
Parser<num> calculatorGrammar() {
|
||||
var expr = reference<num>();
|
||||
|
||||
var number = match<num>(RegExp(r'-?[0-9]+(\.[0-9]+)?'))
|
||||
.value((r) => num.parse(r.span!.text));
|
||||
|
||||
var hex = match<int>(RegExp(r'0x([A-Fa-f0-9]+)'))
|
||||
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 16));
|
||||
|
||||
var binary = match<int>(RegExp(r'([0-1]+)b'))
|
||||
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 2));
|
||||
|
||||
var alternatives = <Parser<num>>[];
|
||||
|
||||
void registerBinary(String op, num Function(num, num) f) {
|
||||
alternatives.add(
|
||||
chain<num>([
|
||||
expr.space(),
|
||||
match<Null>(op).space() as Parser<num>,
|
||||
expr.space(),
|
||||
]).map((r) => f(r.value![0], r.value![2])),
|
||||
);
|
||||
}
|
||||
|
||||
registerBinary('**', (a, b) => pow(a, b));
|
||||
registerBinary('*', (a, b) => a * b);
|
||||
registerBinary('/', (a, b) => a / b);
|
||||
registerBinary('%', (a, b) => a % b);
|
||||
registerBinary('+', (a, b) => a + b);
|
||||
registerBinary('-', (a, b) => a - b);
|
||||
registerBinary('^', (a, b) => a.toInt() ^ b.toInt());
|
||||
registerBinary('&', (a, b) => a.toInt() & b.toInt());
|
||||
registerBinary('|', (a, b) => a.toInt() | b.toInt());
|
||||
|
||||
alternatives.addAll([
|
||||
number,
|
||||
hex,
|
||||
binary,
|
||||
expr.parenthesized(),
|
||||
]);
|
||||
|
||||
expr.parser = longest(alternatives);
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
void main() {
|
||||
var calculator = calculatorGrammar();
|
||||
|
||||
while (true) {
|
||||
stdout.write('Enter an expression: ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = calculator.parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
stderr.writeln(error.toolString);
|
||||
stderr.writeln(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
final Parser<String> id =
|
||||
match<String>(RegExp(r'[A-Za-z]+')).value((r) => r.span!.text);
|
||||
|
||||
// We can use `separatedBy` to easily construct parser
|
||||
// that can be matched multiple times, separated by another
|
||||
// pattern.
|
||||
//
|
||||
// This is useful for parsing arrays or map literals.
|
||||
void main() {
|
||||
while (true) {
|
||||
stdout.write('Enter a string (ex "a,b,c"): ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = id.separatedBy(match(',').space()).parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
print(error.toolString);
|
||||
print(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
Parser jsonGrammar() {
|
||||
var expr = reference();
|
||||
|
||||
// Parse a number
|
||||
var number = match<num>(RegExp(r'-?[0-9]+(\.[0-9]+)?'),
|
||||
errorMessage: 'Expected a number.')
|
||||
.value(
|
||||
(r) => num.parse(r.span!.text),
|
||||
);
|
||||
|
||||
// Parse a string (no escapes supported, because lazy).
|
||||
var string =
|
||||
match(RegExp(r'"[^"]*"'), errorMessage: 'Expected a string.').value(
|
||||
(r) => r.span!.text.substring(1, r.span!.text.length - 1),
|
||||
);
|
||||
|
||||
// Parse an array
|
||||
var array = expr
|
||||
.space()
|
||||
.separatedByComma()
|
||||
.surroundedBySquareBrackets(defaultValue: []);
|
||||
|
||||
// KV pair
|
||||
var keyValuePair = chain([
|
||||
string.space(),
|
||||
match(':').space(),
|
||||
expr.error(errorMessage: 'Missing expression.'),
|
||||
]).castDynamic().cast<Map>().value((r) => {r.value![0]: r.value![2]});
|
||||
|
||||
// Parse an object.
|
||||
var object = keyValuePair
|
||||
.separatedByComma()
|
||||
.castDynamic()
|
||||
.surroundedByCurlyBraces(defaultValue: {});
|
||||
|
||||
expr.parser = longest(
|
||||
[
|
||||
array,
|
||||
number,
|
||||
string,
|
||||
object.error(),
|
||||
],
|
||||
errorMessage: 'Expected an expression.',
|
||||
).space();
|
||||
|
||||
return expr.foldErrors();
|
||||
}
|
||||
|
||||
void main() {
|
||||
var JSON = jsonGrammar();
|
||||
|
||||
while (true) {
|
||||
stdout.write('Enter some JSON: ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = JSON.parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
print(error.toolString);
|
||||
print(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
final Parser minus = match('-');
|
||||
|
||||
final Parser<int> digit =
|
||||
match(RegExp(r'[0-9]'), errorMessage: 'Expected a number');
|
||||
|
||||
final Parser digits = digit.plus();
|
||||
|
||||
final Parser dot = match('.');
|
||||
|
||||
final Parser decimal = ( // digits, (dot, digits)?
|
||||
digits & (dot & digits).opt() //
|
||||
);
|
||||
|
||||
final Parser number = //
|
||||
(minus.opt() & decimal) // minus?, decimal
|
||||
.map<num>((r) => num.parse(r.span!.text));
|
||||
|
||||
void main() {
|
||||
while (true) {
|
||||
stdout.write('Enter a number: ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = number.parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
stderr.writeln(error.toolString);
|
||||
stderr.writeln(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// For some reason, this cannot be run in checked mode???
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
final Parser<String> key =
|
||||
match<String>(RegExp(r'[^=&\n]+'), errorMessage: 'Missing k/v')
|
||||
.value((r) => r.span!.text);
|
||||
|
||||
final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value!));
|
||||
|
||||
final Parser pair = chain([
|
||||
key,
|
||||
match('='),
|
||||
value,
|
||||
]).map((r) {
|
||||
return {
|
||||
r.value![0]: r.value![2],
|
||||
};
|
||||
});
|
||||
|
||||
final Parser pairs = pair
|
||||
.separatedBy(match(r'&'))
|
||||
.map((r) => r.value!.reduce((a, b) => a..addAll(b)));
|
||||
|
||||
final Parser queryString = pairs.opt();
|
||||
|
||||
void main() {
|
||||
while (true) {
|
||||
stdout.write('Enter a query string: ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||
var result = pairs.parse(scanner);
|
||||
|
||||
if (!result.successful) {
|
||||
for (var error in result.errors) {
|
||||
print(error.toolString);
|
||||
print(error.span!.highlight(color: true));
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
void main() {
|
||||
var expr = reference();
|
||||
var symbols = <String, dynamic>{};
|
||||
|
||||
void registerFunction(String name, int nArgs, Function(List<num>) f) {
|
||||
symbols[name] = Tuple2(nArgs, f);
|
||||
}
|
||||
|
||||
registerFunction('**', 2, (args) => pow(args[0], args[1]));
|
||||
registerFunction('*', 2, (args) => args[0] * args[1]);
|
||||
registerFunction('/', 2, (args) => args[0] / args[1]);
|
||||
registerFunction('%', 2, (args) => args[0] % args[1]);
|
||||
registerFunction('+', 2, (args) => args[0] + args[1]);
|
||||
registerFunction('-', 2, (args) => args[0] - args[1]);
|
||||
registerFunction('.', 1, (args) => args[0].toDouble());
|
||||
registerFunction('print', 1, (args) {
|
||||
print(args[0]);
|
||||
return args[0];
|
||||
});
|
||||
|
||||
var number =
|
||||
match(RegExp(r'[0-9]+(\.[0-9]+)?'), errorMessage: 'Expected a number.')
|
||||
.map((r) => num.parse(r.span!.text));
|
||||
|
||||
var id = match(
|
||||
RegExp(
|
||||
r'[A-Za-z_!\\$",\\+-\\./:;\\?<>%&\\*@\[\]\\{\}\\|`\\^~][A-Za-z0-9_!\\$",\\+-\\./:;\\?<>%&\*@\[\]\\{\}\\|`\\^~]*'),
|
||||
errorMessage: 'Expected an ID')
|
||||
.map((r) => symbols[r.span!.text] ??=
|
||||
throw "Undefined symbol: '${r.span!.text}'");
|
||||
|
||||
var atom = number.castDynamic().or(id);
|
||||
|
||||
var list = expr.space().times(2, exact: false).map((r) {
|
||||
try {
|
||||
var out = [];
|
||||
var q = Queue.from(r.value!.reversed);
|
||||
|
||||
while (q.isNotEmpty) {
|
||||
var current = q.removeFirst();
|
||||
if (current is! Tuple2) {
|
||||
out.insert(0, current);
|
||||
} else {
|
||||
var args = [];
|
||||
for (var i = 0; i < (current.item1 as num); i++) {
|
||||
args.add(out.removeLast());
|
||||
}
|
||||
out.add(current.item2(args));
|
||||
}
|
||||
}
|
||||
|
||||
return out.length == 1 ? out.first : out;
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
expr.parser = longest([
|
||||
list,
|
||||
atom,
|
||||
expr.parenthesized(),
|
||||
]); //list | atom | expr.parenthesized();
|
||||
|
||||
while (true) {
|
||||
stdout.write('> ');
|
||||
var line = stdin.readLineSync()!;
|
||||
var result = expr.parse(SpanScanner(line));
|
||||
|
||||
if (result.errors.isNotEmpty) {
|
||||
for (var error in result.errors) {
|
||||
print(error.toolString);
|
||||
print(error.message);
|
||||
}
|
||||
} else {
|
||||
print(result.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
void main() {
|
||||
var pub = match('pub').map((r) => r.span!.text).space();
|
||||
var dart = match('dart').map((r) => 24).space();
|
||||
var lang = match('lang').map((r) => true).space();
|
||||
|
||||
// Parses a Tuple3<String, int, bool>
|
||||
var grammar = tuple3(pub, dart, lang);
|
||||
|
||||
var scanner = SpanScanner('pub dart lang');
|
||||
print(grammar.parse(scanner).value);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export 'src/combinator/combinator.dart';
|
||||
export 'src/error.dart';
|
|
@ -1,26 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Advance<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final int amount;
|
||||
|
||||
_Advance(this.parser, this.amount);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||
if (result.successful) args.scanner.position += amount;
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('advance($amount) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/// Matches any one of the given [parsers].
|
||||
///
|
||||
/// If [backtrack] is `true` (default), a failed parse will not modify the scanner state.
|
||||
///
|
||||
/// You can provide a custom [errorMessage]. You can set it to `false` to not
|
||||
/// generate any error at all.
|
||||
Parser<T> any<T>(Iterable<Parser<T>> parsers,
|
||||
{bool backtrack = true, errorMessage, SyntaxErrorSeverity? severity}) {
|
||||
return _Any(parsers, backtrack != false, errorMessage,
|
||||
severity ?? SyntaxErrorSeverity.error);
|
||||
}
|
||||
|
||||
class _Any<T> extends Parser<T> {
|
||||
final Iterable<Parser<T>> parsers;
|
||||
final bool backtrack;
|
||||
final errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Any(this.parsers, this.backtrack, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> _parse(ParseArgs args) {
|
||||
var inactive = parsers
|
||||
.where((p) => !args.trampoline.isActive(p, args.scanner.position));
|
||||
|
||||
if (inactive.isEmpty) {
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||
}
|
||||
|
||||
var errors = <SyntaxError>[];
|
||||
var replay = args.scanner.position;
|
||||
|
||||
for (var parser in inactive) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (result.successful) {
|
||||
return result;
|
||||
} else {
|
||||
if (backtrack) args.scanner.position = replay;
|
||||
if (parser is _Alt) errors.addAll(result.errors);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessage != false) {
|
||||
errors.add(
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage?.toString() ??
|
||||
'No match found for ${parsers.length} alternative(s)',
|
||||
args.scanner.emptySpan,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||
}
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
// Never called
|
||||
throw ArgumentError('[Combinator] Invalid method call');
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('any(${parsers.length}) (')
|
||||
..indent();
|
||||
var i = 1;
|
||||
|
||||
for (var parser in parsers) {
|
||||
buffer
|
||||
..writeln('#${i++}:')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer.outdent();
|
||||
}
|
||||
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Cache<T> extends Parser<T> {
|
||||
final Map<int, ParseResult<T>> _cache = {};
|
||||
final Parser<T> parser;
|
||||
|
||||
_Cache(this.parser);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
return _cache.putIfAbsent(args.scanner.position, () {
|
||||
return parser._parse(args.increaseDepth());
|
||||
}).change(parser: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('cache(${_cache.length}) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Cast<T, U extends T> extends Parser<U> {
|
||||
final Parser<T> parser;
|
||||
|
||||
_Cast(this.parser);
|
||||
|
||||
@override
|
||||
ParseResult<U> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
return ParseResult<U>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
result.errors,
|
||||
span: result.span,
|
||||
value: result.value as U?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('cast<$U> (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
||||
|
||||
class _CastDynamic<T> extends Parser<dynamic> {
|
||||
final Parser<T> parser;
|
||||
|
||||
_CastDynamic(this.parser);
|
||||
|
||||
@override
|
||||
ParseResult<dynamic> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
return ParseResult<dynamic>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
result.errors,
|
||||
span: result.span,
|
||||
value: result.value,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('cast<dynamic> (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/// Expects to parse a sequence of [parsers].
|
||||
///
|
||||
/// If [failFast] is `true` (default), then the first failure to parse will abort the parse.
|
||||
ListParser<T> chain<T>(Iterable<Parser<T>> parsers,
|
||||
{bool failFast = true, SyntaxErrorSeverity? severity}) {
|
||||
return _Chain<T>(
|
||||
parsers, failFast != false, severity ?? SyntaxErrorSeverity.error);
|
||||
}
|
||||
|
||||
class _Alt<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final String? errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Alt(this.parser, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
return result.successful
|
||||
? result
|
||||
: result.addErrors([
|
||||
SyntaxError(
|
||||
severity, errorMessage, result.span ?? args.scanner.emptySpan),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
parser.stringify(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class _Chain<T> extends ListParser<T> {
|
||||
final Iterable<Parser<T>> parsers;
|
||||
final bool failFast;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Chain(this.parsers, this.failFast, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||
var errors = <SyntaxError>[];
|
||||
var results = <T>[];
|
||||
var spans = <FileSpan>[];
|
||||
var successful = true;
|
||||
|
||||
for (var parser in parsers) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) {
|
||||
if (parser is _Alt) errors.addAll(result.errors);
|
||||
|
||||
if (failFast) {
|
||||
return ParseResult(
|
||||
args.trampoline, args.scanner, this, false, result.errors);
|
||||
}
|
||||
|
||||
successful = false;
|
||||
}
|
||||
|
||||
if (result.value != null) {
|
||||
results.add(result.value!);
|
||||
} else {
|
||||
results.add('NULL' as T);
|
||||
}
|
||||
|
||||
if (result.span != null) {
|
||||
spans.add(result.span!);
|
||||
}
|
||||
}
|
||||
|
||||
FileSpan? span;
|
||||
|
||||
if (spans.isNotEmpty) {
|
||||
span = spans.reduce((a, b) => a.expand(b));
|
||||
}
|
||||
|
||||
return ParseResult<List<T>>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
successful,
|
||||
errors,
|
||||
span: span,
|
||||
value: List<T>.unmodifiable(results),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('chain(${parsers.length}) (')
|
||||
..indent();
|
||||
var i = 1;
|
||||
|
||||
for (var parser in parsers) {
|
||||
buffer
|
||||
..writeln('#${i++}:')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer.outdent();
|
||||
}
|
||||
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Check<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final Matcher matcher;
|
||||
final String? errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Check(this.parser, this.matcher, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var matchState = {};
|
||||
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||
if (!result.successful) {
|
||||
return result;
|
||||
} else if (!matcher.matches(result.value, matchState)) {
|
||||
return result.change(successful: false).addErrors([
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage ??
|
||||
matcher.describe(StringDescription('Expected ')).toString() + '.',
|
||||
result.span,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
var d = matcher.describe(StringDescription());
|
||||
buffer
|
||||
..writeln('check($d) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,394 +0,0 @@
|
|||
library lex.src.combinator;
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:angel3_code_buffer/angel3_code_buffer.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import '../error.dart';
|
||||
|
||||
part 'any.dart';
|
||||
|
||||
part 'advance.dart';
|
||||
|
||||
part 'cache.dart';
|
||||
|
||||
part 'cast.dart';
|
||||
|
||||
part 'chain.dart';
|
||||
|
||||
part 'check.dart';
|
||||
|
||||
part 'compare.dart';
|
||||
|
||||
part 'fold_errors.dart';
|
||||
|
||||
part 'index.dart';
|
||||
|
||||
part 'longest.dart';
|
||||
|
||||
part 'map.dart';
|
||||
|
||||
part 'match.dart';
|
||||
|
||||
part 'max_depth.dart';
|
||||
|
||||
part 'negate.dart';
|
||||
|
||||
part 'opt.dart';
|
||||
|
||||
part 'recursion.dart';
|
||||
|
||||
part 'reduce.dart';
|
||||
|
||||
part 'reference.dart';
|
||||
|
||||
part 'repeat.dart';
|
||||
|
||||
part 'safe.dart';
|
||||
|
||||
part 'to_list.dart';
|
||||
|
||||
part 'util.dart';
|
||||
|
||||
part 'value.dart';
|
||||
|
||||
class ParseArgs {
|
||||
final Trampoline trampoline;
|
||||
final SpanScanner scanner;
|
||||
final int depth;
|
||||
|
||||
ParseArgs(this.trampoline, this.scanner, this.depth);
|
||||
|
||||
ParseArgs increaseDepth() => ParseArgs(trampoline, scanner, depth + 1);
|
||||
}
|
||||
|
||||
/// A parser combinator, which can parse very complicated grammars in a manageable manner.
|
||||
abstract class Parser<T> {
|
||||
ParseResult<T> __parse(ParseArgs args);
|
||||
|
||||
ParseResult<T> _parse(ParseArgs args) {
|
||||
var pos = args.scanner.position;
|
||||
|
||||
if (args.trampoline.hasMemoized(this, pos)) {
|
||||
return args.trampoline.getMemoized<T>(this, pos);
|
||||
}
|
||||
|
||||
if (args.trampoline.isActive(this, pos)) {
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||
}
|
||||
|
||||
args.trampoline.enter(this, pos);
|
||||
var result = __parse(args);
|
||||
args.trampoline.memoize(this, pos, result);
|
||||
args.trampoline.exit(this);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parses text from a [SpanScanner].
|
||||
ParseResult<T> parse(SpanScanner scanner, [int depth = 1]) {
|
||||
var args = ParseArgs(Trampoline(), scanner, depth);
|
||||
return _parse(args);
|
||||
}
|
||||
|
||||
/// Skips forward a certain amount of steps after parsing, if it was successful.
|
||||
Parser<T> forward(int amount) => _Advance<T>(this, amount);
|
||||
|
||||
/// Moves backward a certain amount of steps after parsing, if it was successful.
|
||||
Parser<T> back(int amount) => _Advance<T>(this, amount * -1);
|
||||
|
||||
/// Casts this parser to produce [U] objects.
|
||||
Parser<U> cast<U extends T>() => _Cast<T, U>(this);
|
||||
|
||||
/// Casts this parser to produce [dynamic] objects.
|
||||
Parser<dynamic> castDynamic() => _CastDynamic<T>(this);
|
||||
|
||||
/// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object.
|
||||
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>) f) {
|
||||
return _Change<T, U>(this, f);
|
||||
}
|
||||
|
||||
/// Validates the parse result against a [Matcher].
|
||||
///
|
||||
/// You can provide a custom [errorMessage].
|
||||
Parser<T> check(Matcher matcher,
|
||||
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||
_Check<T>(
|
||||
this, matcher, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||
|
||||
/// Binds an [errorMessage] to a copy of this parser.
|
||||
Parser<T> error({String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||
_Alt<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||
|
||||
/// Removes multiple errors that occur in the same spot; this can reduce noise in parser output.
|
||||
Parser<T> foldErrors({bool Function(SyntaxError a, SyntaxError b)? equal}) {
|
||||
equal ??= (b, e) => b.span?.start.offset == e.span?.start.offset;
|
||||
return _FoldErrors<T>(this, equal);
|
||||
}
|
||||
|
||||
/// Transforms the parse result using a unary function.
|
||||
Parser<U> map<U>(U Function(ParseResult<T>) f) {
|
||||
return _Map<T, U>(this, f);
|
||||
}
|
||||
|
||||
/// Prevents recursion past a certain [depth], preventing stack overflow errors.
|
||||
Parser<T> maxDepth(int depth) => _MaxDepth<T>(this, depth);
|
||||
|
||||
Parser<T> operator ~() => negate();
|
||||
|
||||
/// Ensures this pattern is not matched.
|
||||
///
|
||||
/// You can provide an [errorMessage].
|
||||
Parser<T> negate(
|
||||
{String errorMessage = 'Negate error',
|
||||
SyntaxErrorSeverity severity = SyntaxErrorSeverity.error}) =>
|
||||
_Negate<T>(this, errorMessage, severity);
|
||||
|
||||
/// Caches the results of parse attempts at various locations within the source text.
|
||||
///
|
||||
/// Use this to prevent excessive recursion.
|
||||
Parser<T> cache() => _Cache<T>(this);
|
||||
|
||||
Parser<T> operator &(Parser<T> other) => and(other);
|
||||
|
||||
/// Consumes `this` and another parser, but only considers the result of `this` parser.
|
||||
Parser<T> and(Parser other) => then(other).change<T>((r) {
|
||||
return ParseResult<T>(
|
||||
r.trampoline,
|
||||
r.scanner,
|
||||
this,
|
||||
r.successful,
|
||||
r.errors,
|
||||
span: r.span,
|
||||
value: (r.value != null ? r.value![0] : r.value) as T?,
|
||||
);
|
||||
});
|
||||
|
||||
Parser<T> operator |(Parser<T> other) => or(other);
|
||||
|
||||
/// Shortcut for [or]-ing two parsers.
|
||||
Parser<T> or<U>(Parser<T> other) => any<T>([this, other]);
|
||||
|
||||
/// Parses this sequence one or more times.
|
||||
ListParser<T> plus() => times(1, exact: false);
|
||||
|
||||
/// Safely escapes this parser when an error occurs.
|
||||
///
|
||||
/// The generated parser only runs once; repeated uses always exit eagerly.
|
||||
Parser<T> safe(
|
||||
{bool backtrack = true,
|
||||
String errorMessage = 'error',
|
||||
SyntaxErrorSeverity? severity}) =>
|
||||
_Safe<T>(
|
||||
this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||
|
||||
Parser<List<T>> separatedByComma() =>
|
||||
separatedBy(match<List<T>>(',').space());
|
||||
|
||||
/// Expects to see an infinite amounts of the pattern, separated by the [other] pattern.
|
||||
///
|
||||
/// Use this as a shortcut to parse arrays, parameter lists, etc.
|
||||
Parser<List<T>> separatedBy(Parser other) {
|
||||
var suffix = other.then(this).index(1).cast<T>();
|
||||
return then(suffix.star()).map((r) {
|
||||
var v = r.value;
|
||||
if (v == null || v.length < 2) {
|
||||
return [];
|
||||
}
|
||||
var preceding = v.isEmpty ? [] : (v[0] == null ? [] : [v[0]]);
|
||||
var out = List<T>.from(preceding);
|
||||
if (v[1] != null && v[1] != 'NULL') {
|
||||
v[1].forEach((element) {
|
||||
out.add(element as T);
|
||||
});
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
Parser<T> surroundedByCurlyBraces({required T defaultValue}) => opt()
|
||||
.surroundedBy(match('{').space(), match('}').space())
|
||||
.map((r) => r.value ?? defaultValue);
|
||||
|
||||
Parser<T> surroundedBySquareBrackets({required T defaultValue}) => opt()
|
||||
.surroundedBy(match('[').space(), match(']').space())
|
||||
.map((r) => r.value ?? defaultValue);
|
||||
|
||||
/// Expects to see the pattern, surrounded by the others.
|
||||
///
|
||||
/// If no [right] is provided, it expects to see the same pattern on both sides.
|
||||
/// Use this parse things like parenthesized expressions, arrays, etc.
|
||||
Parser<T> surroundedBy(Parser left, [Parser? right]) {
|
||||
return chain([
|
||||
left,
|
||||
this,
|
||||
right ?? left,
|
||||
]).index(1).castDynamic().cast<T>();
|
||||
}
|
||||
|
||||
/// Parses `this`, either as-is or wrapped in parentheses.
|
||||
Parser<T> maybeParenthesized() {
|
||||
return any([parenthesized(), this]);
|
||||
}
|
||||
|
||||
/// Parses `this`, wrapped in parentheses.
|
||||
Parser<T> parenthesized() =>
|
||||
surroundedBy(match('(').space(), match(')').space());
|
||||
|
||||
/// Consumes any trailing whitespace.
|
||||
Parser<T> space() => trail(RegExp(r'[ \n\r\t]+'));
|
||||
|
||||
/// Consumes 0 or more instance(s) of this parser.
|
||||
ListParser<T> star({bool backtrack = true}) =>
|
||||
times(1, exact: false, backtrack: backtrack).opt();
|
||||
|
||||
/// Shortcut for [chain]-ing two parsers together.
|
||||
ListParser<dynamic> then(Parser other) => chain<dynamic>([this, other]);
|
||||
|
||||
/// Casts this instance into a [ListParser].
|
||||
ListParser<T> toList() => _ToList<T>(this);
|
||||
|
||||
/// Consumes and ignores any trailing occurrences of [pattern].
|
||||
Parser<T> trail(Pattern pattern) =>
|
||||
then(match(pattern).opt()).first().cast<T>();
|
||||
|
||||
/// Expect this pattern a certain number of times.
|
||||
///
|
||||
/// If [exact] is `false` (default: `true`), then the generated parser will accept
|
||||
/// an infinite amount of occurrences after the specified [count].
|
||||
///
|
||||
/// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences.
|
||||
ListParser<T> times(int count,
|
||||
{bool exact = true,
|
||||
String tooFew = 'Too few',
|
||||
String tooMany = 'Too many',
|
||||
bool backtrack = true,
|
||||
SyntaxErrorSeverity? severity}) {
|
||||
return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,
|
||||
severity ?? SyntaxErrorSeverity.error);
|
||||
}
|
||||
|
||||
/// Produces an optional copy of this parser.
|
||||
///
|
||||
/// If [backtrack] is `true` (default), then a failed parse will not
|
||||
/// modify the scanner state.
|
||||
Parser<T> opt({bool backtrack = true}) => _Opt(this, backtrack);
|
||||
|
||||
/// Sets the value of the [ParseResult].
|
||||
Parser<T> value(T Function(ParseResult<T?>) f) {
|
||||
return _Value<T>(this, f);
|
||||
}
|
||||
|
||||
/// Prints a representation of this parser, ideally without causing a stack overflow.
|
||||
void stringify(CodeBuffer buffer);
|
||||
}
|
||||
|
||||
/// A [Parser] that produces [List]s of a type [T].
|
||||
abstract class ListParser<T> extends Parser<List<T>> {
|
||||
/// Shortcut for calling [index] with `0`.
|
||||
Parser<T> first() => index(0);
|
||||
|
||||
/// Modifies this parser to only return the value at the given index [i].
|
||||
Parser<T> index(int i) => _Index<T>(this, i);
|
||||
|
||||
/// Shortcut for calling [index] with the greatest-possible index.
|
||||
Parser<T> last() => index(-1);
|
||||
|
||||
/// Modifies this parser to call `List.reduce` on the parsed values.
|
||||
Parser<T> reduce(T Function(T, T) combine) => _Reduce<T>(this, combine);
|
||||
|
||||
/// Sorts the parsed values, using the given [Comparator].
|
||||
ListParser<T> sort(Comparator<T> compare) => _Compare(this, compare);
|
||||
|
||||
@override
|
||||
ListParser<T> opt({bool backtrack = true}) => _ListOpt(this, backtrack);
|
||||
|
||||
/// Modifies this parser, returning only the values that match a predicate.
|
||||
Parser<List<T>> where(bool Function(T) f) =>
|
||||
map<List<T>>((r) => r.value?.where(f).toList() ?? []);
|
||||
|
||||
/// Condenses a [ListParser] into having a value of the combined span's text.
|
||||
Parser<String> flatten() => map<String>((r) => r.span?.text ?? '');
|
||||
}
|
||||
|
||||
/// Prevents stack overflow in recursive parsers.
|
||||
class Trampoline {
|
||||
final Map<Parser, Queue<int>> _active = {};
|
||||
final Map<Parser, List<Tuple2<int, ParseResult>>> _memo = {};
|
||||
|
||||
bool hasMemoized(Parser parser, int position) {
|
||||
var list = _memo[parser];
|
||||
return list?.any((t) => t.item1 == position) == true;
|
||||
}
|
||||
|
||||
ParseResult<T> getMemoized<T>(Parser parser, int position) {
|
||||
return _memo[parser]?.firstWhere((t) => t.item1 == position).item2
|
||||
as ParseResult<T>;
|
||||
}
|
||||
|
||||
void memoize(Parser parser, int position, ParseResult? result) {
|
||||
if (result != null) {
|
||||
var list = _memo.putIfAbsent(parser, () => []);
|
||||
var tuple = Tuple2(position, result);
|
||||
if (!list.contains(tuple)) list.add(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
bool isActive(Parser parser, int position) {
|
||||
if (!_active.containsKey(parser)) {
|
||||
return false;
|
||||
}
|
||||
var q = _active[parser]!;
|
||||
if (q.isEmpty) return false;
|
||||
//return q.contains(position);
|
||||
return q.first == position;
|
||||
}
|
||||
|
||||
void enter(Parser parser, int position) {
|
||||
_active.putIfAbsent(parser, () => Queue()).addFirst(position);
|
||||
}
|
||||
|
||||
void exit(Parser parser) {
|
||||
if (_active.containsKey(parser)) _active[parser]?.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
/// The result generated by a [Parser].
|
||||
class ParseResult<T> {
|
||||
final Parser<T> parser;
|
||||
final bool successful;
|
||||
final Iterable<SyntaxError> errors;
|
||||
final FileSpan? span;
|
||||
final T? value;
|
||||
final SpanScanner scanner;
|
||||
final Trampoline trampoline;
|
||||
|
||||
ParseResult(
|
||||
this.trampoline, this.scanner, this.parser, this.successful, this.errors,
|
||||
{this.span, this.value});
|
||||
|
||||
ParseResult<T> change(
|
||||
{Parser<T>? parser,
|
||||
bool? successful,
|
||||
Iterable<SyntaxError> errors = const [],
|
||||
FileSpan? span,
|
||||
T? value}) {
|
||||
return ParseResult<T>(
|
||||
trampoline,
|
||||
scanner,
|
||||
parser ?? this.parser,
|
||||
successful ?? this.successful,
|
||||
errors.isNotEmpty ? errors : this.errors,
|
||||
span: span ?? this.span,
|
||||
value: value ?? this.value,
|
||||
);
|
||||
}
|
||||
|
||||
ParseResult<T> addErrors(Iterable<SyntaxError> errors) {
|
||||
return change(
|
||||
errors: List<SyntaxError>.from(this.errors)..addAll(errors),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Compare<T> extends ListParser<T> {
|
||||
final ListParser<T> parser;
|
||||
final Comparator<T> compare;
|
||||
|
||||
_Compare(this.parser, this.compare);
|
||||
|
||||
@override
|
||||
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
if (!result.successful) return result;
|
||||
|
||||
result = result.change(
|
||||
value: result.value?.isNotEmpty == true ? result.value : []);
|
||||
result = result.change(value: List<T>.from(result.value!));
|
||||
return ParseResult<List<T>>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
true,
|
||||
[],
|
||||
span: result.span,
|
||||
value: result.value?..sort(compare),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('sort($compare) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _FoldErrors<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final bool Function(SyntaxError, SyntaxError) equal;
|
||||
|
||||
_FoldErrors(this.parser, this.equal);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||
var errors = result.errors.fold<List<SyntaxError>>([], (out, e) {
|
||||
if (!out.any((b) => equal(e, b))) out.add(e);
|
||||
return out;
|
||||
});
|
||||
return result.change(errors: errors);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('fold errors (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Index<T> extends Parser<T> {
|
||||
final ListParser<T> parser;
|
||||
final int index;
|
||||
|
||||
_Index(this.parser, this.index);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
Object? value;
|
||||
|
||||
if (result.successful) {
|
||||
var vList = result.value;
|
||||
if (vList == null) {
|
||||
throw ArgumentError('ParseResult is null');
|
||||
}
|
||||
if (index == -1) {
|
||||
value = vList.last;
|
||||
} else {
|
||||
if (index < vList.length) {
|
||||
// print(">>>>Index: $index, Size: ${vList.length}");
|
||||
// value =
|
||||
// index == -1 ? result.value!.last : result.value!.elementAt(index);
|
||||
value = result.value!.elementAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ParseResult<T>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
result.errors,
|
||||
span: result.span,
|
||||
value: value as T?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('index($index) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/// Matches any one of the given [parsers].
|
||||
///
|
||||
/// You can provide a custom [errorMessage].
|
||||
Parser<T> longest<T>(Iterable<Parser<T>> parsers,
|
||||
{Object? errorMessage, SyntaxErrorSeverity? severity}) {
|
||||
return _Longest(parsers, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||
}
|
||||
|
||||
class _Longest<T> extends Parser<T> {
|
||||
final Iterable<Parser<T>> parsers;
|
||||
final Object? errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Longest(this.parsers, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> _parse(ParseArgs args) {
|
||||
var inactive = parsers
|
||||
.toList()
|
||||
.where((p) => !args.trampoline.isActive(p, args.scanner.position));
|
||||
|
||||
if (inactive.isEmpty) {
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||
}
|
||||
|
||||
var replay = args.scanner.position;
|
||||
var errors = <SyntaxError>[];
|
||||
var results = <ParseResult<T>>[];
|
||||
|
||||
for (var parser in inactive) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (result.successful && result.span != null) {
|
||||
results.add(result);
|
||||
} else if (parser is _Alt) errors.addAll(result.errors);
|
||||
|
||||
args.scanner.position = replay;
|
||||
}
|
||||
|
||||
if (results.isNotEmpty) {
|
||||
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
|
||||
args.scanner.scan(results.first.span!.text);
|
||||
return results.first;
|
||||
}
|
||||
|
||||
if (errorMessage != false) {
|
||||
errors.add(
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage?.toString() ??
|
||||
'No match found for ${parsers.length} alternative(s)',
|
||||
args.scanner.emptySpan,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||
}
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var replay = args.scanner.position;
|
||||
var errors = <SyntaxError>[];
|
||||
var results = <ParseResult<T>>[];
|
||||
|
||||
for (var parser in parsers) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (result.successful) {
|
||||
results.add(result);
|
||||
} else if (parser is _Alt) errors.addAll(result.errors);
|
||||
|
||||
args.scanner.position = replay;
|
||||
}
|
||||
|
||||
if (results.isNotEmpty) {
|
||||
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
|
||||
args.scanner.scan(results.first.span!.text);
|
||||
return results.first;
|
||||
}
|
||||
|
||||
errors.add(
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage?.toString() ??
|
||||
'No match found for ${parsers.length} alternative(s)',
|
||||
args.scanner.emptySpan,
|
||||
),
|
||||
);
|
||||
|
||||
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('longest(${parsers.length}) (')
|
||||
..indent();
|
||||
var i = 1;
|
||||
|
||||
for (var parser in parsers) {
|
||||
buffer
|
||||
..writeln('#${i++}:')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer.outdent();
|
||||
}
|
||||
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Map<T, U> extends Parser<U> {
|
||||
final Parser<T> parser;
|
||||
final U Function(ParseResult<T>) f;
|
||||
|
||||
_Map(this.parser, this.f);
|
||||
|
||||
@override
|
||||
ParseResult<U> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
return ParseResult<U>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
result.errors,
|
||||
span: result.span,
|
||||
value: result.successful ? f(result) : null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('map<$U> (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
||||
|
||||
class _Change<T, U> extends Parser<U> {
|
||||
final Parser<T> parser;
|
||||
final ParseResult<U> Function(ParseResult<T>) f;
|
||||
|
||||
_Change(this.parser, this.f);
|
||||
|
||||
@override
|
||||
ParseResult<U> __parse(ParseArgs args) {
|
||||
return f(parser._parse(args.increaseDepth())).change(parser: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('change($f) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage].
|
||||
Parser<T> match<T>(Pattern pattern,
|
||||
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||
_Match<T>(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||
|
||||
class _Match<T> extends Parser<T> {
|
||||
final Pattern pattern;
|
||||
final String? errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Match(this.pattern, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var scanner = args.scanner;
|
||||
if (!scanner.scan(pattern)) {
|
||||
return ParseResult(args.trampoline, scanner, this, false, [
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage ?? 'Expected "$pattern".',
|
||||
scanner.emptySpan,
|
||||
),
|
||||
]);
|
||||
}
|
||||
return ParseResult<T>(
|
||||
args.trampoline,
|
||||
scanner,
|
||||
this,
|
||||
true,
|
||||
[],
|
||||
span: scanner.lastSpan,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer.writeln('match($pattern)');
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _MaxDepth<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final int cap;
|
||||
|
||||
_MaxDepth(this.parser, this.cap);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
if (args.depth > cap) {
|
||||
return ParseResult<T>(args.trampoline, args.scanner, this, false, []);
|
||||
}
|
||||
|
||||
return parser._parse(args.increaseDepth());
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('max depth($cap) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Negate<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final String? errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Negate(this.parser, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||
|
||||
if (!result.successful) {
|
||||
return ParseResult<T>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
true,
|
||||
[],
|
||||
span: result.span ?? args.scanner.lastSpan ?? args.scanner.emptySpan,
|
||||
value: result.value,
|
||||
);
|
||||
}
|
||||
|
||||
result = result.change(successful: false);
|
||||
|
||||
if (errorMessage != null) {
|
||||
result = result.addErrors([
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage,
|
||||
result.span,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('negate (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Opt<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final bool backtrack;
|
||||
|
||||
_Opt(this.parser, this.backtrack);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var replay = args.scanner.position;
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) args.scanner.position = replay;
|
||||
|
||||
return result.change(parser: this, successful: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('optional (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
||||
|
||||
class _ListOpt<T> extends ListParser<T> {
|
||||
final ListParser<T> parser;
|
||||
final bool backtrack;
|
||||
|
||||
_ListOpt(this.parser, this.backtrack);
|
||||
|
||||
@override
|
||||
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||
var replay = args.scanner.position;
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) args.scanner.position = replay;
|
||||
|
||||
return result.change(parser: this, successful: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('optional (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/*
|
||||
/// Handles left recursion in a grammar using the Pratt algorithm.
|
||||
class Recursion<T> {
|
||||
Iterable<Parser<T>> prefix;
|
||||
Map<Parser, T Function(T, T, ParseResult<T>)> infix;
|
||||
Map<Parser, T Function(T, T, ParseResult<T>)> postfix;
|
||||
|
||||
Recursion({this.prefix, this.infix, this.postfix}) {
|
||||
prefix ??= [];
|
||||
infix ??= {};
|
||||
postfix ??= {};
|
||||
}
|
||||
|
||||
Parser<T> precedence(int p) => _Precedence(this, p);
|
||||
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('recursion (')
|
||||
..indent()
|
||||
..writeln('prefix(${prefix.length}')
|
||||
..writeln('infix(${infix.length}')
|
||||
..writeln('postfix(${postfix.length}')
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
||||
|
||||
class _Precedence<T> extends Parser<T> {
|
||||
final Recursion r;
|
||||
final int precedence;
|
||||
|
||||
_Precedence(this.r, this.precedence);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
int replay = args.scanner.position;
|
||||
var errors = <SyntaxError>[];
|
||||
var start = args.scanner.state;
|
||||
var reversedKeys = r.infix.keys.toList().reversed;
|
||||
|
||||
for (var pre in r.prefix) {
|
||||
var result = pre._parse(args.increaseDepth()), originalResult = result;
|
||||
|
||||
if (!result.successful) {
|
||||
if (pre is _Alt) errors.addAll(result.errors);
|
||||
args.scanner.position = replay;
|
||||
} else {
|
||||
var left = result.value;
|
||||
replay = args.scanner.position;
|
||||
//print('${result.span.text}:\n' + scanner.emptySpan.highlight());
|
||||
|
||||
while (true) {
|
||||
bool matched = false;
|
||||
|
||||
//for (int i = 0; i < r.infix.length; i++) {
|
||||
for (int i = r.infix.length - 1; i >= 0; i--) {
|
||||
//var fix = r.infix.keys.elementAt(r.infix.length - i - 1);
|
||||
var fix = reversedKeys.elementAt(i);
|
||||
|
||||
if (i < precedence) continue;
|
||||
|
||||
var result = fix._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) {
|
||||
if (fix is _Alt) errors.addAll(result.errors);
|
||||
// If this is the last alternative and it failed, don't continue looping.
|
||||
//if (true || i + 1 < r.infix.length)
|
||||
args.scanner.position = replay;
|
||||
} else {
|
||||
//print('FOUND $fix when left was $left');
|
||||
//print('$i vs $precedence\n${originalResult.span.highlight()}');
|
||||
result = r.precedence(i)._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) {
|
||||
} else {
|
||||
matched = false;
|
||||
var old = left;
|
||||
left = r.infix[fix](left, result.value, result);
|
||||
print(
|
||||
'$old $fix ${result.value} = $left\n${result.span.highlight()}');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) break;
|
||||
}
|
||||
|
||||
replay = args.scanner.position;
|
||||
//print('f ${result.span.text}');
|
||||
|
||||
for (var post in r.postfix.keys) {
|
||||
var result = pre._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) {
|
||||
if (post is _Alt) errors.addAll(result.errors);
|
||||
args.scanner.position = replay;
|
||||
} else {
|
||||
left = r.infix[post](left, originalResult.value, result);
|
||||
}
|
||||
}
|
||||
|
||||
if (!args.scanner.isDone) {
|
||||
// If we're not done scanning, then we need some sort of guard to ensure the
|
||||
// that this exact parser does not run again in the exact position.
|
||||
}
|
||||
return ParseResult(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
true,
|
||||
errors,
|
||||
value: left,
|
||||
span: args.scanner.spanFrom(start),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return ParseResult(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
false,
|
||||
errors,
|
||||
span: args.scanner.spanFrom(start),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('precedence($precedence) (')
|
||||
..indent();
|
||||
r.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,46 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Reduce<T> extends Parser<T> {
|
||||
final ListParser<T> parser;
|
||||
final T Function(T, T) combine;
|
||||
|
||||
_Reduce(this.parser, this.combine);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (!result.successful) {
|
||||
return ParseResult<T>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
false,
|
||||
result.errors,
|
||||
);
|
||||
}
|
||||
|
||||
result = result.change(
|
||||
value: result.value?.isNotEmpty == true ? result.value : []);
|
||||
return ParseResult<T>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
[],
|
||||
span: result.span,
|
||||
value: result.value!.isEmpty ? null : result.value!.reduce(combine),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('reduce($combine) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
Reference<T> reference<T>() => Reference<T>._();
|
||||
|
||||
class Reference<T> extends Parser<T> {
|
||||
Parser<T>? _parser;
|
||||
bool printed = false;
|
||||
|
||||
Reference._();
|
||||
|
||||
set parser(Parser<T> value) {
|
||||
if (_parser != null) {
|
||||
throw StateError('There is already a parser assigned to this reference.');
|
||||
}
|
||||
_parser = value;
|
||||
}
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
if (_parser == null) {
|
||||
throw StateError('There is no parser assigned to this reference.');
|
||||
}
|
||||
return _parser!._parse(args);
|
||||
}
|
||||
|
||||
@override
|
||||
ParseResult<T> _parse(ParseArgs args) {
|
||||
if (_parser == null) {
|
||||
throw StateError('There is no parser assigned to this reference.');
|
||||
}
|
||||
return _parser!._parse(args);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
if (_parser == null) {
|
||||
buffer.writeln('(undefined reference <$T>)');
|
||||
} else if (!printed) {
|
||||
_parser!.stringify(buffer);
|
||||
}
|
||||
printed = true;
|
||||
buffer.writeln('(previously printed reference)');
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Repeat<T> extends ListParser<T> {
|
||||
final Parser<T> parser;
|
||||
final int count;
|
||||
final bool exact, backtrack;
|
||||
final String tooFew;
|
||||
final String tooMany;
|
||||
final SyntaxErrorSeverity severity;
|
||||
|
||||
_Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany,
|
||||
this.backtrack, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||
var errors = <SyntaxError>[];
|
||||
var results = <T>[];
|
||||
var spans = <FileSpan>[];
|
||||
var success = 0;
|
||||
var replay = args.scanner.position;
|
||||
ParseResult<T> result;
|
||||
|
||||
do {
|
||||
result = parser._parse(args.increaseDepth());
|
||||
if (result.successful) {
|
||||
success++;
|
||||
if (result.value != null) {
|
||||
results.add(result.value!);
|
||||
}
|
||||
replay = args.scanner.position;
|
||||
} else if (backtrack) args.scanner.position = replay;
|
||||
|
||||
if (result.span != null) {
|
||||
spans.add(result.span!);
|
||||
}
|
||||
} while (result.successful);
|
||||
|
||||
if (success < count) {
|
||||
errors.addAll(result.errors);
|
||||
errors.add(
|
||||
SyntaxError(
|
||||
severity,
|
||||
tooFew,
|
||||
result.span ?? args.scanner.emptySpan,
|
||||
),
|
||||
);
|
||||
|
||||
if (backtrack) args.scanner.position = replay;
|
||||
|
||||
return ParseResult<List<T>>(
|
||||
args.trampoline, args.scanner, this, false, errors);
|
||||
} else if (success > count && exact) {
|
||||
if (backtrack) args.scanner.position = replay;
|
||||
|
||||
return ParseResult<List<T>>(args.trampoline, args.scanner, this, false, [
|
||||
SyntaxError(
|
||||
severity,
|
||||
tooMany,
|
||||
result.span ?? args.scanner.emptySpan,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
var span = spans.reduce((a, b) => a.expand(b));
|
||||
return ParseResult<List<T>>(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
true,
|
||||
[],
|
||||
span: span,
|
||||
value: results,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
var r = StringBuffer('{$count');
|
||||
if (!exact) r.write(',');
|
||||
r.write('}');
|
||||
buffer
|
||||
..writeln('repeat($r) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Safe<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final bool backtrack;
|
||||
final String errorMessage;
|
||||
final SyntaxErrorSeverity severity;
|
||||
bool _triggered = false;
|
||||
|
||||
_Safe(this.parser, this.backtrack, this.errorMessage, this.severity);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var replay = args.scanner.position;
|
||||
|
||||
try {
|
||||
if (_triggered) throw Exception();
|
||||
return parser._parse(args.increaseDepth());
|
||||
} catch (_) {
|
||||
_triggered = true;
|
||||
if (backtrack) args.scanner.position = replay;
|
||||
var errors = <SyntaxError>[];
|
||||
|
||||
errors.add(
|
||||
SyntaxError(
|
||||
severity,
|
||||
errorMessage,
|
||||
args.scanner.lastSpan ?? args.scanner.emptySpan,
|
||||
),
|
||||
);
|
||||
|
||||
return ParseResult<T>(args.trampoline, args.scanner, this, false, errors);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
var t = _triggered ? 'triggered' : 'not triggered';
|
||||
buffer
|
||||
..writeln('safe($t) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _ToList<T> extends ListParser<T> {
|
||||
final Parser<T> parser;
|
||||
|
||||
_ToList(this.parser);
|
||||
|
||||
@override
|
||||
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth());
|
||||
|
||||
if (result.value is List) {
|
||||
return (result as ParseResult<List<T>>).change(parser: this);
|
||||
}
|
||||
|
||||
var values = <T>[];
|
||||
if (result.value != null) {
|
||||
values.add(result.value!);
|
||||
}
|
||||
return ParseResult(
|
||||
args.trampoline,
|
||||
args.scanner,
|
||||
this,
|
||||
result.successful,
|
||||
result.errors,
|
||||
span: result.span,
|
||||
value: values,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('to list (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
/// A typed parser that parses a sequence of 2 values of different types.
|
||||
Parser<Tuple2<A, B>> tuple2<A, B>(Parser<A> a, Parser<B> b) {
|
||||
return chain([a, b]).map((r) {
|
||||
return Tuple2(r.value?[0] as A, r.value?[1] as B);
|
||||
});
|
||||
}
|
||||
|
||||
/// A typed parser that parses a sequence of 3 values of different types.
|
||||
Parser<Tuple3<A, B, C>> tuple3<A, B, C>(Parser<A> a, Parser<B> b, Parser<C> c) {
|
||||
return chain([a, b, c]).map((r) {
|
||||
return Tuple3(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C);
|
||||
});
|
||||
}
|
||||
|
||||
/// A typed parser that parses a sequence of 4 values of different types.
|
||||
Parser<Tuple4<A, B, C, D>> tuple4<A, B, C, D>(
|
||||
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d) {
|
||||
return chain([a, b, c, d]).map((r) {
|
||||
return Tuple4(
|
||||
r.value?[0] as A, r.value?[1] as B, r.value?[2] as C, r.value?[3] as D);
|
||||
});
|
||||
}
|
||||
|
||||
/// A typed parser that parses a sequence of 5 values of different types.
|
||||
Parser<Tuple5<A, B, C, D, E>> tuple5<A, B, C, D, E>(
|
||||
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e) {
|
||||
return chain([a, b, c, d, e]).map((r) {
|
||||
return Tuple5(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||
r.value?[3] as D, r.value?[4] as E);
|
||||
});
|
||||
}
|
||||
|
||||
/// A typed parser that parses a sequence of 6 values of different types.
|
||||
Parser<Tuple6<A, B, C, D, E, F>> tuple6<A, B, C, D, E, F>(Parser<A> a,
|
||||
Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e, Parser<F> f) {
|
||||
return chain([a, b, c, d, e, f]).map((r) {
|
||||
return Tuple6(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||
r.value?[3] as D, r.value?[4] as E, r.value?[5] as F);
|
||||
});
|
||||
}
|
||||
|
||||
/// A typed parser that parses a sequence of 7 values of different types.
|
||||
Parser<Tuple7<A, B, C, D, E, F, G>> tuple7<A, B, C, D, E, F, G>(
|
||||
Parser<A> a,
|
||||
Parser<B> b,
|
||||
Parser<C> c,
|
||||
Parser<D> d,
|
||||
Parser<E> e,
|
||||
Parser<F> f,
|
||||
Parser<G> g) {
|
||||
return chain([a, b, c, d, e, f, g]).map((r) {
|
||||
return Tuple7(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||
r.value?[3] as D, r.value?[4] as E, r.value?[5] as F, r.value?[6] as G);
|
||||
});
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
part of lex.src.combinator;
|
||||
|
||||
class _Value<T> extends Parser<T> {
|
||||
final Parser<T> parser;
|
||||
final T Function(ParseResult<T>) f;
|
||||
|
||||
_Value(this.parser, this.f);
|
||||
|
||||
@override
|
||||
ParseResult<T> __parse(ParseArgs args) {
|
||||
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||
return result.successful ? result.change(value: f(result)) : result;
|
||||
}
|
||||
|
||||
@override
|
||||
void stringify(CodeBuffer buffer) {
|
||||
buffer
|
||||
..writeln('set value($f) (')
|
||||
..indent();
|
||||
parser.stringify(buffer);
|
||||
buffer
|
||||
..outdent()
|
||||
..writeln(')');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
|
||||
class SyntaxError implements Exception {
|
||||
final SyntaxErrorSeverity severity;
|
||||
final String? message;
|
||||
final FileSpan? span;
|
||||
String? _toolString;
|
||||
|
||||
SyntaxError(this.severity, this.message, this.span);
|
||||
|
||||
String? get toolString {
|
||||
if (_toolString != null) return _toolString;
|
||||
var type = severity == SyntaxErrorSeverity.warning ? 'warning' : 'error';
|
||||
return _toolString = '$type: ${span!.start.toolString}: $message';
|
||||
}
|
||||
}
|
||||
|
||||
enum SyntaxErrorSeverity {
|
||||
warning,
|
||||
error,
|
||||
info,
|
||||
hint,
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
name: angel3_combinator
|
||||
version: 2.0.2
|
||||
description: Packrat parser combinators that support static typing, generics, file spans, memoization, and more.
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/combinator
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel3_code_buffer: ^2.0.0
|
||||
matcher: ^0.12.10
|
||||
source_span: ^1.8.1
|
||||
string_scanner: ^1.1.0
|
||||
tuple: ^2.0.0
|
||||
dev_dependencies:
|
||||
test: ^1.17.4
|
||||
pedantic: ^1.11.0
|
|
@ -1,12 +0,0 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'list_test.dart' as list;
|
||||
import 'match_test.dart' as match;
|
||||
import 'misc_test.dart' as misc;
|
||||
import 'value_test.dart' as value;
|
||||
|
||||
void main() {
|
||||
group('list', list.main);
|
||||
group('match', match.main);
|
||||
group('value', value.main);
|
||||
misc.main();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
SpanScanner scan(String text) => SpanScanner(text);
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
var number = chain([
|
||||
match(RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)),
|
||||
match(',').opt(),
|
||||
]).first().cast<int>();
|
||||
|
||||
var numbers = number.plus();
|
||||
|
||||
test('sort', () {
|
||||
var parser = numbers.sort((a, b) => a.compareTo(b));
|
||||
expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]);
|
||||
});
|
||||
test('reduce', () {
|
||||
var parser = numbers.reduce((a, b) => a + b);
|
||||
expect(parser.parse(scan('21,2,3,34,20')).value, 80);
|
||||
expect(parser.parse(scan('not numbers')).value, isNull);
|
||||
});
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
test('match string', () {
|
||||
expect(match('hello').parse(scan('hello world')).successful, isTrue);
|
||||
});
|
||||
test('match start only', () {
|
||||
expect(match('hello').parse(scan('goodbye hello')).successful, isFalse);
|
||||
});
|
||||
|
||||
test('fail if no match', () {
|
||||
expect(match('hello').parse(scan('world')).successful, isFalse);
|
||||
});
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:matcher/matcher.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
test('advance', () {
|
||||
var scanner = scan('hello world');
|
||||
|
||||
// Casted -> dynamic just for the sake of coverage.
|
||||
var parser = match('he').forward(2).castDynamic();
|
||||
parser.parse(scanner);
|
||||
expect(scanner.position, 4);
|
||||
});
|
||||
|
||||
test('change', () {
|
||||
var parser = match('hello').change((r) => r.change(value: 23));
|
||||
expect(parser.parse(scan('helloworld')).value, 23);
|
||||
});
|
||||
|
||||
test('check', () {
|
||||
var parser = match<int>(RegExp(r'[A-Za-z]+'))
|
||||
.value((r) => r.span!.length)
|
||||
.check(greaterThan(3));
|
||||
expect(parser.parse(scan('helloworld')).successful, isTrue);
|
||||
expect(parser.parse(scan('yo')).successful, isFalse);
|
||||
});
|
||||
|
||||
test('map', () {
|
||||
var parser = match(RegExp(r'[A-Za-z]+')).map<int>((r) => r.span!.length);
|
||||
expect(parser.parse(scan('hello')).value, 5);
|
||||
});
|
||||
|
||||
test('negate', () {
|
||||
var parser = match('hello').negate(errorMessage: 'world');
|
||||
expect(parser.parse(scan('goodbye world')).successful, isTrue);
|
||||
expect(parser.parse(scan('hello world')).successful, isFalse);
|
||||
expect(parser.parse(scan('hello world')).errors.first.message, 'world');
|
||||
});
|
||||
|
||||
group('opt', () {
|
||||
var single = match('hello').opt(backtrack: true);
|
||||
var list = match('hel').then(match('lo')).opt();
|
||||
|
||||
test('succeeds if present', () {
|
||||
expect(single.parse(scan('hello')).successful, isTrue);
|
||||
expect(list.parse(scan('hello')).successful, isTrue);
|
||||
});
|
||||
|
||||
test('succeeds if not present', () {
|
||||
expect(single.parse(scan('goodbye')).successful, isTrue);
|
||||
expect(list.parse(scan('goodbye')).successful, isTrue);
|
||||
});
|
||||
|
||||
test('backtracks if not present', () {
|
||||
for (var parser in [single, list]) {
|
||||
var scanner = scan('goodbye');
|
||||
var pos = scanner.position;
|
||||
parser.parse(scanner);
|
||||
expect(scanner.position, pos);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('safe', () {});
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
void main() {}
|
||||
|
||||
/*
|
||||
void main() {
|
||||
var number = match( RegExp(r'-?[0-9]+(\.[0-9]+)?'))
|
||||
.map<num>((r) => num.parse(r.span.text));
|
||||
|
||||
var term = reference<num>();
|
||||
|
||||
var r = Recursion<num>();
|
||||
|
||||
r.prefix = [number];
|
||||
|
||||
r.infix.addAll({
|
||||
match('*'): (l, r, _) => l * r,
|
||||
match('/'): (l, r, _) => l / r,
|
||||
match('+'): (l, r, _) => l + r,
|
||||
match('-'): (l, r, _) => l - r,
|
||||
|
||||
|
||||
match('-'): (l, r, _) => l - r,
|
||||
match('+'): (l, r, _) => l + r,
|
||||
match('/'): (l, r, _) => l / r,
|
||||
match('*'): (l, r, _) => l * r,
|
||||
|
||||
});
|
||||
|
||||
term.parser = r.precedence(0);
|
||||
|
||||
num parse(String text) {
|
||||
var scanner = SpanScanner(text);
|
||||
var result = term.parse(scanner);
|
||||
print(result.span.highlight());
|
||||
return result.value;
|
||||
}
|
||||
|
||||
test('prefix', () {
|
||||
expect(parse('24'), 24);
|
||||
});
|
||||
|
||||
test('infix', () {
|
||||
expect(parse('12/6'), 2);
|
||||
expect(parse('24+23'), 47);
|
||||
expect(parse('24-23'), 1);
|
||||
expect(parse('4*3'), 12);
|
||||
});
|
||||
|
||||
test('precedence', () {
|
||||
expect(parse('2+3*5*2'), 15);
|
||||
//expect(parse('2+3+5-2*2'), 15);
|
||||
});
|
||||
}
|
||||
*/
|
|
@ -1,15 +0,0 @@
|
|||
import 'package:angel3_combinator/angel3_combinator.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'common.dart';
|
||||
|
||||
void main() {
|
||||
var parser = match('hello').value((r) => 'world');
|
||||
|
||||
test('sets value', () {
|
||||
expect(parser.parse(scan('hello world')).value, 'world');
|
||||
});
|
||||
|
||||
test('no value if no match', () {
|
||||
expect(parser.parse(scan('goodbye world')).value, isNull);
|
||||
});
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
name: angel_container_generator
|
||||
version: 2.0.0
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
description: Codegen support for using pkg:reflectable with pkg:angel_container.
|
||||
homepage: https://github.com/angel-dart/container.git
|
||||
publish_to: none
|
||||
|
|
|
@ -2,7 +2,6 @@ name: angel_eventsource
|
|||
version: 2.0.0
|
||||
description: Server-sent Events (SSE) plugin for Angel.
|
||||
homepage: https://github.com/angel-dart/eventsource
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
publish_to: none
|
||||
environment:
|
||||
sdk: ">=2.10.0 <3.0.0"
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Change Log
|
||||
|
||||
## 4.1.3
|
||||
|
||||
* Updated README
|
||||
|
||||
## 4.1.2
|
||||
|
||||
* Updated README
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
# Angel3 Framework
|
||||
|
||||
[![version](https://img.shields.io/badge/pub-v4.1.2-brightgreen)](https://pub.dev/packages/angel3_framework)
|
||||
[![version](https://img.shields.io/badge/pub-v4.1.3-brightgreen)](https://pub.dev/packages/angel3_framework)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/framework/LICENSE)
|
||||
|
||||
**Replacement of Angel Framework with major change to support NNBD**
|
||||
**Replacement of Angel Framework with major change to support NNBD.**
|
||||
|
||||
Angel3 framework is a high-powered HTTP server with support for dependency injection, sophisticated routing, authentication, ORM, graphql etc. It is designed to keep the core minimal but extensible through a series of plugin packages. It won't dictate which features, databases or web templating engine to use. This flexibility enable Angel3 framework to grow with your application as new features can be added to handle the new use cases.
|
||||
|
||||
This package is the core of the [Angel3](https://github.com/dukefirehawk/angel/tree/angel3) framework. To see more details, please refer to the [Developer Guide](https://angel3-docs.dukefirehawk.com).
|
||||
|
||||
## Usage
|
||||
## Installation and Setup
|
||||
|
||||
### Create a new project by cloning from boilerplate templates
|
||||
### (Option 1) Create a new project by cloning from boilerplate templates
|
||||
|
||||
1. Download and install [Dart](https://dart.dev/get-dart)
|
||||
|
||||
|
@ -36,7 +37,7 @@ This package is the core of the [Angel3](https://github.com/dukefirehawk/angel/t
|
|||
|
||||
5. Run as docker. Edit and build the image with the provided `Dockerfile` file.
|
||||
|
||||
### Create a new project with Angel3 CLI
|
||||
### (Option 2) Create a new project with Angel3 CLI
|
||||
|
||||
1. Download and install [Dart](https://dart.dev/get-dart)
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:angel3_container/mirrors.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:angel3_container/mirrors.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
|
|
|
@ -243,7 +243,9 @@ abstract class ResponseContext<RawResponse>
|
|||
final m = _findRoute(route.router);
|
||||
|
||||
if (m != null) return m;
|
||||
} else if (route.name == name) return route;
|
||||
} else if (route.name == name) {
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: angel3_framework
|
||||
version: 4.1.2
|
||||
version: 4.1.3
|
||||
description: A high-powered HTTP server extensible framework with dependency injection, routing and much more.
|
||||
homepage: https://angel3-framework.web.app/
|
||||
repository: https://github.com/dukefirehawk/angel
|
||||
|
|
|
@ -31,6 +31,8 @@ AnsiCode chooseLogColor(Level level) {
|
|||
} else if (level == Level.CONFIG ||
|
||||
level == Level.FINE ||
|
||||
level == Level.FINER ||
|
||||
level == Level.FINEST) return lightGray;
|
||||
level == Level.FINEST) {
|
||||
return lightGray;
|
||||
}
|
||||
return resetAll;
|
||||
}
|
||||
|
|
|
@ -220,5 +220,5 @@ class CustomCloseService extends Service {
|
|||
@Expose('/foo')
|
||||
class FooController extends Controller {
|
||||
@Expose('/bar')
|
||||
bar() async => 'baz';
|
||||
Future<String> bar() async => 'baz';
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# Change Log
|
||||
|
||||
## 4.2.1
|
||||
|
||||
* Fixed license link
|
||||
|
||||
## 4.2.0
|
||||
|
||||
* Updated to use `belatuk_html_builder` package
|
||||
* Upgraded from `pendantic` to `lints` linter
|
||||
|
||||
## 4.1.1
|
||||
|
||||
* Fixed NNBD issues
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
MIT License (MIT)
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
# Angel3 Hot Reloader
|
||||
|
||||
[![version](https://img.shields.io/badge/pub-v4.1.1-brightgreen)](https://pub.dartlang.org/packages/angel3_hot)
|
||||
[![version](https://img.shields.io/badge/pub-v4.2.1-brightgreen)](https://pub.dev/packages/angel3_hot)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/hot/LICENSE)
|
||||
[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dukefirehawk/angel/tree/angel3/packages/hot/LICENSE)
|
||||
|
||||
![Screenshot of terminal](screenshots/angel3-screenshot.png)
|
||||
|
||||
Supports *hot reloading* of Angel3 servers on file changes. This is faster and more reliable than merely reactively restarting a `Process`.
|
||||
This package only works with the [Angel3 framework](https://github.com/dukefirehawk/angel).
|
||||
This package only works with the [Angel3 framework](https://pub.dev/packages/angel3_framework).
|
||||
|
||||
**Not recommended to use in production, unless you are specifically intending for a "hot code push" in production..**
|
||||
|
||||
|
@ -19,8 +18,8 @@ In your `pubspec.yaml`:
|
|||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel3_framework: ^4.0.0
|
||||
angel3_hot: ^4.0.0
|
||||
angel3_framework: ^4.1.0
|
||||
angel3_hot: ^4.2.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
linter:
|
||||
rules:
|
||||
- unnecessary_const
|
||||
- unnecessary_new
|
||||
include: package:lints/recommended.yaml
|
|
@ -10,8 +10,8 @@ import 'package:angel3_websocket/server.dart';
|
|||
import 'package:charcode/ascii.dart';
|
||||
import 'package:glob/glob.dart';
|
||||
import 'package:glob/list_local_fs.dart';
|
||||
import 'package:angel3_html_builder/elements.dart';
|
||||
import 'package:angel3_html_builder/angel3_html_builder.dart';
|
||||
import 'package:belatuk_html_builder/elements.dart';
|
||||
import 'package:belatuk_html_builder/belatuk_html_builder.dart';
|
||||
import 'package:io/ansi.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:vm_service/vm_service.dart' as vm;
|
||||
|
@ -211,7 +211,7 @@ class HotReloader {
|
|||
var serverUri =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
|
||||
var observatoryUri;
|
||||
Uri? observatoryUri;
|
||||
if (isHot) {
|
||||
observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri!);
|
||||
}
|
||||
|
@ -224,6 +224,7 @@ class HotReloader {
|
|||
if (isHot) {
|
||||
stdout.write(
|
||||
'An Observatory debugger and profiler on ${Platform.operatingSystem} is available at: ');
|
||||
|
||||
print(wrapWith('$observatoryUri', [cyan, styleUnderlined]));
|
||||
} else {
|
||||
stdout.write(
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
name: angel3_hot
|
||||
description: Supports hot reloading/hot code push of Angel3 servers on file changes.
|
||||
version: 4.1.1
|
||||
version: 4.2.1
|
||||
homepage: https://angel3-framework.web.app/
|
||||
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/hot
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel3_framework: ^4.0.0
|
||||
angel3_framework: ^4.1.0
|
||||
angel3_websocket: ^4.0.0
|
||||
angel3_html_builder: ^2.0.0
|
||||
belatuk_html_builder: ^3.0.0
|
||||
charcode: ^1.2.0
|
||||
glob: ^2.0.1
|
||||
io: ^1.0.0
|
||||
|
@ -18,4 +18,4 @@ dependencies:
|
|||
dev_dependencies:
|
||||
http: ^0.13.2
|
||||
logging: ^1.0.1
|
||||
pedantic: ^1.11.0
|
||||
lints: ^1.0.0
|
||||
|
|
58
packages/html/.gitignore
vendored
58
packages/html/.gitignore
vendored
|
@ -1,58 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
.dart_tool
|
|
@ -1,5 +1,9 @@
|
|||
# 3.0.0
|
||||
# Change Log
|
||||
|
||||
## 3.0.0
|
||||
|
||||
* Migrated to support Dart SDK 2.12.x NNBD
|
||||
|
||||
# 2.0.0
|
||||
## 2.0.0
|
||||
|
||||
* Angel 2 + Dart 2 updates.
|
|
@ -1,21 +1,29 @@
|
|||
MIT License
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017 Tobe O
|
||||
Copyright (c) 2021, dukefirehawk.com
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
# html
|
||||
[![Pub](https://img.shields.io/pub/v/angel_html.svg)](https://pub.dartlang.org/packages/angel_html)
|
||||
[![build status](https://travis-ci.org/angel-dart/html.svg)](https://travis-ci.org/angel-dart/html)
|
||||
# Angel3 HTML
|
||||
|
||||
A plug-in that allows you to return html_builder AST's from request handlers, and have them sent as HTML automatically.
|
||||
[![version](https://img.shields.io/badge/pub-v3.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_html)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[`package:html_builder`](https://github.com/thosakwe/html_builder) is a simple virtual DOM library
|
||||
(without diffing, you can find that
|
||||
[here](https://github.com/thosakwe/html_builder_vdom)), with a handy Dart DSL that makes it easy to build HTML
|
||||
AST's:
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/html/LICENSE)
|
||||
|
||||
A plug-in that allows you to return `belatuk_html_builder` AST's from request handlers, and have them sent as HTML automatically.
|
||||
|
||||
[`package:belatuk_html_builder`](https://pub.dev/packages/belatuk_html_builder) is a simple virtual DOM library with a handy Dart DSL that makes it easy to build HTML AST's:
|
||||
|
||||
```dart
|
||||
import 'package:html_builder/elements.dart';
|
||||
import 'package:belatuk_html_builder/elements.dart';
|
||||
|
||||
Node myDom = html(lang: 'en', c: [
|
||||
head(c: [
|
||||
|
@ -28,19 +29,21 @@ Node myDom = html(lang: 'en', c: [
|
|||
```
|
||||
|
||||
This plug-in means that you can now `return` these AST's, and Angel will automatically send them to
|
||||
clients. Ultimately, the implication is that you can use `html_builder` as a substitute for a
|
||||
templating system within Dart. With [hot reloading](https://github.com/angel-dart/hot), you won't
|
||||
clients. Ultimately, the implication is that you can use `belatuk_html_builder` as a substitute for a
|
||||
templating system within Dart. With [hot reloading](https://pub.dev/packages/angel3_hot), you won't
|
||||
even need to reload your server (as it should be).
|
||||
|
||||
# Installation
|
||||
## Installation
|
||||
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_html: ^1.0.0
|
||||
angel3_html: ^3.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
## Usage
|
||||
|
||||
The `renderHtml` function does all the magic for you.
|
||||
|
||||
```dart
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
||||
include: package:lints/recommended.yaml
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_html/angel_html.dart';
|
||||
import 'package:html_builder/elements.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_framework/http.dart';
|
||||
import 'package:angel3_html/angel3_html.dart';
|
||||
import 'package:belatuk_html_builder/elements.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
void main() async {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:html_builder/html_builder.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:belatuk_html_builder/belatuk_html_builder.dart';
|
||||
|
||||
/// Returns a [RequestMiddleware] that allows you to return `html_builder` [Node]s as responses.
|
||||
///
|
||||
/// You can provide a custom [renderer]. The default renders minified HTML5 pages.
|
||||
///
|
||||
/// Set [enforceAcceptHeader] to `true` to throw a `406 Not Acceptable` if the client doesn't accept HTML responses.
|
||||
RequestHandler renderHtml({StringRenderer? renderer, bool? enforceAcceptHeader}) {
|
||||
RequestHandler renderHtml(
|
||||
{StringRenderer? renderer, bool? enforceAcceptHeader}) {
|
||||
renderer ??= StringRenderer(pretty: false, html5: true);
|
||||
|
||||
return (RequestContext req, ResponseContext res) {
|
||||
|
@ -15,7 +16,7 @@ RequestHandler renderHtml({StringRenderer? renderer, bool? enforceAcceptHeader})
|
|||
|
||||
res.serializer = (data) {
|
||||
if (data is! Node) {
|
||||
return oldSerializer!(data);
|
||||
return oldSerializer(data);
|
||||
} else {
|
||||
if (enforceAcceptHeader == true && !req.accepts('text/html')) {
|
||||
throw AngelHttpException.notAcceptable();
|
|
@ -1,28 +1,16 @@
|
|||
name: angel_html
|
||||
name: angel3_html
|
||||
version: 3.0.0
|
||||
description: Support for rendering html_builder AST's as responses in Angel.
|
||||
homepage: https://github.com/angel-dart/html_builder
|
||||
publish_to: none
|
||||
homepage: https://angel3-framework.web.app/
|
||||
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/html_builder
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
angel_framework:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/framework
|
||||
html_builder:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/html_builder
|
||||
angel3_framework: ^4.0.0
|
||||
belatuk_html_builder: ^3.0.0
|
||||
dev_dependencies:
|
||||
angel_test:
|
||||
git:
|
||||
url: https://github.com/dukefirehawk/angel.git
|
||||
ref: sdk-2.12.x_nnbd
|
||||
path: packages/test
|
||||
angel3_test: ^4.0.0
|
||||
html: ^0.15.0
|
||||
logging: ^1.0.1
|
||||
test: ^1.17.0
|
||||
pedantic: ^1.11.0
|
||||
lints: ^1.0.0
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_html/angel_html.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:html_builder/elements.dart';
|
||||
import 'package:html_builder/html_builder.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_html/angel3_html.dart';
|
||||
import 'package:angel3_test/angel3_test.dart';
|
||||
import 'package:belatuk_html_builder/elements.dart';
|
||||
import 'package:belatuk_html_builder/belatuk_html_builder.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
|
|
71
packages/html_builder/.gitignore
vendored
71
packages/html_builder/.gitignore
vendored
|
@ -1,71 +0,0 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.dart_tool
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
# Directory created by dartdoc
|
||||
# If you don't generate documentation locally you can remove this line.
|
||||
doc/api/
|
||||
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
.project
|
||||
.buildlog
|
||||
**/packages/
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
|
||||
## VsCode
|
||||
.vscode/
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
/out/
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
|
@ -1,12 +0,0 @@
|
|||
Primary Authors
|
||||
===============
|
||||
|
||||
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||
|
||||
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||
code base to support NNBD.
|
||||
|
||||
* __[Tobe O](thosakwe@gmail.com)__
|
||||
|
||||
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||
is no longer involved with the project.
|
|
@ -1,31 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 2.0.3
|
||||
|
||||
* Added example
|
||||
* Updated README
|
||||
|
||||
## 2.0.2
|
||||
|
||||
* Run `dartfmt -w .`
|
||||
|
||||
## 2.0.1
|
||||
|
||||
* Added pedantic dart rules
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Migrated to work with Dart SDK 2.12.x NNBD
|
||||
|
||||
## 1.0.4
|
||||
|
||||
* Added `rebuild`, `rebuildRecursive`, and `NodeBuilder`.
|
||||
|
||||
## 1.0.3
|
||||
|
||||
* Dart 2 ready!
|
||||
|
||||
## 1.0.2
|
||||
|
||||
Changed `h` and the `Node` constructor to take `Iterable`s of children,
|
||||
instead of just `List`s.
|
|
@ -1,21 +0,0 @@
|
|||
MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 dukefirehawk.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,113 +1,3 @@
|
|||
# Angel3 Html Builder
|
||||
|
||||
[![version](https://img.shields.io/badge/pub-v2.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_html_builder)
|
||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/html_builder/LICENSE)
|
||||
|
||||
Build HTML AST's and render them to HTML.
|
||||
|
||||
This can be used as an internal DSL, i.e. for a templating engine.
|
||||
|
||||
## Installation
|
||||
|
||||
In your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel3_html_builder: ^2.0.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:angel3_html_builder/angel3_html_builder.dart';
|
||||
|
||||
void main() {
|
||||
// Akin to React.createElement(...);
|
||||
var $el = h('my-element', p: {}, c: []);
|
||||
|
||||
// Attributes can be plain Strings.
|
||||
h('foo', p: {
|
||||
'bar': 'baz'
|
||||
});
|
||||
|
||||
// Null attributes do not appear.
|
||||
h('foo', p: {
|
||||
'does-not-appear': null
|
||||
});
|
||||
|
||||
// If an attribute is a bool, then it will only appear if its value is true.
|
||||
h('foo', p: {
|
||||
'appears': true,
|
||||
'does-not-appear': false
|
||||
});
|
||||
|
||||
// Or, a String or Map.
|
||||
h('foo', p: {
|
||||
'style': 'background-color: white; color: red;'
|
||||
});
|
||||
|
||||
h('foo', p: {
|
||||
'style': {
|
||||
'background-color': 'white',
|
||||
'color': 'red'
|
||||
}
|
||||
});
|
||||
|
||||
// Or, a String or Iterable.
|
||||
h('foo', p: {
|
||||
'class': 'a b'
|
||||
});
|
||||
|
||||
h('foo', p: {
|
||||
'class': ['a', 'b']
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Standard HTML5 elements:
|
||||
```dart
|
||||
import 'package:angel3_html_builder/elements.dart';
|
||||
|
||||
void main() {
|
||||
var $dom = html(lang: 'en', c: [
|
||||
head(c: [
|
||||
title(c: [text('Hello, world!')])
|
||||
]),
|
||||
body(c: [
|
||||
h1(c: [text('Hello, world!')]),
|
||||
p(c: [text('Ok')])
|
||||
])
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
Rendering to HTML:
|
||||
```dart
|
||||
String html = StringRenderer().render($dom);
|
||||
```
|
||||
|
||||
Example with the [Angel](https://github.com/dukefirehawk/angel/tree/angel3) server-side framework,
|
||||
which has [dedicated html_builder support](https://github.com/dukefirehawk/angel/tree/html):
|
||||
|
||||
```dart
|
||||
import 'dart:io';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'package:angel3_html_builder/elements.dart';
|
||||
|
||||
configureViews(Angel app) async {
|
||||
app.get('/foo/:id', (req, res) async {
|
||||
var foo = await app.service('foo').read(req.params['id']);
|
||||
return html(c: [
|
||||
head(c: [
|
||||
title(c: [text(foo.name)])
|
||||
]),
|
||||
body(c: [
|
||||
h1(c: [text(foo.name)])
|
||||
])
|
||||
]);
|
||||
});
|
||||
}
|
||||
```
|
||||
`angel3_html_builder` package has been deprecated and replaced by `belatuk_html_builder`package at [`Belatuk Common Utilities`](<https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/html_builder>)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue