From 1a9dd30112feaf3c42c2c254ef754cb0b6162bf5 Mon Sep 17 00:00:00 2001 From: Yessiest Date: Sat, 1 Mar 2025 19:54:20 +0000 Subject: [PATCH] it's all downhill from here --- lib/blankshell.rb | 1279 ++++++++++++++++++++++++++++++++++++++++++--- lib/test3.rb | 129 ++++- 2 files changed, 1327 insertions(+), 81 deletions(-) diff --git a/lib/blankshell.rb b/lib/blankshell.rb index 3bf64b1..4bc6cdf 100644 --- a/lib/blankshell.rb +++ b/lib/blankshell.rb @@ -2,6 +2,168 @@ module PointBlank module Parsing + module LinkSharedMethods + # Normalize a label + # @param string [String] + # @return [String] + def normalize_label(string) + string = string.downcase(:fold).strip.gsub(/\s+/, " ") + return nil if string.empty? + + string + end + + # Read link label. + # Returns matched label or nil, and remainder of the string + # @param text [String] + # @return [Array(, String)] + def read_return_label(text) + prev = text + label = "" + return nil, text unless text.start_with?('[') + + bracketcount = 0 + text.split(/(?, String)] + def read_label(text) + prev = text + label = "" + return nil, text unless text.start_with?('[') + + bracketcount = 0 + text.split(/(?, String)] + def read_destination(text) + if (result = text.match(/\A<.*?(?/m)) && + !result[0][1..].match?(/(?<])/, '')[1..-2], + text.delete_prefix(result[0]).lstrip] + elsif (result = text.match(/\A\S+/)) && + !result[0].start_with?('<') && + result && + balanced?(result[0]) + [result[0], + text.delete_prefix(result[0]).lstrip] + else + [nil, text] + end + end + + # Read link title. + # Returns matched label or nil, and remainder of the string + # @param text [String] + # @return [Array(, String)] + def read_title(text) + if text.start_with?("'") && + (result = text.match(/\A'.*?(?(?: \S|)/) - [line.lstrip.delete_prefix('>').lstrip, true] + [normalize(line), true] + end + + private + + # Normalize line in quoteblock + def normalize(line) + line.gsub(/\A {0,3}> ?/, '') + end + end + + # Fenced code block + # (TODO: This needs ~~~ as alternative to ticks, + # and proper relative indentation) + class FencedCodeBlock < NullParser + # (see ::PointBlank::Parsing::NullParser#begin?) + def self.begin?(line) + line.start_with?(/\A {0,3}```[^`]+$/) + end + + # (see ::PointBlank::Parsing::NullParser#applyprops) + def applyprops(block) + block.properties["infoline"] = @infoline + end + + # (see ::PointBlank::Parsing::NullParser#consume) + def consume(line, _parent = nil, **_hargs) + return [nil, false] if @closed + + try_close(line) + push(line) if @open && !@closed + self.open(line) + ["", false] + end + + private + + def try_close(line) + @closed = true if @open && line.match?(/\A {0,3}```/) + end + + def open(line) + return if @open + + @infoline = line.match(/\A {0,3}```(.*)/)[1] + @open = true + end + end + + # Indented code block + class IndentedBlock < NullParser + # (see ::PointBlank::Parsing::NullParser#begin?) + def self.begin?(line) + line.start_with?(/\A {4}/) + end + + # (see ::PointBlank::Parsing::NullParser#consume) + def consume(line, _parent = nil, **_hargs) + return [nil, nil] unless self.class.begin?(line) || + line.strip.empty? + + push(normalize(line)) + ["", false] + end + + private + + def normalize(line) + line.gsub("\A(?: |\t)", '') + end + end + + # Thematic break parser + class ThematicBreakParser < NullParser + # (see PointBlank::Parsing::NullParser#begin?) + def self.begin?(line) + line.match?(/\A {0,3}(?:[- ]+|[* ]+|[_ ]+)\n/) + end + + # (see PointBlank::Parsing::NullParser#consume) + def consume(_line, _parent = nil, **_hargs) + return [nil, nil] if @closed + + @closed = true + ["", nil] + end + end + + # Class of parsers that process the paragraph after it finished collection + class NullOverlay < NullParser + # Stub + def self.begin?(_line) + false + end + + # Process block after it closed + # @param block [::PointBlank::DOM::DOMObject] + # @param lazy [Boolean] + # @return [nil, Class] + def process(_block, lazy: false); end + end + + # Overlay for processing underline classes of paragraph + class ParagraphUnderlineOverlay < NullOverlay + # (see ::PointBlank::Parsing::NullOverlay#process) + def process(block, lazy: false) + output = check_underlines(block.content.lines.last, lazy) + block.content = block.content.lines[0..-2].join("") if output + output + end + + private + + # Check if the current line is an underline (morphs class) + def check_underlines(line, lazy) + return nil if lazy + + ::PointBlank::DOM::Paragraph.valid_children.each do |underline| + parser = underline.parser + next unless parser < ::PointBlank::Parsing::UnderlineParser + next unless parser.begin? line + + return underline + end + nil + end + end + + # Overlay for link reference definitions + class LinkReferenceOverlay < NullOverlay + include LinkSharedMethods + + def initialize + super + @definitions = {} + end + + # (see ::PointBlank::Parsing::NullOverlay#process) + def process(block, **_lazy) + text = block.content + loop do + prev = text + label, text = read_label(text) + break prev unless label + + destination, text = read_destination(text) + break prev unless destination + + title, text = read_title(text) + push_definition(label, destination, title) + end + modify(block, text) + nil + end + + private + + def root(block) + current_root = block + current_root = current_root.parent while current_root.parent + current_root + end + + def modify(block, text) + rootblock = root(block) + rootblock.properties[:linkdefs] = + if rootblock.properties[:linkdefs] + @definitions.merge(rootblock.properties[:linkdefs]) + else + @definitions.dup + end + block.content = text + end + + def push_definition(label, uri, title = nil) + labelname = label.strip.downcase.gsub(/\s+/, ' ') + return if @definitions[labelname] + + @definitions[labelname] = { + uri: uri, + title: title + } + end + end + + # Inline scanner + class StackScanner + def initialize(doc, init_tokens: nil) + @doc = doc + @init_tokens = init_tokens + end + + # Scan document + def scan + rounds = quantize(@doc.class.unsorted_children) + tokens = @init_tokens || [@doc.content] + rounds.each do |valid_parsers| + @valid_parsers = valid_parsers + tokens = tokenize(tokens) + tokens = forward_walk(tokens) + tokens = reverse_walk(tokens) + end + structure = finalize(tokens) + structure.each { |child| @doc.append_child(child) } + end + + private + + # Finalize structure, concatenate adjacent text parts, + # transform into Text objects + # @param parts [Array] + # @return [Array<::PointBlank::DOM::DOMObject>] + def finalize(structure) + structnew = [] + buffer = "" + structure.each do |block| + block = block.first if block.is_a? Array + buffer += block if block.is_a? String + next if block.is_a? String + + structnew.append(construct_text(buffer)) unless buffer.empty? + buffer = "" + structnew.append(block) + end + structnew.append(construct_text(buffer)) unless buffer.empty? + structnew + end + + # Construct text object for a string + # @param string [String] + # @return [::PointBlank::DOM::Text] + def construct_text(string) + obj = ::PointBlank::DOM::Text.new + obj.content = string + obj + end + + # Transform text into a list of tokens + def tokenize(tokens) + parts = tokens + @valid_parsers.each do |parser| + newparts = [] + parts.each do |x| + if x.is_a? String + newparts.append(*parser.tokenize(x)) + else + newparts.append(x) + end + end + parts = newparts + end + parts + end + + # Process parsed tokens (callback on open, forward search direction) + def forward_walk(parts) + parts = parts.dup + newparts = [] + while (part = parts.shift) + next newparts.append(part) if part.is_a? String + + if part[1].respond_to?(:forward_walk) && part.last == :open + part, parts = part[1].forward_walk([part] + parts) + end + newparts.append(part) + end + newparts + end + + # Process parsed tokens (callback on close, inverse search direction) + def reverse_walk(parts) + backlog = [] + parts.each do |part| + backlog.append(part) + next unless part.is_a? Array + next unless part.last == :close + next unless part[1].respond_to?(:reverse_walk) + + backlog = part[1].reverse_walk(backlog) + end + backlog + end + + # Quantize valid children + def quantize(children) + children.group_by(&:last).map { |_, v| v.map(&:first).map(&:parser) } + end + end + + # Null inline scanner element + # @abstract + class NullInline + class << self + attr_accessor :parser_for + end + + # Tokenize a string + # @param string [String] + # @return [Array] + def self.tokenize(string) + [string] + end + + # @!method self.reverse_walk(backlog) + # Reverse-walk the backlog and construct a valid element from it + # @param backlog [Array] + # @return [Array] + + # @!method self.forward_walk(backlog) + # Forward-walk the backlog starting from the current valid element + # @param backlog [Array] + # @return [Array] + + # Check that the symbol at this index is not escaped + # @param index [Integer] + # @param string [String] + # @return [nil, Integer] + def self.check_unescaped(index, string) + return index if index.zero? + + count = 0 + index -= 1 + while index >= 0 && string[index] == "\\" + count += 1 + index -= 1 + end + (count % 2).zero? + end + + # Find the first occurence of an unescaped pattern + # @param string [String] + # @param pattern [Regexp, String] + # @return [Integer, nil] + def self.find_unescaped(string, pattern) + initial = 0 + while (index = string.index(pattern, initial)) + return index if check_unescaped(index, string) + + initial = index + 1 + end + nil + end + + # Iterate over every string/unescaped token part + # @param string [String] + # @param pattern [Regexp] + # @param callback [#call] + # @return [Array] + def self.iterate_tokens(string, pattern, &filter) + tokens = [] + initial = 0 + while (index = string.index(pattern, initial)) + prefix = (index.zero? ? nil : string[initial..(index - 1)]) + tokens.append(prefix) if prefix + unescaped = check_unescaped(index, string) + match = filter.call(index.positive? ? string[..(index - 1)] : "", + string[index..], + unescaped) + tokens.append(match) + match = match.first if match.is_a? Array + initial = index + match.length + end + remaining = string[initial..] || "" + tokens.append(remaining) unless remaining.empty? + tokens + end + + # Build child + # @param children [Array] + # @return [::PointBlank::DOM::DOMObject] + def self.build(children) + obj = parser_for.new + if parser_for.valid_children.empty? + children.each do |child| + child = child.first if child.is_a? Array + child = construct_text(child) if child.is_a? String + obj.append_child(child) + end + else + tokens = children.map do |child| + child.is_a?(Array) ? child.first : child + end + scanner = StackScanner.new(obj, init_tokens: tokens) + scanner.scan + end + obj + end + + # Construct text object for a string + # @param string [String] + # @return [::PointBlank::DOM::Text] + def self.construct_text(string) + obj = ::PointBlank::DOM::Text.new + obj.content = string + obj + end + + # Check that contents can be contained within this element + # @param elements [Array] + # @return [Boolean] + def self.check_contents(elements) + elements.each do |element| + next unless element.is_a? ::PointBlank::DOM::DOMObject + next if parser_for.valid_children.include? element.class + + return false + end + true + end + end + + # Code inline parser + class CodeInline < NullInline + # (see ::PointBlank::Parsing::NullInline#tokenize) + def self.tokenize(string) + open = {} + iterate_tokens(string, "`") do |_before, current_text, matched| + if matched + match = current_text.match(/^`+/)[0] + if open[match] + open[match] = nil + [match, self, :close] + else + open[match] = true + [match, self, :open] + end + else + current_text[0] + end + end + end + + # TODO: optimize, buffer only after walking + # (see ::PointBlank::Parsing::NullInline#forward_walk) + def self.forward_walk(parts) + buffer = "" + opening = parts.first.first + cutoff = 0 + parts.each_with_index do |part, idx| + text = (part.is_a?(Array) ? part.first : part) + buffer += text + next unless part.is_a? Array + + break (cutoff = idx) if part.first == opening && + part.last == :close + end + buffer = buffer[opening.length..(-1 - opening.length)] + [cutoff.positive? ? build([buffer]) : opening, parts[(cutoff + 1)..]] + end + end + + # Autolink inline parser + class AutolinkInline < NullInline + # (see ::PointBlank::Parsing::NullInline#tokenize) + def self.tokenize(string) + iterate_tokens(string, /[<>]/) do |_before, current_text, matched| + if matched + if current_text.start_with?("<") + ["<", self, :open] + else + [">", self, :close] + end + else + current_text[0] + end + end + end + + # TODO: optimize, buffer only after walking + # (see ::PointBlank::Parsing::NullInline#forward_walk + def self.forward_walk(parts) + buffer = "" + cutoff = 0 + parts.each_with_index do |part, idx| + text = (part.is_a?(Array) ? part.first : part) + buffer += text + next unless part.is_a? Array + + break (cutoff = idx) if part.first == ">" && part.last == :close + end + return '<', parts[1..] unless buffer.match?(/^<[\w\-_+]+:[^<>\s]+>$/) + + [build([buffer[1..-2]]), parts[(cutoff + 1)..]] + end + end + + # Hyperreference inline superclass + # @abstract + class HyperlinkInline < NullInline + # Parse link properties according to given link suffix + # @param input [String] + # @return [Array(, String)] + def self.parse_linkinfo(input) + props, remainder = read_properties(input) + return nil, "" unless props + + capture = input[..(input.length - remainder.length - 1)] + [props, capture] + end + + # Build object and apply link info to it + # @param capture [Array] + # @return [::PointBlank::DOM::DOMObject] + def self.build_w_linkinfo(capture) + linkinfo = capture[-1][2] + obj = build(capture[1..-2]) + obj.properties = linkinfo + obj + end + + # TODO: optimize, increase index instead of building buffers + # (see ::PointBlank::Parsing::NullInline#reverse_walk) + def self.reverse_walk(backlog) + before = [] + capture = [] + open = true + cls = nil + backlog.reverse_each do |block| + (open ? capture : before).prepend(block) + next unless block.is_a?(Array) && block[1] < self + + open = false + cls = block[1] + return backlog unless block[1].check_contents(capture) + end + return backlog if open + + before + [cls.build_w_linkinfo(capture)] + end + end + + # Image inline parser + class ImageInline < HyperlinkInline + class << self + include ::PointBlank::Parsing::LinkSharedMethods + end + + # (see ::PointBlank::Parsing::NullInline#tokenize) + def self.tokenize(string) + iterate_tokens(string, /(?:!\[|\]\()/) do |_before, text, matched| + next text[0] unless matched + next ["![", self, :open] if text.start_with? "![" + next text[0] unless text.start_with? "]" + + info, capture = parse_linkinfo(text[1..]) + info ? ["]#{capture}", HyperlinkInline, info, :close] : text[0] + end + end + end + + # Link inline parser + class LinkInline < HyperlinkInline + class << self + include ::PointBlank::Parsing::LinkSharedMethods + end + + # (see ::PointBlank::Parsing::NullInline#tokenize) + def self.tokenize(string) + iterate_tokens(string, /(?:\[|\][(\[])/) do |_before, text, matched| + next text[0] unless matched + next ["[", self, :open] if text.start_with? "[" + next text[0] unless text.start_with? "]" + + info, capture = parse_linkinfo(text[1..]) + info ? ["]#{capture}", HyperlinkInline, info, :close] : text[0] + end + end + end + + # Emphasis and strong emphasis inline parser + class EmphInline < NullInline + INFIX_TOKENS = /^[^\p{S}\p{P}\p{Zs}_]_++[^\p{S}\p{P}\p{Zs}_]$/ + # (see ::PointBlank::Parsing::NullInline#tokenize) + def self.tokenize(string) + iterate_tokens(string, /(?:_++|\*++)/) do |bfr, text, matched| + token, afr = text.match(/^(_++|\*++)(.?)/)[1..2] + left = left_token?(bfr[-1] || "", token, afr) + right = right_token?(bfr[-1] || "", token, afr) + break_into_elements(token, [bfr[-1] || "", token, afr].join(''), + left, right, matched) + end + end + + # Is this token, given these surrounding characters, left-flanking? + # @param bfr [String] + # @param token [String] + # @param afr [String] + def self.left_token?(bfr, _token, afr) + bfr_white = bfr.match?(/[\p{Zs}\n\r]/) || bfr.empty? + afr_white = afr.match?(/[\p{Zs}\n\r]/) || afr.empty? + bfr_symbol = bfr.match?(/[\p{P}\p{S}]/) + afr_symbol = afr.match?(/[\p{P}\p{S}]/) + !afr_white && (!afr_symbol || (afr_symbol && (bfr_symbol || bfr_white))) + end + + # Is this token, given these surrounding characters, reft-flanking? + # @param bfr [String] + # @param token [String] + # @param afr [String] + def self.right_token?(bfr, _token, afr) + bfr_white = bfr.match?(/[\p{Z}\n\r]/) || bfr.empty? + afr_white = afr.match?(/[\p{Z}\n\r]/) || afr.empty? + bfr_symbol = bfr.match?(/[\p{P}\p{S}]/) + afr_symbol = afr.match?(/[\p{P}\p{S}]/) + !bfr_white && (!bfr_symbol || (bfr_symbol && (afr_symbol || afr_white))) + end + + # Break token string into elements + # @param token_inner [String] + # @param token [String] + # @param left [Boolean] + # @param right [Boolean] + # @param matched [Boolean] + def self.break_into_elements(token_inner, token, left, right, matched) + return token_inner[0] unless matched + + star_token = token_inner.match?(/^\*+$/) + infix_token = token.match(INFIX_TOKENS) + return token_inner if !star_token && infix_token + + if left && right + [token_inner, self, :open, :close] + elsif left + [token_inner, self, :open] + elsif right + [token_inner, self, :close] + else + token_inner + end + end + + # (see ::PointBlank::Parsing::NullInline#reverse_walk) + def self.reverse_walk(backlog) + until backlog.last.first.empty? + capture = [] + before = [] + closer = backlog.last + star = closer.first.match?(/^\*+$/) + open = true + backlog[..-2].reverse_each do |blk| + open = false if blk.is_a?(Array) && blk[2] == :open && + blk.first.match?(/^\*+$/) == star && + blk[1] == self && + ((blk.first.length + closer.first.length) % 3 != 0 || + ((blk.first.length % 3).zero? && + (closer.first.length % 3).zero?)) + (open ? capture : before).prepend(blk) + next unless blk.is_a?(Array) + return backlog unless blk[1].check_contents(capture) + end + return backlog if open + + opener = before[-1] + strong = if closer.first.length > 1 && opener.first.length > 1 + # Strong emphasis + closer[0] = closer.first[2..] + opener[0] = opener.first[2..] + true + else + # Emphasis + closer[0] = closer.first[1..] + opener[0] = opener.first[1..] + false + end + before = before[..-2] if opener.first.empty? + backlog = before + [build_emph(capture, strong)] + [closer] + end + backlog + end + + # Build strong or normal emphasis depending on the boolean flag + # @param children [Array] + # @param strong [Boolean] + # @return [::PointBlank::DOM::DOMObject] + def self.build_emph(children, strong) + obj = if strong + ::PointBlank::DOM::InlineStrong + else + ::PointBlank::DOM::InlineEmphasis + end.new + tokens = children.map do |child| + child.is_a?(Array) ? child.first : child + end + scanner = StackScanner.new(obj, init_tokens: tokens) + scanner.scan + obj end end end + # Domain object model elements module DOM class DOMError < StandardError; end @@ -353,7 +1347,8 @@ module PointBlank # Sort children by priority # @return [void] def sort_children - @valid_children = @unsorted_children.sort_by(&:last).map(&:first) + @valid_children = @unsorted_children&.sort_by(&:last)&.map(&:first) || + [] end # Define valid child for this DOMObject class @@ -375,13 +1370,25 @@ module PointBlank # @param child [::PointBlank::Parsing::NullParser] # @return [void] def define_parser(parser) + parser.parser_for = self @parser = parser end - # Define if this DOMObject class is overflowable + # Define an overlay - a parsing strategy that occurs once a block is closed. + # May transform block if #process method of the overlay class returns + # a class. + # @param overlay [::PointBlank::Parsing::NullOverlay] # @return [void] - def enable_overflow - @overflow = true + def define_overlay(overlay, priority = 9999) + @unsorted_overlays ||= [] + @unsorted_overlays.append([overlay, priority]) + end + + # Sort overlays by priority + # @return [void] + def sort_overlays + @valid_overlays = @unsorted_overlays&.sort_by(&:last)&.map(&:first) || + [] end # Parse a document @@ -399,24 +1406,35 @@ module PointBlank @scanner = sc.scanner @parser = sc.parser @unsorted_children = sc.unsorted_children.dup + @unsorted_overlays = sc.unsorted_overlays.dup end sort_children end + # Get array of valid overlays sorted by priority + # @return [Array<::PointBlank::Parsing::NullOverlay>] + def valid_overlays + sort_overlays unless @valid_overlays + @valid_overlays + end + # Get array of valid children sorted by priority + # @return [Array] def valid_children sort_children unless @valid_children @valid_children end - attr_accessor :scanner, :parser, :overflow, - :unsorted_children + attr_accessor :scanner, :parser, + :unsorted_children, + :unsorted_overlays end include ::Enumerable def initialize @children = [] + @temp_children = [] @properties = {} @content = "" end @@ -464,8 +1482,23 @@ module PointBlank @children.append(child) end - attr_accessor :content, :parser, :parent, :position - attr_reader :properties + # Append temp. child + # @param child [DOMObject, String] + def append_temp(child) + unless child.is_a?(::PointBlank::DOM::DOMObject) || + child.is_a?(String) + raise DOMError, "invlaid temp class #{child.class}" + end + + @temp_children.append(child) + end + + attr_accessor :content, :parser, :parent, :position, :properties + attr_reader :temp_children + end + + # Temp. text class + class TempText < DOMObject end # Inline text @@ -474,59 +1507,121 @@ module PointBlank # Inline preformatted text class InlinePre < DOMObject + define_parser ::PointBlank::Parsing::CodeInline end # Infline formattable text class InlineFormattable < DOMObject end - # Bold text - class InlineBold < InlineFormattable - end - - # Italics text - class InlineItalics < InlineFormattable - end - - # Inline italics text (alternative) - class InlineAltItalics < InlineFormattable - end - - # Underline text - class InlineUnder < InlineFormattable - end - - # Strikethrough text - class InlineStrike < InlineFormattable + # Image + class InlineImage < InlineFormattable + define_parser ::PointBlank::Parsing::ImageInline + define_child ::PointBlank::DOM::InlinePre, 4000 + ## that would be really funny lmao + # define_child ::PointBlank::DOM::InlineImage end # Hyperreferenced text class InlineLink < InlineFormattable - end - - # Image - class InlineImage < InlinePre + define_parser ::PointBlank::Parsing::LinkInline + define_child ::PointBlank::DOM::InlinePre, 4000 + define_child ::PointBlank::DOM::InlineImage, 5000 + ## idk if this makes sense honestly + # define_child ::PointBlank::DOM::InlineAutolink end # Linebreak class InlineBreak < DOMObject end + # Autolink + class InlineAutolink < DOMObject + define_parser ::PointBlank::Parsing::AutolinkInline + end + + # Inline root + class InlineRoot < DOMObject + define_scanner ::PointBlank::Parsing::StackScanner + define_child ::PointBlank::DOM::InlinePre, 4000 + define_child ::PointBlank::DOM::InlineAutolink, 4000 + define_child ::PointBlank::DOM::InlineImage, 5000 + define_child ::PointBlank::DOM::InlineLink, 6000 + end + + # Strong emphasis + class InlineStrong < InlineRoot + end + + # Emphasis + class InlineEmphasis < InlineRoot + end + + InlineRoot.class_eval do + define_child ::PointBlank::DOM::InlineStrong, 8000 + define_child ::PointBlank::DOM::InlineEmphasis, 8000 + end + + InlineRoot.subclasses.each(&:upsource) + + InlineStrong.class_eval do + define_parser ::PointBlank::Parsing::EmphInline + end + + InlineEmphasis.class_eval do + define_parser ::PointBlank::Parsing::EmphInline + end + + InlineImage.class_eval do + define_child ::PointBlank::DOM::InlineStrong, 8000 + define_child ::PointBlank::DOM::InlineEmphasis, 8000 + end + + InlineLink.class_eval do + define_child ::PointBlank::DOM::InlineStrong, 8000 + define_child ::PointBlank::DOM::InlineEmphasis, 8000 + end # Block root (virtual) class Block < DOMObject end + # Leaf block (virtual) + class LeafBlock < DOMObject + # Virtual hook to push inlines in place of leaf blocks + def parse_inner + child = ::PointBlank::DOM::Text.new + child.content = content + append_child(child) + end + end + # Document root class Document < Block end # Paragraph in a document (separated by 2 newlines) - class Paragraph < InlineFormattable + class Paragraph < DOMObject + class << self + # Define an overlay + end + define_parser ::PointBlank::Parsing::ParagraphParser + define_overlay ::PointBlank::Parsing::ParagraphUnderlineOverlay, 0 + define_overlay ::PointBlank::Parsing::LinkReferenceOverlay + + # Virtual hook to parse inline contents of a finished paragraph + def parse_inner + child = ::PointBlank::DOM::InlineRoot.new + child.content = content + scanner = ::PointBlank::Parsing::StackScanner.new(child) + scanner.scan + self.content = "" + child.each { |c| append_child(c) } + end end # Heading level 1 - class SetextHeading1 < InlineFormattable + class SetextHeading1 < LeafBlock define_parser ::PointBlank::Parsing::SetextParserLV1 end @@ -536,7 +1631,7 @@ module PointBlank end # Heading level 1 - class ATXHeading1 < InlineFormattable + class ATXHeading1 < LeafBlock define_parser ::PointBlank::Parsing::ATXParserLV1 end @@ -565,8 +1660,9 @@ module PointBlank define_parser ::PointBlank::Parsing::ATXParserLV6 end - # Preformatted code block - class CodeBlock < DOMObject + # Preformatted fenced code block + class CodeBlock < LeafBlock + define_parser ::PointBlank::Parsing::FencedCodeBlock end # Quote block @@ -577,31 +1673,44 @@ module PointBlank class TableBlock < DOMObject end - # List element - class ListElement < Block + # Unordered list element + class ULListElement < Block + end + + # Ordered list element + class OLListElement < Block end # Unordered list - class ULBlock < Block + class ULBlock < DOMObject + define_scanner ::PointBlank::Parsing::LineScanner + define_parser ::PointBlank::Parsing::ULParser + define_child ::PointBlank::DOM::ULListElement end # Ordered list block - class OLBlock < Block + class OLBlock < DOMObject + define_scanner ::PointBlank::Parsing::LineScanner + define_parser ::PointBlank::Parsing::ULParser + define_child ::PointBlank::DOM::OLListElement end # Indent block - class IndentBlock < DOMObject + class IndentBlock < LeafBlock + define_parser ::PointBlank::Parsing::IndentedBlock end # Horizontal rule class HorizontalRule < DOMObject + define_parser ::PointBlank::Parsing::ThematicBreakParser end # Block root (real) Block.class_eval do define_scanner ::PointBlank::Parsing::LineScanner define_parser ::PointBlank::Parsing::NullParser - define_child ::PointBlank::DOM::Paragraph + define_child ::PointBlank::DOM::IndentBlock, 9999 + define_child ::PointBlank::DOM::Paragraph, 9998 define_child ::PointBlank::DOM::ATXHeading1, 600 define_child ::PointBlank::DOM::ATXHeading2, 600 define_child ::PointBlank::DOM::ATXHeading3, 600 @@ -609,7 +1718,11 @@ module PointBlank define_child ::PointBlank::DOM::ATXHeading5, 600 define_child ::PointBlank::DOM::ATXHeading6, 600 define_child ::PointBlank::DOM::QuoteBlock, 600 - define_child ::PointBlank::DOM::ULBlock, 500 + define_child ::PointBlank::DOM::ULBlock, 700 + define_child ::PointBlank::DOM::OLBlock, 700 + define_child ::PointBlank::DOM::CodeBlock, 600 + define_child ::PointBlank::DOM::HorizontalRule, 300 + sort_children end Paragraph.class_eval do @@ -626,5 +1739,17 @@ module PointBlank ULBlock.class_eval do define_parser ::PointBlank::Parsing::ULParser end + + ULListElement.class_eval do + define_parser ::PointBlank::Parsing::ULElementParser + end + + OLBlock.class_eval do + define_parser ::PointBlank::Parsing::OLParser + end + + OLListElement.class_eval do + define_parser ::PointBlank::Parsing::OLElementParser + end end end diff --git a/lib/test3.rb b/lib/test3.rb index 10411a2..900f8de 100644 --- a/lib/test3.rb +++ b/lib/test3.rb @@ -2,7 +2,7 @@ require_relative 'blankshell' -structure = PointBlank::DOM::Document.parse(<<~DOC) +doc = <<~DOC Penis # STREEMER VIN SAUCE JORKS HIS PEANUTS ON S TREeAM > pee @@ -20,6 +20,13 @@ structure = PointBlank::DOM::Document.parse(<<~DOC) PEES ========= + [definition]: /url 'title' + [definition + 2 + ]: + /long_url_with_varying_stuff + (title) + > COME ON AND SNIFF THE PAINT > > WITH MEEE @@ -40,7 +47,120 @@ structure = PointBlank::DOM::Document.parse(<<~DOC) > < CONTINUATION > > BREAKER COCK + + + Plus block opens + and continues. + + This is the next paragraph of a plus block, + and this is a continuation line in the block + + This thing continues the outer block and has a plus sign still. + next part + - SIMPS LMAO + continuation + + This by the way should continue the + block but should be a separate + paragraph + - Next shit + + > INCLUDING INNER QUOTES BY THE WAY + WITH INNER PARAGRAPH FALL OFF!!! + + also a paragraph inside this thing + + - BUT CAN WE GET EVEN STUPIDER????? + + > YES WE CAN!!!! + + - Another element + + NOW it breaks + 1. FREDDY FAZBER??? + HARHAR HAR HAR HAR + HAR HAR HARHAR + + HOLY SHITTO FREDDY FASTBER??? + AR AR HARHAR HAR + HURHURHURHUR + + 2. fast + ber + 10. BIG + still the same OLblock + 11) OK NOW THIS IS EBIN + different block + 12930192) THIS still continues because idk why really + lmao + + > QUONT PARGRAP + WHAT THEF UCK BASSBOOSTED + + >```fencedcode block infoline (up to interpretation) + > #THIS should have a very specific structure, not modified by anything + > + > int main() { + > int i = 1; + > if (i > 0) { + > printf("anus\\n"); + > } + > return 0; + > } + >``` + + Also code block + + Hello mario + + also these should continue so that's a thing + + - Thematic break test + - - - - - - - - - - - - - - - - - - - - - + - Above should be a thematic break, not a list containing a thematic break + + but what if + -------------- + WRONG???? + + aaa + bbb + ccc + + now it's time to CUHHHMMMMMMM + - + - + - peepee Pe + - `cum on <` hogogwagarts >hogogwagarts` + - ``` test `should work tho `` and this should be continued` ```` + - \\ + - `` \\ ``` + - \\```amongus`` + - ``amongus``\\` + - ![image](/test.jpg 'title') + - moretests![image](/test.jpg (title))after + - more tests ![image](/invalid(link 'valid') after + - more tests ![image](/valid(link) 'valid') after + - next test + ![image `inner block` etc](/should_be_valid "should be valid") + amongus + - ![image `this shouldn't be allowed to be an image](/shouldn't be valid `technicallynotatitle`) + - [outer![inner](/AAAAAA 'peepee')](/poopoo 'AAAAAA') + - [amongus][definition] + - *emphasis on multiple words* + - **strong emphasis on multiple words** + - infix**emphasis**block + - no_infix_empahsis + - _emphasis_ + - __strong emphasis__ + - __nested __strong__ emphasis__ + - __(__this__)__ + - *among us*** ***vr* + - *among **us*vr**** + - *among **us *vr**** + - *among**us* + - [*outer*![****inner****](/AAAAAA 'peepee')](/poopoo 'AAAAAA') DOC + +structure = PointBlank::DOM::Document.parse(doc) def red(string) "\033[31m#{string}\033[0m" end @@ -49,10 +169,11 @@ def yellow(string) end def prettyprint(doc, indent = 0) - closed = doc.properties[:closed] - puts "#{yellow(doc.class.name.gsub(/\w+::DOM::/,""))}#{red(closed ? "(c)" : "")}: #{doc.content.inspect}" + puts "#{yellow(doc.class.name.gsub(/\w+::DOM::/, ''))}: "\ + "#{doc.content.inspect} "\ + "#{doc.properties.empty? ? '' : red(doc.properties.inspect)}" doc.children.each do |child| - print red("#{" " * indent} - ") + print red("#{' ' * indent} - ") prettyprint(child, indent + 4) end end