interscript 2.1.0b5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe8810808a1b92abfe576f5012d56dd04a45adc43339810b5cd4aed1f51f0fb5
4
- data.tar.gz: 6249485428951e59179ef502375156f91cc854ded308d12be5b2f84c9713d90e
3
+ metadata.gz: 884aa8d7c4db0c885f92a62e23019911692fa2990894c02b11fc75251d2a3546
4
+ data.tar.gz: d02abe8ffff5df459c7c71bdf999555efc4cd8545efe196e7923977836dad157
5
5
  SHA512:
6
- metadata.gz: 5ce43d1b114d6288798832c3f446d6479641ff5381e392ff5cd3c4d3303d6383b77be5c3ae2881faf45b1fd057f41120647d4f97a1ee23b47fab209ac5d9cb0c
7
- data.tar.gz: 664c41c9e684516dd975154b835aa16ba2663e4690680eeb5ad1780c0e9c25adcfd68ac4aeb4d3fc9af60cded971b96640cc594f348b356389234ed714515783
6
+ metadata.gz: c9cb6efe33c22ab5d7374c8f369493a8b0f1d98144e96fcf266350eae533eabb1fe389e91d33d4373c7af0107dcfafc63f35765e9d7b4233661535d6d8bb18fc
7
+ data.tar.gz: 99febada62f66496eb18036fceaa76a029096f2aab96331793c5b183c38c53701201f5161d8fed2536dbd7cc41098d66167986ae6acb363faa1a446f1d1ae4e6
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.0b5"
2
+ VERSION = "2.2.0"
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.0b5
4
+ version: 2.2.0
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: