interscript 2.1.0b6 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67fee0bf4248e98e2e9921ed44c95d053587e3e7568d7bfd5c200f3dd8046950
4
- data.tar.gz: 84e0fa3cde05cf8e99cf810a9d0a16cdcab4d9fdcf9a366b405fef52cfce27eb
3
+ metadata.gz: 2088a63470d6a75261e2a774c73cf30b33e06e69b91d679b0eef5d7431197ef0
4
+ data.tar.gz: fafd8fa34f47f600c1dc0f7d8da2628a264aec9c5387b21bbcf483e9606c915f
5
5
  SHA512:
6
- metadata.gz: 4c5db8e05372a3039a0523a9d45057ede863bc50df44f0dccdc7a1200aef4310e8ae61fa105bc2e728acd2e92b9d9a1ddab283c3782a42052b18d6d339e3fff9
7
- data.tar.gz: 4fd36499b7ce58cc92ad4eca65113ce716c9b7a5f70a6cb5486bc44b42374537c57b90cb64b727ffc572e818fcd6c046ee1f77ad6d452768c0ff840dfe1a7761
6
+ metadata.gz: b9e25faac07f4c517c291a7fd61ab435b70c84e0e9404b6cc4da8196e044e603ed6475d66d20f36d48fb75b8117d454dd1fce61244dafd99f588298fcbd96844
7
+ data.tar.gz: 95c6a784bcca66f9b05f39260c2dd5a6a4a945f044c955d518d661381eb8de0610be7f7ec029fe923273f6624933f5affd7f825d94b80b838866f35fd7797d15
data/Gemfile CHANGED
@@ -26,4 +26,6 @@ unless ENV["SKIP_JS"]
26
26
  end
27
27
  end
28
28
 
29
+ gem 'pry'
30
+
29
31
  gem 'simplecov', require: false, group: :test
data/bin/console CHANGED
@@ -3,12 +3,8 @@
3
3
  require "bundler/setup"
4
4
  require "interscript"
5
5
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
6
+ require "interscript/utils/helpers"
7
+ include Interscript::Utils::Helpers
8
8
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
9
+ require "pry"
10
+ Pry.start
data/interscript.gemspec CHANGED
@@ -27,5 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_dependency "thor"
30
- spec.add_dependency "interscript-maps"
30
+ spec.add_dependency "interscript-maps", "~> #{Interscript::VERSION.split('.')[0,2].join(".")}.0"
31
+ spec.add_dependency "text"
31
32
  end
data/lib/interscript.rb CHANGED
@@ -53,6 +53,16 @@ module Interscript
53
53
  output_file
54
54
  end
55
55
 
56
+ # Detects the transliteration that gives the most close approximation
57
+ # of transliterating source into destination.
58
+ #
59
+ # Set multiple: true to get a full report.
60
+ def detect(source, destination, **kwargs)
61
+ detector = Detector.new
62
+ detector.set_from_kwargs(**kwargs)
63
+ detector.(source, destination)
64
+ end
65
+
56
66
  def map_gems
57
67
  @map_gems ||= Gem.find_latest_files('interscript-maps.yaml').map do |i|
58
68
  [i, YAML.load_file(i)]
@@ -109,3 +119,5 @@ require "interscript/interpreter"
109
119
 
110
120
  require 'interscript/dsl'
111
121
  require 'interscript/node'
122
+
123
+ require 'interscript/detector'
@@ -53,6 +53,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
53
53
 
54
54
  def compile_rule(r, map = @map, wrapper = false)
55
55
  c = ""
56
+ return c if r.reverse_run == true
56
57
  case r
57
58
  when Interscript::Node::Stage
58
59
  c += "map.stages.#{r.name} = function(s) {\n"
@@ -75,6 +76,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
75
76
  raise ArgumentError, "Can't parallelize rules with :not_before" if i.not_before
76
77
  raise ArgumentError, "Can't parallelize rules with :not_after" if i.not_after
77
78
 
79
+ next if i.reverse_run == true
78
80
  a << [compile_item(i.from, map, :par), compile_item(i.to, map, :parstr)]
79
81
  end
80
82
  ah = a.hash.abs
@@ -88,7 +90,8 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
88
90
  a = []
89
91
  Interscript::Stdlib.deterministic_sort_by_max_length(r.children).each do |i|
90
92
  raise ArgumentError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
91
-
93
+
94
+ next if i.reverse_run == true
92
95
  a << [build_regexp(i, map), compile_item(i.to, map, :parstr)]
93
96
  end
94
97
  ah = a.hash.abs
@@ -102,6 +105,8 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
102
105
  from = %{"#{build_regexp(r, map).gsub("/", "\\\\/")}"}
103
106
  if r.to == :upcase
104
107
  to = 'function(a){return a.toUpperCase();}'
108
+ elsif r.to == :downcase
109
+ to = 'function(a){return a.toLowerCase();}'
105
110
  else
106
111
  to = compile_item(r.to, map, :str)
107
112
  end
@@ -42,6 +42,7 @@ class Interscript::Compiler::Ruby < Interscript::Compiler
42
42
 
43
43
  def compile_rule(r, map = @map, wrapper = false)
44
44
  c = ""
45
+ return c if r.reverse_run == true
45
46
  case r
46
47
  when Interscript::Node::Stage
47
48
  c += "Interscript::Maps.add_map_stage \"#{@map.name}\", #{r.name.inspect} do |s|\n"
@@ -65,6 +66,7 @@ class Interscript::Compiler::Ruby < Interscript::Compiler
65
66
  raise ArgumentError, "Can't parallelize rules with :not_before" if i.not_before
66
67
  raise ArgumentError, "Can't parallelize rules with :not_after" if i.not_after
67
68
 
69
+ next if i.reverse_run == true
68
70
  a << [compile_item(i.from, map, :par), compile_item(i.to, map, :parstr)]
69
71
  end
70
72
  ah = a.hash.abs
@@ -79,6 +81,7 @@ class Interscript::Compiler::Ruby < Interscript::Compiler
79
81
  Interscript::Stdlib.deterministic_sort_by_max_length(r.children).each do |i|
80
82
  raise ArgumentError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
81
83
 
84
+ next if i.reverse_run == true
82
85
  a << [build_regexp(i, map), compile_item(i.to, map, :parstr)]
83
86
  end
84
87
  ah = a.hash.abs
@@ -92,6 +95,8 @@ class Interscript::Compiler::Ruby < Interscript::Compiler
92
95
  from = "/#{build_regexp(r, map).gsub("/", "\\\\/")}/"
93
96
  if r.to == :upcase
94
97
  to = '&:upcase'
98
+ elsif r.to == :downcase
99
+ to = '&:downcase'
95
100
  else
96
101
  to = compile_item(r.to, map, :str)
97
102
  end
@@ -0,0 +1,60 @@
1
+ require "text"
2
+
3
+ class Interscript::Detector
4
+ attr_accessor :compiler
5
+ attr_accessor :distance_computer
6
+ attr_accessor :map_pattern
7
+
8
+ # TODO: use transliterate_each
9
+ attr_accessor :each
10
+
11
+ attr_accessor :load_path
12
+ attr_accessor :cache
13
+
14
+ # Returns a summary of all detected transliterations
15
+ attr_accessor :multiple
16
+
17
+ def initialize
18
+ @compiler = Interscript::Interpreter
19
+ @distance_computer = DistanceComputer::Levenshtein
20
+ @map_pattern = "*"
21
+
22
+ @each = false
23
+
24
+ @load_path = false
25
+ @cache = CACHE
26
+ end
27
+
28
+ def set_from_kwargs(**kwargs)
29
+ kwargs.each do |k,v|
30
+ self.public_send(:"#{k}=", v)
31
+ end
32
+ end
33
+
34
+ def call(source, destination)
35
+ maps = Interscript.maps(select: @map_pattern, load_path: @load_path)
36
+
37
+ summary = maps.map do |map|
38
+ try_dest = Interscript.transliterate(map, source, compiler: @compiler)
39
+
40
+ [map, try_dest]
41
+ end.map do |map, try_dest|
42
+ dist = @distance_computer.(try_dest, destination)
43
+
44
+ [map, dist]
45
+ end.sort_by(&:last).to_h
46
+
47
+ if @multiple
48
+ summary.to_h
49
+ else
50
+ summary.first.first
51
+ end
52
+ end
53
+
54
+ CACHE = {}
55
+
56
+ # A DistanceComputer needs to respond to #call(source, destination)
57
+ module DistanceComputer
58
+ Levenshtein = Text::Levenshtein.method(:distance)
59
+ end
60
+ end
@@ -2,12 +2,45 @@ require "yaml"
2
2
 
3
3
  module Interscript::DSL
4
4
  @cache = {}
5
- def self.parse(map_name)
5
+ def self.parse(map_name, reverse: true)
6
6
  # map name aliases? here may be a place to wrap it
7
7
 
8
8
  return @cache[map_name] if @cache[map_name]
9
- path = Interscript.locate(map_name)
9
+
10
+ # This is a composition, so let's make a new virtual map
11
+ # that calls all maps in a sequence.
12
+ if map_name.include? "|"
13
+ map_parts = map_name.split("|").map(&:strip)
14
+
15
+ doc = Interscript::DSL::Document.new(map_name) do
16
+ map_parts.each_with_index do |i, idx|
17
+ dependency i, as: :"part#{idx}"
18
+ end
19
+
20
+ stage {
21
+ map_parts.each_with_index do |i, idx|
22
+ run map[:"part#{idx}"].stage.main
23
+ end
24
+ }
25
+ end.node
26
+
27
+ return @cache[map_name] = doc
28
+ end
29
+
30
+ path = begin
31
+ Interscript.locate(map_name)
32
+ rescue Interscript::MapNotFoundError => e
33
+ # But maybe we called the map in a reversed fashion?
34
+ begin
35
+ raise e if reverse == false # Protect from an infinite loop
36
+ reverse_name = Interscript::Node::Document.reverse_name(map_name)
37
+ return @cache[map_name] = parse(reverse_name, reverse: false).reverse
38
+ rescue Interscript::MapNotFoundError
39
+ raise e
40
+ end
41
+ end
10
42
  library = path.end_with?(".iml")
43
+
11
44
  map_name = File.basename(path, ".imp")
12
45
  map_name = File.basename(map_name, ".iml")
13
46
 
@@ -8,16 +8,16 @@ class Interscript::DSL::Group
8
8
  self.instance_exec(&block)
9
9
  end
10
10
 
11
- def run(stage)
11
+ def run(stage, **kwargs)
12
12
  if stage.class != Interscript::Node::Item::Stage
13
13
  raise TypeError, "I::Node::Item::Stage expected, got #{stage.class}"
14
14
  end
15
- @node.children << Interscript::Node::Rule::Run.new(stage)
15
+ @node.children << Interscript::Node::Rule::Run.new(stage, **kwargs)
16
16
  end
17
17
 
18
18
  def sub(from, to, **kwargs, &block)
19
- puts "sub(#{from.inspect},#{to}, kargs = #{
20
- kargs.inspect
19
+ puts "sub(#{from.inspect},#{to}, kwargs = #{
20
+ kwargs.inspect
21
21
  }) from #{self.inspect}" if $DEBUG
22
22
 
23
23
  rule = Interscript::Node::Rule::Sub.new(from, to, **kwargs)
@@ -25,6 +25,7 @@ class Interscript::DSL::Group
25
25
  end
26
26
 
27
27
  def upcase; :upcase; end
28
+ def downcase; :downcase; end
28
29
 
29
30
  Interscript::Stdlib.available_functions.each do |fun|
30
31
  define_method fun do |**kwargs|
@@ -35,9 +36,9 @@ class Interscript::DSL::Group
35
36
  end
36
37
  end
37
38
 
38
- def parallel(&block)
39
+ def parallel(**kwargs, &block)
39
40
  puts "parallel(#{chars.inspect}) from #{self.inspect}" if $DEBUG
40
- group = Interscript::DSL::Group::Parallel.new(&block)
41
+ group = Interscript::DSL::Group::Parallel.new(**kwargs, &block)
41
42
  @node.children << group.node
42
43
  end
43
44
  end
@@ -1,6 +1,6 @@
1
1
  class Interscript::DSL::Group::Parallel < Interscript::DSL::Group
2
- def initialize(&block)
3
- @node = Interscript::Node::Group::Parallel.new
2
+ def initialize(reverse_run: nil, &block)
3
+ @node = Interscript::Node::Group::Parallel.new(reverse_run: reverse_run)
4
4
  self.instance_exec(&block)
5
5
  end
6
6
  end
@@ -76,6 +76,7 @@ class Interscript::Interpreter < Interscript::Compiler
76
76
  end
77
77
 
78
78
  def execute_rule r
79
+ return if r.reverse_run == true
79
80
  case r
80
81
  when Interscript::Node::Group::Parallel
81
82
  if r.cached_tree
@@ -96,6 +97,7 @@ class Interscript::Interpreter < Interscript::Compiler
96
97
  raise ArgumentError, "Can't parallelize rules with :after" if i.after
97
98
  raise ArgumentError, "Can't parallelize rules with :not_before" if i.not_before
98
99
  raise ArgumentError, "Can't parallelize rules with :not_after" if i.not_after
100
+ next if i.reverse_run == true
99
101
  subs_array << [build_item(i.from, :par), build_item(i.to, :parstr)]
100
102
  end
101
103
  tree = Interscript::Stdlib.parallel_replace_compile_tree(subs_array) #.sort_by{|k,v| -k.length})
@@ -108,7 +110,7 @@ class Interscript::Interpreter < Interscript::Compiler
108
110
  subs_array = []
109
111
  Interscript::Stdlib.deterministic_sort_by_max_length(r.children).each do |i| # rule.from.max_length gives somewhat better test results, why is that
110
112
  raise ArgumentError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
111
-
113
+ next if i.reverse_run == true
112
114
  subs_array << [build_regexp(i), build_item(i.to, :parstr)]
113
115
  end
114
116
  r.subs_regexp = Interscript::Stdlib.parallel_regexp_compile(subs_array)
@@ -129,6 +131,8 @@ class Interscript::Interpreter < Interscript::Compiler
129
131
  when Interscript::Node::Rule::Sub
130
132
  if r.to == :upcase
131
133
  @str = @str.gsub(Regexp.new(build_regexp(r)), &:upcase)
134
+ elsif r.to == :downcase
135
+ @str = @str.gsub(Regexp.new(build_regexp(r)), &:downcase)
132
136
  else
133
137
  @str = @str.gsub(Regexp.new(build_regexp(r)), build_item(r.to, :str))
134
138
  end
@@ -4,6 +4,10 @@ class Interscript::Node
4
4
  raise NotImplementedError, "You can't construct a Node directly"
5
5
  end
6
6
 
7
+ def ==(other)
8
+ self.class == other.class
9
+ end
10
+
7
11
  def to_hash
8
12
  { :class => self.class.to_s,
9
13
  :question => "is something missing?"
@@ -7,6 +7,12 @@ class Interscript::Node::AliasDef < Interscript::Node
7
7
  @data = data
8
8
  end
9
9
 
10
+ def ==(other)
11
+ super &&
12
+ self.name == other.name &&
13
+ self.data == other.data
14
+ end
15
+
10
16
  def to_hash
11
17
  { :class => self.class.to_s,
12
18
  :name => @name,
@@ -4,6 +4,22 @@ class Interscript::Node::Dependency < Interscript::Node
4
4
  def initialize
5
5
  end
6
6
 
7
+ def reverse
8
+ rdep = self.class.new
9
+ rdep.name = name
10
+ rdep.full_name = Interscript::Node::Document.reverse_name(full_name)
11
+ rdep.import = import
12
+ rdep.document = document&.reverse
13
+ rdep
14
+ end
15
+
16
+ def ==(other)
17
+ super &&
18
+ self.full_name == other.full_name &&
19
+ self.import == other.import &&
20
+ self.name == other.name
21
+ end
22
+
7
23
  def to_hash
8
24
  { :class => self.class.to_s,
9
25
  :name => @name,
@@ -34,6 +34,39 @@ class Interscript::Node::Document
34
34
  end
35
35
  end
36
36
 
37
+ def reverse
38
+ @reverse ||= self.class.new.tap do |rdoc|
39
+ rdoc.name = self.class.reverse_name(name)
40
+ rdoc.metadata = metadata&.reverse
41
+ rdoc.tests = tests&.reverse
42
+ rdoc.dependencies = dependencies.map(&:reverse)
43
+ rdoc.stages = stages.transform_values(&:reverse)
44
+ rdoc.dep_aliases = dep_aliases.transform_values(&:reverse)
45
+ rdoc.aliases = aliases
46
+ end
47
+ end
48
+
49
+ def self.reverse_name(name)
50
+ newname = (name || "noname").split("-")
51
+ newname[2], newname[3] = newname[3], newname[2] if newname.length >= 4
52
+ newname = newname.join("-")
53
+ if newname == name
54
+ newname.gsub!("-reverse", "")
55
+ end
56
+ if newname == name
57
+ newname += "-reverse"
58
+ end
59
+ newname
60
+ end
61
+
62
+ def ==(other)
63
+ self.class == other.class &&
64
+ self.metadata == other.metadata &&
65
+ self.tests == other.tests &&
66
+ self.stages == other.stages &&
67
+ self.aliases == other.aliases
68
+ end
69
+
37
70
  def to_hash
38
71
  { :class => self.class.to_s, :metadata => @metadata&.to_hash,
39
72
  :tests => @tests&.to_hash,
@@ -1,7 +1,8 @@
1
1
  class Interscript::Node::Group < Interscript::Node
2
- attr_accessor :children
2
+ attr_accessor :children, :reverse_run
3
3
 
4
- def initialize
4
+ def initialize(reverse_run: nil)
5
+ @reverse_run = reverse_run
5
6
  @children = []
6
7
  end
7
8
 
@@ -20,11 +21,21 @@ class Interscript::Node::Group < Interscript::Node
20
21
  self
21
22
  end
22
23
 
24
+ def reverse
25
+ self.class.new(reverse_run: reverse_run.nil? ? nil : !reverse_run).tap do |r|
26
+ r.children = self.children.reverse.map(&:reverse)
27
+ end
28
+ end
29
+
23
30
  def to_hash
24
31
  { :class => self.class.to_s,
25
32
  :children => @children.map{|x| x.to_hash} }
26
33
  end
27
34
 
35
+ def ==(other)
36
+ super && self.children == other.children && self.reverse_run == other.reverse_run
37
+ end
38
+
28
39
  def inspect
29
40
  @children.map(&:inspect).join("\n").gsub(/^/, " ")
30
41
  end
@@ -36,6 +36,10 @@ class Interscript::Node::Item < Interscript::Node
36
36
  :item => self.item }
37
37
  end
38
38
 
39
+ def ==(other)
40
+ super
41
+ end
42
+
39
43
  def self.try_convert(i)
40
44
  i = Interscript::Node::Item::String.new(i) if i.class == ::String
41
45
  raise TypeError, "Wrong type #{i.class}, expected I::Node::Item" unless Interscript::Node::Item === i
@@ -10,6 +10,10 @@ class Interscript::Node::Item::Alias < Interscript::Node::Item
10
10
  !map && Interscript::Stdlib::ALIASES.has_key?(name)
11
11
  end
12
12
 
13
+ def boundary_like?
14
+ Interscript::Stdlib.boundary_like_alias?(name)
15
+ end
16
+
13
17
  def max_length
14
18
  if stdlib?
15
19
  ([:none].include? name) ? 0 : 1
@@ -19,6 +23,10 @@ class Interscript::Node::Item::Alias < Interscript::Node::Item
19
23
  end
20
24
  end
21
25
 
26
+ # Not implemented properly
27
+ def downcase; self; end
28
+ def upcase; self; end
29
+
22
30
  def first_string
23
31
  self
24
32
  end
@@ -32,6 +40,10 @@ class Interscript::Node::Item::Alias < Interscript::Node::Item
32
40
  }
33
41
  end
34
42
 
43
+ def ==(other)
44
+ super && self.name == other.name && self.map == other.map
45
+ end
46
+
35
47
  def inspect
36
48
  if map
37
49
  "map.#{map}.#{name}"
@@ -25,6 +25,9 @@ class Interscript::Node::Item::Any < Interscript::Node::Item
25
25
  end
26
26
  end
27
27
 
28
+ def downcase; self.class.new(self.data.map(&:downcase)); end
29
+ def upcase; self.class.new(self.data.map(&:upcase)); end
30
+
28
31
  def first_string
29
32
  case @value
30
33
  when Array
@@ -70,6 +73,10 @@ class Interscript::Node::Item::Any < Interscript::Node::Item
70
73
  hash
71
74
  end
72
75
 
76
+ def ==(other)
77
+ super && self.data == other.data
78
+ end
79
+
73
80
  def inspect
74
81
  "any(#{value.inspect})"
75
82
  end
@@ -15,11 +15,18 @@ class Interscript::Node::Item::CaptureGroup < Interscript::Node::Item
15
15
  data.nth_string
16
16
  end
17
17
 
18
+ def downcase; self.dup.tap { |i| i.data = i.data.downcase }; end
19
+ def upcase; self.dup.tap { |i| i.data = i.data.upcase }; end
20
+
18
21
  def to_hash
19
22
  { :class => self.class.to_s,
20
23
  :data => self.data.to_hash }
21
24
  end
22
25
 
26
+ def ==(other)
27
+ super && self.data == other.data
28
+ end
29
+
23
30
  def inspect
24
31
  "capture(#{@data.inspect})"
25
32
  end
@@ -44,6 +51,10 @@ class Interscript::Node::Item::CaptureRef < Interscript::Node::Item
44
51
  :id => self.id }
45
52
  end
46
53
 
54
+ def ==(other)
55
+ super && self.id == other.id
56
+ end
57
+
47
58
  def inspect
48
59
  "ref(#{@id.inspect})"
49
60
  end
@@ -10,11 +10,35 @@ class Interscript::Node::Item::Group < Interscript::Node::Item
10
10
  def +(item)
11
11
  item = Interscript::Node::Item.try_convert(item)
12
12
  out = self.dup
13
- out.children << item
13
+ if Interscript::Node::Item::Group === item
14
+ out.children += item.children
15
+ else
16
+ out.children << item
17
+ end
14
18
  out.verify!
15
19
  out
16
20
  end
17
21
 
22
+ def compact
23
+ out = self.dup do |n|
24
+ n.children = n.children.reject do |i|
25
+ (Interscript::Node::Alias === i && i.name == :none) ||
26
+ (Interscript::Node::String === i && i.data == "")
27
+ end
28
+ end
29
+
30
+ if out.children.count == 0
31
+ Interscript::Node::Alias.new(:none)
32
+ elsif out.children.count == 1
33
+ out.children.first
34
+ else
35
+ out
36
+ end
37
+ end
38
+
39
+ def downcase; self.dup.tap { |i| i.children = i.children.map(&:downcase) }; end
40
+ def upcase; self.dup.tap { |i| i.children = i.children.map(&:upcase) }; end
41
+
18
42
  # Verify if a group is valid
19
43
  def verify!
20
44
  wrong = @children.find do |i|
@@ -45,6 +69,10 @@ class Interscript::Node::Item::Group < Interscript::Node::Item
45
69
  :children => self.children.map{|x| x.to_hash} }
46
70
  end
47
71
 
72
+ def ==(other)
73
+ super && self.children == other.children
74
+ end
75
+
48
76
  def inspect
49
77
  @children.map(&:inspect).join("+")
50
78
  end
@@ -22,6 +22,10 @@ class Interscript::Node::Item::Repeat < Interscript::Node::Item
22
22
  :data => self.data.to_hash }
23
23
  end
24
24
 
25
+ def ==(other)
26
+ super && self.data == other.data
27
+ end
28
+
25
29
  def inspect
26
30
  str = case self
27
31
  when Interscript::Node::Item::Maybe
@@ -13,6 +13,10 @@ class Interscript::Node::Item::Stage < Interscript::Node::Item
13
13
  }
14
14
  end
15
15
 
16
+ def ==(other)
17
+ super && self.name == other.name && self.map == other.map
18
+ end
19
+
16
20
  def inspect
17
21
  if map
18
22
  "map.#{@map}.stage.#{@name}"
@@ -17,6 +17,9 @@ class Interscript::Node::Item::String < Interscript::Node::Item
17
17
  self.data
18
18
  end
19
19
 
20
+ def downcase; self.dup.tap { |i| i.data = i.data.downcase }; end
21
+ def upcase; self.dup.tap { |i| i.data = i.data.upcase }; end
22
+
20
23
  alias nth_string first_string
21
24
 
22
25
  def + other
@@ -33,6 +36,10 @@ class Interscript::Node::Item::String < Interscript::Node::Item
33
36
  end
34
37
  end
35
38
 
39
+ def ==(other)
40
+ super && self.data == other.data
41
+ end
42
+
36
43
  def inspect
37
44
  @data.inspect
38
45
  end
@@ -11,6 +11,16 @@ class Interscript::Node::MetaData < Interscript::Node
11
11
  @data[k]
12
12
  end
13
13
 
14
+ def reverse
15
+ self.class.new(data.dup, **{}).tap do |rmd|
16
+ rmd[:source_script], rmd[:destination_script] = rmd[:destination_script], rmd[:source_script]
17
+ end
18
+ end
19
+
20
+ def ==(other)
21
+ super && self.data == other.data
22
+ end
23
+
14
24
  def to_hash
15
25
  {:class => self.class.to_s,
16
26
  :data => @data}
@@ -1,4 +1,7 @@
1
1
  class Interscript::Node::Rule < Interscript::Node
2
+ def ==(other)
3
+ super && self.reverse_run == other.reverse_run
4
+ end
2
5
  end
3
6
 
4
7
  require "interscript/node/rule/sub"
@@ -1,7 +1,8 @@
1
1
  class Interscript::Node::Rule::Funcall < Interscript::Node::Rule
2
- attr_accessor :name, :kwargs
3
- def initialize name, **kwargs
2
+ attr_accessor :name, :kwargs, :reverse_run
3
+ def initialize name, reverse_run: nil, **kwargs
4
4
  @name = name
5
+ @reverse_run = reverse_run
5
6
  @kwargs = kwargs
6
7
  end
7
8
 
@@ -12,6 +13,15 @@ class Interscript::Node::Rule::Funcall < Interscript::Node::Rule
12
13
  }
13
14
  end
14
15
 
16
+ def reverse
17
+ self.class.new(Interscript::Stdlib.reverse_function[@name.to_sym],
18
+ reverse_run: reverse_run.nil? ? nil : !reverse_run, **kwargs)
19
+ end
20
+
21
+ def ==
22
+ super && self.name == other.name && self.kwargs == other.kwargs
23
+ end
24
+
15
25
  def inspect
16
26
  "#{@name} #{kwargs.inspect[1..-2]}"
17
27
  end
@@ -1,7 +1,8 @@
1
1
  class Interscript::Node::Rule::Run < Interscript::Node::Rule
2
- attr_accessor :stage
3
- def initialize stage
2
+ attr_accessor :stage, :reverse_run
3
+ def initialize stage, reverse_run: nil
4
4
  @stage = stage
5
+ @reverse_run = reverse_run
5
6
  end
6
7
 
7
8
  def to_hash
@@ -9,7 +10,19 @@ class Interscript::Node::Rule::Run < Interscript::Node::Rule
9
10
  :stage => self.stage.to_hash }
10
11
  end
11
12
 
13
+ def reverse
14
+ Interscript::Node::Rule::Run.new(stage,
15
+ reverse_run: reverse_run.nil? ? nil : !reverse_run
16
+ )
17
+ end
18
+
19
+ def ==(other)
20
+ super && self.stage == other.stage
21
+ end
22
+
12
23
  def inspect
13
- "run #{@stage.inspect}"
24
+ out = "run #{@stage.inspect}"
25
+ out += ", reverse_run: #{@reverse_run.inspect}" unless reverse_run.nil?
26
+ out
14
27
  end
15
28
  end
@@ -1,12 +1,19 @@
1
1
  class Interscript::Node::Rule::Sub < Interscript::Node::Rule
2
2
  attr_accessor :from, :to
3
3
  attr_accessor :before, :not_before, :after, :not_after
4
+ attr_accessor :reverse_before, :reverse_not_before, :reverse_after, :reverse_not_after
5
+ attr_accessor :reverse_run
4
6
  attr_accessor :priority
5
7
 
6
- def initialize from, to, before: nil, not_before: nil, after: nil, not_after: nil, priority: nil
8
+ def initialize (from, to,
9
+ before: nil, not_before: nil,
10
+ after: nil, not_after: nil,
11
+ priority: nil, reverse_run: nil)
7
12
  self.from = Interscript::Node::Item.try_convert from
8
13
  if to == :upcase
9
14
  self.to = :upcase
15
+ elsif to == :downcase
16
+ self.to = :downcase
10
17
  else
11
18
  self.to = Interscript::Node::Item.try_convert to
12
19
  end
@@ -16,6 +23,8 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
16
23
  #raise TypeError, "Can't supply both before and not_before" if before && not_before
17
24
  #raise TypeError, "Can't supply both after and not_after" if after && not_after
18
25
 
26
+ self.reverse_run = reverse_run
27
+
19
28
  self.before = Interscript::Node::Item.try_convert(before) if before
20
29
  self.after = Interscript::Node::Item.try_convert(after) if after
21
30
  self.not_before = Interscript::Node::Item.try_convert(not_before) if not_before
@@ -37,7 +46,13 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
37
46
  puts params.inspect if $DEBUG
38
47
  hash = { :class => self.class.to_s,
39
48
  :from => self.from.to_hash,
40
- :to => Symbol === self.to ? self.to : self.to.to_hash
49
+ :to => Symbol === self.to ? self.to : self.to.to_hash,
50
+ :reverse_run => self.reverse_run,
51
+ :before => self.before&.to_hash,
52
+ :not_before => self.not_before&.to_hash,
53
+ :after => self.after&.to_hash,
54
+ :not_after => self.not_after&.to_hash,
55
+ :priority => self.priority
41
56
  }
42
57
 
43
58
  hash[:before] = self.before&.to_hash if self.before
@@ -49,19 +64,165 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
49
64
  hash
50
65
  end
51
66
 
67
+ def reverse
68
+ if to == :upcase
69
+ xfrom = from.downcase
70
+ xto = :downcase
71
+ elsif to == :downcase
72
+ xfrom = from.upcase
73
+ xto = :upcase
74
+ else
75
+ xto, xfrom = reverse_transfer(from, to)
76
+ end
77
+
78
+ # A special case: sub "a", "" shouldn't be present in a reverse map
79
+ rrun = self.reverse_run.nil? ? nil : !self.reverse_run
80
+ if rrun.nil? && !has_assertions? &&
81
+ (xfrom == "" ||
82
+ (Interscript::Node::Item::String === xfrom && xfrom.data == '') ||
83
+ (Interscript::Node::Item::Alias === xfrom && xfrom.name == :none)
84
+ )
85
+
86
+ rrun = true
87
+ end
88
+
89
+ Interscript::Node::Rule::Sub.new(xfrom, xto,
90
+ before: before, after: after,
91
+ not_before: not_before, not_after: not_after,
92
+
93
+ reverse_run: rrun,
94
+
95
+ priority: priority ? -priority : nil
96
+ )
97
+ end
98
+
99
+ def has_assertions?
100
+ !!(before || not_before || not_after || after)
101
+ end
102
+
103
+ # Attempt to transfer some references to boundary/line_begin around.
104
+ # Those in general should go into before/after clauses, but for now
105
+ # let's try to get the best compatibility possible. Also, CaptureGroup,
106
+ # CaptureRef need to be shifted around
107
+ def reverse_transfer from, to
108
+ # This part is about moving initial and final boundary like aliases
109
+ case from
110
+ when Interscript::Node::Item::Group
111
+ first = from.children.first
112
+ last = from.children.last
113
+
114
+ if Interscript::Node::Item::Alias === first && first.boundary_like?
115
+ out = Interscript::Node::Item::Group.new + first + to
116
+ to = out.compact
117
+
118
+ from = from.dup.tap do |i|
119
+ i.children = i.children[1..-1]
120
+ end.compact
121
+ end
122
+
123
+ if Interscript::Node::Item::Alias === last && last.boundary_like?
124
+ out = Interscript::Node::Item::Group.new + to + last
125
+ to = out.compact
126
+
127
+ from = from.dup.tap do |i|
128
+ i.children = i.children[0..-2]
129
+ end.compact
130
+ end
131
+ when Interscript::Node::Item::Alias
132
+ if from.boundary_like?
133
+ to = if from.name.to_s.end_with? "_end"
134
+ Interscript::Node::Item::Group.new + to + from
135
+ else
136
+ Interscript::Node::Item::Group.new + from + to
137
+ end
138
+ from = Interscript::Node::Item::Alias.new(:none)
139
+ end
140
+ end
141
+
142
+ # This part is about moving backreferences
143
+ state = {left:[], right:[]}
144
+
145
+ from = reverse_transfer_visit(from, :from, state)
146
+ to = reverse_transfer_visit(to, :to, state)
147
+
148
+ [from, to]
149
+ end
150
+
151
+ private def reverse_transfer_visit(node, type, state)
152
+ node = Interscript::Node::Item.try_convert(node)
153
+
154
+ case node
155
+ when Interscript::Node::Item::Alias
156
+ if node.name == :kor_maybedash
157
+ state[:left] << node
158
+ Interscript::Node::Item::CaptureRef.new(state[:left].length)
159
+ else
160
+ node
161
+ end
162
+ when Interscript::Node::Item::String
163
+ node
164
+ when Interscript::Node::Item::Any
165
+ if Array === node.value
166
+ node.dup.tap do |i|
167
+ i.value = i.value.map { |c| reverse_transfer_visit(c, type, state) }
168
+ end
169
+ else
170
+ node
171
+ end
172
+ when Interscript::Node::Item::Group
173
+ node.dup.tap do |i|
174
+ i.children = i.children.map { |c| reverse_transfer_visit(c, type, state) }
175
+ end
176
+ when Interscript::Node::Item::Repeat
177
+ node.dup.tap do |i|
178
+ i.data = reverse_transfer_visit(i.data, type, state)
179
+ end
180
+ when Interscript::Node::Item::CaptureRef
181
+ if type == :from
182
+ node
183
+ elsif state[:right][node.id]
184
+ node
185
+ else
186
+ state[:right][node.id] = true
187
+ state[:left][node.id - 1] or raise "Capture count doesn't match"
188
+ end
189
+ when Interscript::Node::Item::CaptureGroup
190
+ state[:left] << node
191
+ out = Interscript::Node::Item::CaptureRef.new(state[:left].length)
192
+ reverse_transfer_visit(node.data, type, state) # Visit but don't care
193
+ out
194
+ else
195
+ raise "Type #{node.class} unhandled!"
196
+ end
197
+ end
198
+
199
+ def ==(other)
200
+ super &&
201
+ self.from == other.from &&
202
+ self.to == other.to &&
203
+ self.before == other.before &&
204
+ self.after == other.after &&
205
+ self.not_before == other.not_before &&
206
+ self.not_after == other.not_after &&
207
+ self.priority == other.priority
208
+ end
209
+
52
210
  def inspect
53
211
  out = "sub "
54
212
  params = []
55
213
  params << @from.inspect
56
- if @to == :upcase
57
- params << "upcase"
214
+ if Symbol === @to
215
+ params << @to.to_s
58
216
  else
59
217
  params << @to.inspect
60
218
  end
219
+ params << "reverse_run: #{@reverse_run.inspect}" unless @reverse_run.nil?
220
+
61
221
  params << "before: #{@before.inspect}" if @before
62
222
  params << "after: #{@after.inspect}" if @after
63
223
  params << "not_before: #{@not_before.inspect}" if @not_before
64
224
  params << "not_after: #{@not_after.inspect}" if @not_after
225
+
65
226
  params << "priority: #{@priority.inspect}" if @priority
66
227
  out << params.join(", ")
67
228
  end
@@ -1,9 +1,10 @@
1
1
  class Interscript::Node::Stage < Interscript::Node::Group::Sequential
2
2
  attr_accessor :name, :doc_name
3
3
 
4
- def initialize name = :main
4
+ def initialize(name = :main, reverse_run: nil, doc_name: nil)
5
5
  @name = name
6
- super()
6
+ @doc_name = doc_name
7
+ super(reverse_run: reverse_run)
7
8
  end
8
9
 
9
10
  def to_hash
@@ -12,6 +13,23 @@ class Interscript::Node::Stage < Interscript::Node::Group::Sequential
12
13
  :children => @children.map{|x| x.to_hash} }
13
14
  end
14
15
 
16
+ def reverse
17
+ @reverse ||= begin
18
+ self.class.new(name,
19
+ doc_name: Interscript::Node::Document.reverse_name(doc_name),
20
+ reverse_run: reverse_run.nil? ? nil : !reverse_run
21
+ ).tap do |r|
22
+ r.children = self.children.reverse.map(&:reverse)
23
+ end
24
+ end
25
+ end
26
+
27
+ def ==(other)
28
+ super &&
29
+ self.name == other.name &&
30
+ self.reverse_run == other.reverse_run
31
+ end
32
+
15
33
  def inspect
16
34
  name = "(#{@name})" if @name != :main
17
35
  "stage#{name} {\n#{super}\n}"
@@ -8,6 +8,14 @@ class Interscript::Node::Tests < Interscript::Node
8
8
  @data << pair
9
9
  end
10
10
 
11
+ def reverse
12
+ self.class.new(data.map(&:reverse))
13
+ end
14
+
15
+ def ==(other)
16
+ super && self.data == other.data
17
+ end
18
+
11
19
  def to_hash
12
20
  { :class => self.class.to_s,
13
21
  :data => @data }
@@ -22,6 +22,10 @@ class Interscript::Stdlib
22
22
  ! %i[none space].include?(a)
23
23
  end
24
24
 
25
+ def self.boundary_like_alias?(a)
26
+ %i[line_start line_end string_start string_end boundary non_word_boundary].include?(a)
27
+ end
28
+
25
29
  @treecache = {}
26
30
 
27
31
  def self.parallel_regexp_compile(subs_hash)
@@ -167,7 +171,20 @@ class Interscript::Stdlib
167
171
  end
168
172
 
169
173
  def self.available_functions
170
- %i[title_case downcase compose decompose separate secryst]
174
+ %i[title_case downcase compose decompose separate unseparate secryst]
175
+ end
176
+
177
+ def self.reverse_function
178
+ {
179
+ title_case: :downcase, # Those two are best-effort,
180
+ downcase: :title_case, # but probably wrong.
181
+
182
+ compose: :decompose,
183
+ decompose: :compose,
184
+
185
+ separate: :unseparate,
186
+ unseparate: :separate
187
+ }
171
188
  end
172
189
 
173
190
  module Functions
@@ -177,8 +194,13 @@ class Interscript::Stdlib
177
194
  output
178
195
  end
179
196
 
180
- def self.downcase(output, _:nil)
181
- output.downcase
197
+ def self.downcase(output, word_separator: nil)
198
+ if word_separator
199
+ output = output.gsub(/^(.)/, &:downcase)
200
+ output = output.gsub(/#{word_separator}(.)/, &:downcase) unless word_separator == ''
201
+ else
202
+ output.downcase
203
+ end
182
204
  end
183
205
 
184
206
  def self.compose(output, _:nil)
@@ -193,6 +215,10 @@ class Interscript::Stdlib
193
215
  output.split("").join(separator)
194
216
  end
195
217
 
218
+ def self.unseparate(output, separator: " ")
219
+ output.split(separator).join("")
220
+ end
221
+
196
222
  @secryst_models = {}
197
223
  def self.secryst(output, model:)
198
224
  require "secryst" rescue nil # Try to load secryst, but don't fail hard if not possible.
@@ -0,0 +1,39 @@
1
+ module Interscript::Utils
2
+ module Helpers
3
+ def document name=nil, &block
4
+ $example_id ||= 0
5
+ $example_id += 1
6
+ name ||= "example-#{$example_id}"
7
+
8
+ Interscript::DSL::Document.new(name, &block).node.tap do |i|
9
+ $documents ||= {}
10
+ $documents[name] = i
11
+ end
12
+ end
13
+
14
+ def stage &block
15
+ document {
16
+ stage(&block)
17
+ }
18
+ end
19
+ end
20
+ end
21
+
22
+ class Interscript::Node::Document
23
+ def call(str, stage=:main, compiler=$compiler || Interscript::Interpreter, **kwargs)
24
+ compiler.(self).(str, stage, **kwargs)
25
+ end
26
+ end
27
+
28
+ module Interscript::DSL
29
+ class << self
30
+ alias original_parse parse
31
+ def parse(map_name, **kwargs)
32
+ if $documents && $documents[map_name]
33
+ $documents[map_name]
34
+ else
35
+ original_parse(map_name, **kwargs)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module Interscript
2
- VERSION = "2.1.0b6"
2
+ VERSION = "2.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: interscript
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0b6
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -26,6 +26,20 @@ dependencies:
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: interscript-maps
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: text
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -70,6 +84,7 @@ files:
70
84
  - lib/interscript/compiler.rb
71
85
  - lib/interscript/compiler/javascript.rb
72
86
  - lib/interscript/compiler/ruby.rb
87
+ - lib/interscript/detector.rb
73
88
  - lib/interscript/dsl.rb
74
89
  - lib/interscript/dsl/aliases.rb
75
90
  - lib/interscript/dsl/document.rb
@@ -104,6 +119,7 @@ files:
104
119
  - lib/interscript/node/stage.rb
105
120
  - lib/interscript/node/tests.rb
106
121
  - lib/interscript/stdlib.rb
122
+ - lib/interscript/utils/helpers.rb
107
123
  - lib/interscript/utils/regexp_converter.rb
108
124
  - lib/interscript/version.rb
109
125
  - lib/interscript/visualize.rb
@@ -129,9 +145,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
129
145
  version: 2.3.0
130
146
  required_rubygems_version: !ruby/object:Gem::Requirement
131
147
  requirements:
132
- - - ">"
148
+ - - ">="
133
149
  - !ruby/object:Gem::Version
134
- version: 1.3.1
150
+ version: '0'
135
151
  requirements: []
136
152
  rubygems_version: 3.1.6
137
153
  signing_key: