Skip to content

Instantly share code, notes, and snippets.

@hyrious
Created December 2, 2019 01:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyrious/5e53dab145f0df892aa93714f0579eb6 to your computer and use it in GitHub Desktop.
Save hyrious/5e53dab145f0df892aa93714f0579eb6 to your computer and use it in GitHub Desktop.
Try parse sublime-syntax
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