Created
December 2, 2019 01:11
-
-
Save hyrious/5e53dab145f0df892aa93714f0579eb6 to your computer and use it in GitHub Desktop.
Try parse sublime-syntax
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'psych' | |
module Sublime | |
class Syntax | |
class Context | |
META_PATTERNS = %w( | |
meta_scope | |
meta_content_scope | |
meta_include_prototype | |
clear_scopes | |
) | |
attr_accessor :path, :patterns, *META_PATTERNS | |
def initialize path=[], patterns=[] | |
@path = path | |
@patterns = patterns | |
@meta_include_prototype = true | |
prepare_patterns patterns | |
end | |
def prepare_patterns patterns | |
return if patterns.empty? | |
META_PATTERNS.each do |key| | |
if patterns[0].key? key | |
public_send "#{key}=", patterns[0][key] | |
return prepare_patterns patterns.drop(1) | |
end | |
end | |
end | |
def to_s | |
@path.empty? ? '(anonymous)' : @path.join('->') | |
end | |
alias name to_s | |
end | |
def self.from_file path | |
yaml = (File.read path).delete_prefix %{%YAML 1.2\n} | |
new Psych.safe_load yaml | |
end | |
def initialize data | |
@data = data | |
@contexts = {} | |
@context_stack = [] | |
@scope_stack = [] | |
@regexps = {} | |
@result = [] | |
end | |
attr_reader :result | |
def strip_comments str | |
lines = str.lines(chomp: true) | |
lines.map! { |l| l.sub /\s*#.*$\n?/, '' } if lines.size > 1 | |
lines.join("\n") | |
end | |
def regexp str | |
@regexps[str] ||= Regexp.new str | |
end | |
# context_of 'object', 0, 2 | |
# => - clear_scopes: 1 | |
# - meta_scope: meta.mapping.key.json string.quoted.double.json | |
# - meta_include_prototype: false | |
# - include: inside-string | |
def context_of path | |
path = [path] if String === path | |
@contexts[path] ||= begin | |
real_path = path.flat_map { |e| Integer === e ? [e, 'push'] : e } | |
patterns = @data['contexts'].dig *real_path | |
return nil if patterns.nil? | |
Context.new path, patterns | |
end | |
end | |
def run code | |
@code = code.dup | |
clear_states | |
until @code.empty? | |
patterns = next_patterns | |
match_data, pattern = patterns.flat_map do |pattern| | |
match_data = regexp(strip_comments(pattern['match'])).match @code | |
match_data ? [[match_data, pattern]] : [] | |
end.min_by { |match_data, *| match_data.begin(0) } | |
break unless match_data | |
if !match_data.pre_match.empty? | |
match_head match_data.pre_match | |
end | |
@scope_stack.push pattern['scope'] if pattern['scope'] | |
match_head match_data.to_s | |
pattern['captures']&.each do |index, once_scope| | |
range = match_data.offset index | |
next if range[0] == range[1] | |
@result.last.push [*range, once_scope] | |
end | |
if push = pattern['push'] | |
if String === push | |
next_path = [push.split.last] | |
@context_stack.push *push.split.map { |e| context_of e } | |
else | |
@context_stack.push Context.new [], push | |
end | |
end | |
@context_stack.pop if pattern['pop'] | |
if push = pattern['set'] | |
@context_stack.pop | |
if String === push | |
next_path = [push.split.last] | |
@context_stack.push *push.split.map { |e| context_of e } | |
else | |
@context_stack.push Context.new [], push | |
end | |
end | |
if embed = pattern['embed'] | |
# not implemented | |
end | |
@scope_stack.pop if pattern['scope'] | |
end | |
match_head @code if !@code.empty? | |
end | |
def current_scope once_scope=nil | |
scopes = @scope_stack.clone | |
scopes << once_scope if once_scope | |
scopes | |
end | |
def match_head token, once_scope=nil | |
return if token.empty? | |
if !@code.start_with?(token) | |
puts "match_head(#{token.inspect}) error, @code = #{@code}" | |
exit 1 | |
end | |
@code.slice! token | |
@result.push [@pos, @pos + token.size, current_scope] | |
@pos += token.size | |
end | |
def clear_states | |
@pos = 0 | |
@contexts.clear | |
@context_stack.clear | |
@context_stack.push context_of 'main' | |
@scope_stack.clear | |
@scope_stack.push @data['scope'] | |
@result.clear | |
@prototype = context_of 'prototype' | |
end | |
def current_context | |
@context_stack.last | |
end | |
def include_prototype? | |
return false if @prototype.nil? | |
return false if @context_stack.any? { |e| !e.meta_include_prototype } | |
true | |
end | |
def next_patterns patterns=nil | |
if patterns.nil? | |
patterns = current_context.patterns | |
if include_prototype? | |
patterns = @prototype.patterns + patterns | |
end | |
end | |
patterns.flat_map do |pattern| | |
if path = pattern['include'] | |
next_patterns context_of(path).patterns | |
elsif pattern['match'] | |
[pattern] | |
else | |
[] | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment