blob: beaa46a9c775ef2cc58a6445ce17b6903dc04f4a [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'ast.dart';
import 'block_syntaxes/block_syntax.dart';
import 'block_syntaxes/blockquote_syntax.dart';
import 'block_syntaxes/code_block_syntax.dart';
import 'block_syntaxes/dummy_block_syntax.dart';
import 'block_syntaxes/empty_block_syntax.dart';
import 'block_syntaxes/header_syntax.dart';
import 'block_syntaxes/horizontal_rule_syntax.dart';
import 'block_syntaxes/html_block_syntax.dart';
import 'block_syntaxes/ordered_list_syntax.dart';
import 'block_syntaxes/paragraph_syntax.dart';
import 'block_syntaxes/setext_header_syntax.dart';
import 'block_syntaxes/unordered_list_syntax.dart';
import 'document.dart';
/// Maintains the internal state needed to parse a series of lines into blocks
/// of Markdown suitable for further inline parsing.
class BlockParser {
final List<String> lines;
/// The Markdown document this parser is parsing.
final Document document;
/// The enabled block syntaxes.
///
/// To turn a series of lines into blocks, each of these will be tried in
/// turn. Order matters here.
final List<BlockSyntax> blockSyntaxes = [];
/// Index of the current line.
int _pos = 0;
/// Whether the parser has encountered a blank line between two block-level
/// elements.
bool encounteredBlankLine = false;
/// The collection of built-in block parsers.
final List<BlockSyntax> standardBlockSyntaxes = [
const EmptyBlockSyntax(),
const HtmlBlockSyntax(),
const SetextHeaderSyntax(),
const HeaderSyntax(),
const CodeBlockSyntax(),
const BlockquoteSyntax(),
const HorizontalRuleSyntax(),
const UnorderedListSyntax(),
const OrderedListSyntax(),
const ParagraphSyntax()
];
BlockParser(this.lines, this.document) {
blockSyntaxes.addAll(document.blockSyntaxes);
if (document.withDefaultBlockSyntaxes) {
blockSyntaxes.addAll(standardBlockSyntaxes);
} else {
blockSyntaxes.add(const DummyBlockSyntax());
}
}
/// Gets the current line.
String get current => lines[_pos];
/// Gets the line after the current one or `null` if there is none.
String? get next {
// Don't read past the end.
if (_pos >= lines.length - 1) return null;
return lines[_pos + 1];
}
/// Gets the line that is [linesAhead] lines ahead of the current one, or
/// `null` if there is none.
///
/// `peek(0)` is equivalent to [current].
///
/// `peek(1)` is equivalent to [next].
String? peek(int linesAhead) {
if (linesAhead < 0) {
throw ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
}
// Don't read past the end.
if (_pos >= lines.length - linesAhead) return null;
return lines[_pos + linesAhead];
}
void advance() {
_pos++;
}
bool get isDone => _pos >= lines.length;
/// Gets whether or not the current line matches the given pattern.
bool matches(RegExp regex) {
if (isDone) return false;
return regex.hasMatch(current);
}
/// Gets whether or not the next line matches the given pattern.
bool matchesNext(RegExp regex) {
if (next == null) return false;
return regex.hasMatch(next!);
}
List<Node> parseLines() {
final blocks = <Node>[];
while (!isDone) {
for (final syntax in blockSyntaxes) {
if (syntax.canParse(this)) {
final block = syntax.parse(this);
if (block != null) blocks.add(block);
break;
}
}
}
return blocks;
}
}