tcl-ruby 0.1.0 → 0.1.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
  SHA1:
3
- metadata.gz: 05002f0d80add9496fd333278753cb393c1cb467
4
- data.tar.gz: cc478beb9d6bb5c8562d089931570efe36fb4c70
3
+ metadata.gz: 07b644f73103f389ffc7575edd8a550b330f6774
4
+ data.tar.gz: 55726309ea62a15b0d77e67c5f6462b44b561303
5
5
  SHA512:
6
- metadata.gz: ec9711ba1d51886f8ae015af255b5f40bb792278d23f8c1d8714b8ed6cc5ce6a90e9632f40390777e3c3c62e346ea33eb59d3bc68e51d0ccb088ef53f0f0be30
7
- data.tar.gz: a932d6e3cbceba76a77fc4250130ab62f6d834a7167bdf63e6981e545d703488a92c0d9e6462c2d43b8952de780cfcbea17af56500e2789648e1b2fedcc81be8
6
+ metadata.gz: 89b1f5f58c6058715c363f13b10825c4e03592e67ea176abdcc5b4f51f7f6d46a5aa5984fe0b55684d80683eea2e9ded573b479a906b8b4d971fccabe839f3a0
7
+ data.tar.gz: ebc2b1bf14efde9a4f53b76ca863847b08bd1fefecd2d1ef22c804f65a85be6a1150b20caba4371357c3371b1e4e14daa47d582edca01623d80d8bdf8bdb29d0
data/.rubocop.yml CHANGED
@@ -1,3 +1,11 @@
1
1
  Lint/Debugger:
2
2
  Exclude:
3
3
  - bin/console
4
+
5
+ # Missing top level class documentation comment.
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ # Avoid the use of double negation (!!)
10
+ Style/DoubleNegation:
11
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.2.0
4
+ - 2.1.0
4
5
  before_install: gem install bundler -v 1.11.2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Code Climate](https://codeclimate.com/github/kiyonori-matsumoto/tcl-ruby/badges/gpa.svg)](https://codeclimate.com/github/kiyonori-matsumoto/tcl-ruby) [![Build Status](https://travis-ci.org/kiyonori-matsumoto/tcl-ruby.svg?branch=master)](https://travis-ci.org/kiyonori-matsumoto/tcl-ruby)
1
+ [![Code Climate](https://codeclimate.com/github/kiyonori-matsumoto/tcl-ruby/badges/gpa.svg)](https://codeclimate.com/github/kiyonori-matsumoto/tcl-ruby) [![Build Status](https://travis-ci.org/kiyonori-matsumoto/tcl-ruby.svg?branch=master)](https://travis-ci.org/kiyonori-matsumoto/tcl-ruby) [![Gem Version](https://badge.fury.io/rb/tcl-ruby.svg)](https://badge.fury.io/rb/tcl-ruby)
2
2
 
3
3
  # Tcl::Ruby
4
4
 
@@ -23,19 +23,22 @@ Or install it yourself as:
23
23
  ## Usage
24
24
 
25
25
  Instanciate Interpreter
26
+
26
27
  ```ruby
27
- f = Interpreter.new
28
+ f = Tcl::Ruby::Interpreter.new
28
29
  ```
29
30
  Parse commands you want to
31
+
30
32
  ```ruby
31
33
  f.parse('llength {A B C D}')
32
- => 4
34
+ # => 4
33
35
  ```
34
36
  You can get variables with Interpreter#variables
37
+
35
38
  ```ruby
36
39
  f.parse('set A 123')
37
40
  f.variables('A')
38
- => '123'
41
+ # => '123'
39
42
  ```
40
43
 
41
44
  ## Development
data/lib/tcl/ruby.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require 'tcl/ruby/command.rb'
2
2
  require 'tcl/ruby/error.rb'
3
3
  require 'tcl/ruby/list_array.rb'
4
+ require 'tcl/ruby/option_parser.rb'
4
5
  require 'tcl/ruby/parser.rb'
6
+ require 'tcl/ruby/string.rb'
5
7
  require 'tcl/ruby/util.rb'
8
+ require 'tcl/ruby/commands/array.rb'
6
9
  require 'tcl/ruby/commands/basic.rb'
10
+ require 'tcl/ruby/commands/inout.rb'
7
11
  require 'tcl/ruby/commands/list.rb'
8
- require 'tcl/ruby/commands/array.rb'
12
+ require 'tcl/ruby/commands/string.rb'
@@ -1,52 +1,96 @@
1
+ require 'strscan'
2
+
1
3
  module Tcl
2
4
  module Ruby
3
5
  class Interpreter
4
- def command(arg)
5
- return @prev if arg[0][0] == '#'
6
- # return previous command result when comment statement executed
7
- arg.map! { |e| replace(e) }
8
- arg.to_string
9
- name = arg[0]
10
- if @proc.key?(name)
11
- exec_proc(arg[1..-1], @proc[name])
12
- elsif @hooks.key?(name)
13
- @hooks[name].call(arg[1..-1])
14
- elsif respond_to?("___#{name}", true)
15
- @prev = send("___#{name}", arg[1..-1])
16
- else
17
- raise(CommandError, "command not found, #{name}")
6
+ private
7
+
8
+ def command(cmds)
9
+ ret = nil
10
+ cmds.each do |arg|
11
+ next if arg.empty?
12
+ arg.replace(&method(:replace))
13
+ name = "___#{arg[0]}"
14
+ if @proc.key?(arg[0]) then ret = exec_proc(arg[1..-1], @proc[arg[0]])
15
+ elsif @hooks.key?(arg[0]) then ret = @hooks[arg[0]].call(arg[1..-1])
16
+ elsif respond_to?(name, true)
17
+ begin
18
+ ret = send(name, *arg[1..-1])
19
+ rescue ArgumentError => e
20
+ raise(TclArgumentError, "#{arg[0]}: #{e.message}")
21
+ end
22
+ else
23
+ raise(CommandError, "command not found, #{arg[0]}")
24
+ end
18
25
  end
26
+ ret
19
27
  end
20
28
 
21
29
  def replace(list)
22
- return list if list[0] == '{'
30
+ # replace commands
31
+ list = replace_commands(list)
32
+
23
33
  # replace variable
24
- l = list.gsub(/\$\{(.+?)\}|\$([\w()]+)/) do
25
- v = Regexp.last_match(Regexp.last_match(1) ? 1 : 2)
26
- h = vv = nil
27
- if (m = v.match(/\((\w+)\)\z/))
28
- h = m[1]
29
- vv = v
30
- v = vv.sub(/\((\w+)\)\z/, '')
34
+ replace_variable(list)
35
+ end
36
+
37
+ def replace_commands(list)
38
+ l = list.dup
39
+ s = StringScanner.new(l)
40
+ buffer = nil
41
+ depth = 0
42
+ until s.empty?
43
+ if s.scan(/\\./m)
44
+ buffer << s[0] if buffer
45
+ elsif s.scan(/\[/)
46
+ if depth == 0
47
+ pos = s.pos - 1
48
+ buffer = ''
49
+ end
50
+ depth += 1
51
+ buffer << s[0]
52
+ elsif s.scan(/\]/)
53
+ depth -= 1
54
+ buffer << s[0]
55
+ if depth == 0
56
+ l[pos, buffer.length] = parse(buffer[1..-2]).to_s
57
+ s.string = l
58
+ buffer = nil
59
+ end
60
+ elsif s.scan(/[^\[\]\\]+/m)
61
+ buffer << s[0] if buffer
31
62
  end
32
- raise(TclVariableNotFoundError, v.to_s, 'no such variable') unless
63
+ end
64
+ raise(ParseError, 'unmatched brackets') if depth != 0
65
+ l
66
+ end
67
+
68
+ def replace_variable(elem)
69
+ elem.gsub!(/\$\{(.+?)\}|\$(\w+\([^\s)]+\))|\$(\w+)/) do |_|
70
+ v = Regexp.last_match(1) || Regexp.last_match(2) ||
71
+ Regexp.last_match(3)
72
+ h = nil
73
+ vv = v.dup
74
+ v.sub!(/\(([^\s)]+)\)\z/) { |_m| h = Regexp.last_match(1); '' }
75
+ raise TclVariableNotFoundError.new(v, 'no such variable') unless
33
76
  @variables.key?(v)
34
- if h
35
- raise(TclVariableNotFoundError, vv.to_s, "variable isn't array") unless @variables[v].is_a?(Hash)
36
- @variables[v][h].to_s
77
+ if h # variable specified is hash
78
+ raise TclVariableNotFoundError.new(vv, "variable isn't array") unless @variables[v].is_a?(Hash)
79
+ h = replace_variable(h) # analyze var_string on parenthesis
80
+ @variables[v][h]
37
81
  else
38
- raise(TclVariableNotFoundError, v.to_s, 'variable is array') if @variables[v].is_a?(Hash)
82
+ raise TclVariableNotFoundError.new(v, 'variable is array') if
83
+ @variables[v].is_a?(Hash)
39
84
  @variables[v]
40
85
  end
41
86
  end
42
- # replace commands
43
- l = l.gsub(/\[(.+)\]/) { parse(Regexp.last_match(1)) }
87
+ elem
44
88
  end
45
89
 
46
90
  def exec_proc(arg, proc_info)
47
91
  proc_arg = parse(proc_info[0], true)
48
92
  raise(TclArgumentError, proc_arg.to_s) if proc_arg.size != arg.size
49
- @variables[:___global].each do |v| # FIXME: Buggy
93
+ @variables[:___global].each do |v| # backup globals
50
94
  @global[v] = @variables[v]
51
95
  end if @variables.key?(:___global)
52
96
  @v_stack.push(@variables)
@@ -57,10 +101,13 @@ module Tcl
57
101
  ret = catch(:return) do
58
102
  parse(proc_info[1])
59
103
  end
60
- @variables[:___global].each do |v| # FIXME: Buggy
104
+ @variables[:___global].each do |v| # write back
61
105
  @global[v] = @variables[v]
62
106
  end if @variables.key?(:___global)
63
107
  @variables = @v_stack.pop
108
+ @variables[:___global].each do |v| # re-copy global
109
+ @variables[v] = @global[v]
110
+ end if @variables.key?(:___global)
64
111
  ret
65
112
  end
66
113
  end
@@ -3,28 +3,23 @@ module Tcl
3
3
  class Interpreter
4
4
  private
5
5
 
6
- def ___array(arg)
7
- send("___array_#{arg[0]}", arg[1..-1])
6
+ def ___array(*arg)
7
+ send("___array_#{arg[0]}", *arg[1..-1])
8
+ rescue ArgumentError => e
9
+ raise(TclArgumentError, "array #{arg[0]}: #{e.message}")
8
10
  end
9
11
 
10
- def ___array_set(arg)
11
- name = arg[0]
12
+ def ___array_set(name, list)
12
13
  raise(CommandError, "#{name} is not array") unless
13
14
  @variables[name].is_a?(Hash) || !@variables.key?(name)
14
- l = parse(arg[1], true)
15
- raise(TclArgumentError, 'list must have an even number of elements') unless
16
- l.size.even?
15
+ l = parse(list, true).to_h
17
16
  @variables[name] ||= {}
18
- @variables[name].merge!(Hash[*l])
17
+ @variables[name].merge!(l)
19
18
  end
20
19
 
21
- def ___array_unset(arg)
22
- raise(TclArgumentError, 'array unset arrayName ?pattern?') unless
23
- (1..2).cover?(arg.size)
24
- name = arg[0]
20
+ def ___array_unset(name, pattern = nil)
25
21
  return '' unless @variables[name].is_a?(Hash)
26
- if arg.size == 2
27
- pattern = arg[1]
22
+ if pattern
28
23
  @variables[name].delete(pattern)
29
24
  else
30
25
  @variables.delete(name)
@@ -32,13 +27,9 @@ module Tcl
32
27
  ''
33
28
  end
34
29
 
35
- def ___array_get(arg)
36
- raise(TclArgumentError, 'array get arrayName ?pattern?') unless
37
- (1..2).cover?(arg.size)
38
- name = arg[0]
30
+ def ___array_get(name, pattern = nil)
39
31
  return '' unless @variables[name].is_a?(Hash)
40
- if arg.size == 2
41
- pattern = arg[1]
32
+ if pattern
42
33
  return '' unless @variables[name].key?(pattern)
43
34
  "#{pattern} #{@variables[name][pattern]}"
44
35
  else
@@ -46,9 +37,7 @@ module Tcl
46
37
  end
47
38
  end
48
39
 
49
- def ___array_exists(arg)
50
- raise(TclArgumentError, 'array exists arrayName') unless arg.size == 1
51
- name = arg[0]
40
+ def ___array_exists(name)
52
41
  @variables[name].is_a?(Hash) ? '1' : '0'
53
42
  end
54
43
  end
@@ -3,18 +3,27 @@ module Tcl
3
3
  class Interpreter
4
4
  private
5
5
 
6
- def ___set(arg)
7
- raise(TclArgumentError, 'set variable ?newValue?') unless
8
- (1..2).cover? arg.size
9
- @variables[arg[0]] = arg[1] if arg.size == 2
10
- @variables[arg[0]]
6
+ def ___set(var_name, value = nil)
7
+ if (m = var_name.match(/(\w+)\((\S+?)\)/))
8
+ ___array_set(m[1], "{#{m[2]}} {#{value}}") if value
9
+ ___array_get(m[1], "{#{m[2]}}")
10
+ else
11
+ @variables[var_name] = value if value
12
+ @variables[var_name]
13
+ end
11
14
  end
12
15
 
13
- def ___expr(arg)
16
+ def ___expr(*arg)
14
17
  eval arg.join('')
15
18
  end
16
19
 
17
- def ___if(arg)
20
+ def ___eval(*arg)
21
+ raise(TclArgumentError, 'wrong number of arguments(0 for 1..)') if
22
+ arg.empty?
23
+ parse(___concat(*arg))
24
+ end
25
+
26
+ def ___if(*arg)
18
27
  arg.delete('then')
19
28
  arg.delete('else')
20
29
  arg.delete('elseif')
@@ -27,20 +36,19 @@ module Tcl
27
36
  nil
28
37
  end
29
38
 
30
- def ___for(arg)
31
- raise(CommandError, 'for start test next body') unless arg.size == 4
32
- parse(arg[0])
39
+ def ___for(start, tst, nxt, body)
40
+ parse(start)
33
41
  catch(:break) do
34
- while eval(replace(arg[1]))
42
+ while eval(replace(tst))
35
43
  catch(:continue) do
36
- parse(arg[3])
44
+ parse(body)
37
45
  end
38
- parse(arg[2])
46
+ parse(nxt)
39
47
  end
40
48
  end
41
49
  end
42
50
 
43
- def ___foreach(arg)
51
+ def ___foreach(*arg)
44
52
  varlist = []
45
53
  list = []
46
54
  while arg[2]
@@ -48,26 +56,22 @@ module Tcl
48
56
  list << parse(arg[1], true)
49
57
  arg.shift(2)
50
58
  end
51
- body = arg[0]
52
59
  catch(:break) do
53
60
  while list.any?(&:any?)
54
61
  # assign variables
55
62
  varlist.each_with_index do |v, idx|
56
- v.each do |vv|
57
- @variables[vv] = list[idx].shift || ''
58
- end
63
+ v.each { |vv| @variables[vv] = list[idx].shift || '' }
59
64
  end
60
65
  catch(:continue) do
61
- parse(body)
66
+ parse(arg[0])
62
67
  end
63
68
  end
64
69
  end
65
70
  end
66
71
 
67
- def ___while(arg)
68
- body = arg[1]
72
+ def ___while(tst, body)
69
73
  catch(:break) do
70
- while eval(replace(arg[0]))
74
+ while eval(replace(tst))
71
75
  catch(:continue) do
72
76
  parse(body)
73
77
  end
@@ -75,41 +79,38 @@ module Tcl
75
79
  end
76
80
  end
77
81
 
78
- def ___break(_arg)
82
+ def ___break
79
83
  throw :break
80
84
  end
81
85
 
82
- def ___continue(_arg)
86
+ def ___continue
83
87
  throw :continue
84
88
  end
85
89
 
86
- def ___return(arg)
87
- throw(:return, arg[0])
90
+ def ___return(val = nil)
91
+ throw(:return, val)
88
92
  end
89
93
 
90
- def ___incr(arg)
91
- raise(TclArgumentError, 'incr varName ?increment?') unless (1..2).cover?(arg.size)
92
- incr = (arg[1]) ? arg[1].to_i : 1
93
- @variables[arg[0]] = ((@variables[arg[0]] || 0).to_i + incr).to_s
94
+ def ___incr(var_name, increment = 1)
95
+ @variables[var_name] = ((@variables[var_name] || 0).to_i +
96
+ increment.to_i).to_s
94
97
  end
95
98
 
96
- def ___puts(arg)
97
- puts arg[0]
99
+ def ___format(str, *args)
100
+ str % args
98
101
  end
99
102
 
100
- def ___proc(arg)
101
- raise(TclArgumentError, 'proc name args body') unless arg.size == 3
102
- @proc[arg[0]] = arg[1..2]
103
+ def ___proc(name, args, body)
104
+ @proc[name] = [args, body]
103
105
  end
104
106
 
105
- def ___global(arg)
107
+ def ___global(*arg)
106
108
  @variables[:___global] ||= []
107
109
  arg.each do |v|
108
110
  @variables[:___global] << v
109
111
  @variables[v] = @global[v] if @global
110
112
  end
111
113
  end
112
- # define_method('___#') { |_p| nil }
113
114
  end
114
115
  end
115
116
  end
@@ -0,0 +1,54 @@
1
+ module Tcl
2
+ module Ruby
3
+ class Interpreter
4
+ private
5
+
6
+ def ___open(filename, access = 'r', permission = 0744)
7
+ begin
8
+ fp = open(filename, access, permission)
9
+ rescue
10
+ raise(CommandError, "File #{filename} cannot open")
11
+ end
12
+ k = "file#{fp.object_id.to_s(36)}"
13
+ @files[k] = fp
14
+ k
15
+ end
16
+
17
+ def ___close(id)
18
+ fp = get_fp(id, delete: true)
19
+ fp.close
20
+ end
21
+
22
+ def ___gets(id, var_name = nil)
23
+ fp = get_fp(id)
24
+ str = fp.gets
25
+ if var_name
26
+ @variables[var_name] = str
27
+ str.length
28
+ else
29
+ str
30
+ end
31
+ end
32
+
33
+ def ___puts(*arg)
34
+ opts = {}
35
+ opts = OptionParser.parse(['nonewline'], arg) if arg.size != 1
36
+ __puts_body(*arg, opts)
37
+ end
38
+
39
+ def __puts_body(id = nil, val, nonewline: false)
40
+ fp = id ? get_fp(id) : $stdout
41
+ if nonewline
42
+ fp.print val
43
+ else
44
+ fp.puts val
45
+ end
46
+ end
47
+
48
+ def ___eof(id)
49
+ fp = get_fp(id)
50
+ fp.eof? ? '1' : '0'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,69 +3,113 @@ module Tcl
3
3
  class Interpreter
4
4
  private
5
5
 
6
- def ___llength(arg)
7
- raise(CommandError, 'llength list') if arg.size != 1
8
- parse(arg[0], true).size
6
+ def ___concat(*arg)
7
+ arg.map(&:strip).join(' ')
9
8
  end
10
9
 
11
- def ___list(arg)
12
- arg.join(' ').to_s
10
+ def ___llength(list)
11
+ parse(list, true).size.to_s
13
12
  end
14
13
 
15
- def ___lindex(arg)
16
- if arg[1].nil?
17
- arg[0]
18
- elsif arg[1] == ''
19
- arg[0]
20
- else
21
- l = arg[0]
22
- arg[1..-1].each do |as|
23
- parse(as, true).each do |a|
24
- l = parse(l, true)
25
- a.gsub!(/end/, (l.size - 1).to_s)
26
- pos = eval(a)
27
- if (0...l.size).cover?(pos)
28
- l = l[pos]
29
- else
30
- return ''
31
- end
32
- end
14
+ def ___list(*arg)
15
+ ListArray.new(arg).to_list
16
+ end
17
+
18
+ def ___lindex(list, *indexes)
19
+ return list if indexes.nil? || indexes.empty?
20
+ l = list
21
+ indexes.each do |as|
22
+ parse(as, true).each do |a|
23
+ l = parse(l, true)
24
+ pos = parse_index_format(a)
25
+ l = l[pos]
33
26
  end
34
- l
35
27
  end
28
+ l || ''
36
29
  end
37
30
 
38
- def ___join(arg)
39
- raise(CommandError, 'join list joinString?') unless (1..2).cover? arg.size
40
- separator = arg[1] || ' '
41
- parse(arg[0], true).join(separator)
31
+ def ___join(list, separator = ' ')
32
+ parse(list, true).join(separator)
42
33
  end
43
34
 
44
- def ___linsert(arg)
45
- raise(CommandError, 'linsert list insertposition elements') unless arg.size >= 3
46
- l = parse(arg[0], true)
47
- l.insert(arg[1].to_i, *arg[2..-1])
48
- # "{#{l.join(' ')}}"
35
+ def ___linsert(list, index, element, *elements)
36
+ l = parse(list, true)
37
+ l.insert(parse_index_format(index), element, *elements)
49
38
  l.to_list
50
39
  end
51
40
 
52
- def ___lrange(arg)
53
- raise(CommandError, 'lrange list first last') unless arg.size == 3
54
- first = arg[1].to_i
55
- first = 0 if first < 0
56
- last = arg[2].to_i
57
- l = parse(arg[0], true)
58
- if first <= last
59
- l[first..last].to_list
41
+ def ___lrange(list, first, last)
42
+ first = parse_index_format first
43
+ last = parse_index_format last
44
+ l = parse(list, true)
45
+ l[first..last].to_list
46
+ end
47
+
48
+ def ___lappend(var_name, *values)
49
+ l = parse(variables(var_name), true)
50
+ l.push(*values)
51
+ @variables[var_name] = l.to_list
52
+ end
53
+
54
+ def ___lsort(*args)
55
+ opts = {}
56
+ if args.size > 1
57
+ opts = OptionParser.parse(
58
+ ['ascii', 'dictionary', 'integer', 'real', 'command?', 'increasing',
59
+ 'decreasing', 'index?', 'unique'], args)
60
+ end
61
+ __lsort_body(*args, opts)
62
+ end
63
+
64
+ def __lsort_body(list, opts)
65
+ l = parse(list, true)
66
+ func = if opts['directionary'] then :upcase
67
+ elsif opts['integer'] then :to_i
68
+ elsif opts['real'] then :to_f
69
+ else :to_s
70
+ end
71
+ if opts['index']
72
+ index = parse_index_format(opts['index'])
73
+ sort_func = -> (x) { parse(x, true)[index].send(func) }
60
74
  else
61
- ''
75
+ sort_func = func
62
76
  end
77
+ l.uniq!(&sort_func) if opts['unique']
78
+ l.sort_by!(&sort_func)
79
+ l.reverse! if opts['decreasing']
80
+ ListArray.new(l).to_list
63
81
  end
64
82
 
65
- def ___lappend(arg)
66
- l = parse(variables(arg[0]), true)
67
- l.push(*arg[1..-1])
68
- @variables[arg[0]] = l.to_list
83
+ def ___lsearch(*args)
84
+ opts = {}
85
+ if args.size > 2
86
+ opts = OptionParser.parse(
87
+ ['all', 'ascii', 'decreasing', 'dictionary', 'exact', 'glob',
88
+ 'increasing', 'inline', 'integer', 'not', 'real', 'regexp',
89
+ 'sorted', 'start?'], args)
90
+ end
91
+ __lsearch_body(*args, opts)
92
+ end
93
+
94
+ def __lsearch_body(list, pattern, opts)
95
+ l = parse(list, true)
96
+ func = case [opts['all'], opts['inline']]
97
+ when [true, true] then :select
98
+ when [true, nil] then :find_index_all
99
+ when [nil, true] then :find
100
+ else :index
101
+ end
102
+ block = if opts['regexp'] then -> (b, x) { !!(x =~ /#{pattern}/) == b }
103
+ elsif opts['exact'] then -> (b, x) { (x == pattern) == b }
104
+ else
105
+ pattern.gsub!(/\*/, '.*')
106
+ pattern.tr!('?', '.')
107
+ pattern.gsub!(/\\(.)/) { Regexp.last_match(1) }
108
+ -> (b, x) { !!(x =~ /\A#{pattern}\z/) == b }
109
+ end.curry
110
+
111
+ v = l.send(func, &block.call(!opts['not']))
112
+ ListArray.new(v).to_list
69
113
  end
70
114
  end
71
115
  end
@@ -0,0 +1,118 @@
1
+ require 'strscan'
2
+
3
+ module Tcl
4
+ module Ruby
5
+ class Interpreter
6
+ private
7
+
8
+ def ___string(*arg)
9
+ send("___string_#{arg[0]}", *arg[1..-1])
10
+ rescue ArgumentError => e
11
+ raise(TclArgumentError, "string #{arg[0]}: #{e.message}")
12
+ end
13
+
14
+ def ___string_length(str)
15
+ str.length
16
+ end
17
+
18
+ def ___string_equal(*arg)
19
+ opts = {}
20
+ if arg.size != 2
21
+ opts = OptionParser.parse(['nocase', 'length?'], arg)
22
+ raise(TclArgumentError, 'string equal ?-nocase? ?-length int? string1 string2') unless arg.size == 2
23
+ end
24
+ __string_equal_body(*arg, opts)
25
+ end
26
+
27
+ def __string_equal_body(str1, str2, opts = {})
28
+ if opts.key?('nocase')
29
+ str1 = str1.upcase
30
+ str2 = str2.upcase
31
+ end
32
+ if opts.key?('length')
33
+ range = (0...opts['length'].to_i)
34
+ (str1[range] == str2[range]) ? '1' : '0'
35
+ else
36
+ (str1 == str2) ? '1' : '0'
37
+ end
38
+ end
39
+
40
+ def ___string_index(str, index)
41
+ str[parse_index_format(index)]
42
+ end
43
+
44
+ def ___string_map(*arg)
45
+ opts = {}
46
+ if arg.size != 2
47
+ opts = OptionParser.parse(['nocase'], arg)
48
+ raise(TclArgumentError, 'string map ?-nocase? charMap string') unless arg.size == 2
49
+ end
50
+ __string_map_body(*arg, opts)
51
+ end
52
+
53
+ def __string_map_body(char_map, str, opts = {})
54
+ h = parse(char_map, true).to_h
55
+ scan = StringScanner.new str
56
+ rstr = ''
57
+ until scan.empty?
58
+ r = h.each do |k, v|
59
+ next unless (opts['nocase'] && scan.scan(/#{k}/i)) ||
60
+ scan.scan(/#{k}/)
61
+ rstr << v
62
+ break false
63
+ end
64
+ rstr << scan.scan(/./) if r
65
+ end
66
+ rstr
67
+ end
68
+
69
+ def ___string_range(str, first, last)
70
+ first = parse_index_format first
71
+ last = parse_index_format last
72
+ str[first..last]
73
+ end
74
+
75
+ def ___string_repeat(str, count)
76
+ str * count.to_i
77
+ end
78
+
79
+ def ___string_tolower(str, first = 0, last = nil)
80
+ last ||= str.size
81
+ __string_tosomething(str, first, last, :downcase)
82
+ end
83
+
84
+ def __string_tosomething(str, first, last, modifier)
85
+ first = parse_index_format first
86
+ last = parse_index_format last
87
+ str[first..last] = str[first..last].send(modifier)
88
+ str
89
+ end
90
+
91
+ def ___string_totitle(str, first = 0, last = -1)
92
+ __string_tosomething(str, first, last, :capitalize)
93
+ end
94
+
95
+ def ___string_toupper(str, first = 0, last = -1)
96
+ __string_tosomething(str, first, last, :upcase)
97
+ end
98
+
99
+ def ___string_trim(str, chars = '\s')
100
+ __string_trimmer(str, chars, 3)
101
+ end
102
+
103
+ def ___string_trimleft(str, chars = '\s')
104
+ __string_trimmer(str, chars, 1)
105
+ end
106
+
107
+ def ___string_trimright(str, chars = '\s')
108
+ __string_trimmer(str, chars, 2)
109
+ end
110
+
111
+ def __string_trimmer(str, chars, mode)
112
+ str.sub!(/\A[#{chars}]+/, '') if mode & 1 != 0
113
+ str.sub!(/[#{chars}]+\z/, '') if mode & 2 != 0
114
+ str
115
+ end
116
+ end
117
+ end
118
+ end
@@ -4,9 +4,9 @@ module Tcl
4
4
  class ParseError < TclError; end
5
5
  class CommandError < TclError; end
6
6
  class TclArgumentError < TclError
7
- def initialize(msg)
8
- super("wrong \# args: should be\"#{msg}\"")
9
- end
7
+ # def initialize(msg)
8
+ # super("wrong \# args: should be\"#{msg}\"")
9
+ # end
10
10
  end
11
11
  class TclVariableNotFoundError < TclError
12
12
  def initialize(var, type = '')
@@ -1,33 +1,77 @@
1
+ require 'forwardable'
2
+
1
3
  module Tcl
2
4
  module Ruby
3
5
  class ListArray
6
+ extend Forwardable
7
+
8
+ def_delegators :@ary, :find, :any?
9
+
4
10
  Array.public_instance_methods(false).each do |name|
5
- next if name == '<<'
6
11
  define_method(name) do |*args, &block|
7
12
  @ary.send(name, *args, &block)
8
13
  end
9
14
  end
10
15
 
11
- def initialize
16
+ def uniq!(&block)
17
+ @ary = @ary.reverse.uniq(&block).reverse
18
+ self
19
+ end
20
+
21
+ def uniq(&block)
22
+ dup.uniq!(&block)
23
+ end
24
+
25
+ def find_index_all
26
+ raise ArgumentError unless block_given?
27
+ r = []
28
+ @ary.each_with_index do |e, idx|
29
+ r << idx.to_s if yield(e)
30
+ end
31
+ r
32
+ end
33
+
34
+ def initialize(ary = [])
35
+ @ary = Array(ary).map(&:to_s)
36
+ @brackets = []
37
+ end
38
+
39
+ def clear
12
40
  @ary = []
41
+ @brackets = []
42
+ end
43
+
44
+ def bracket_add(val)
45
+ @brackets[@ary.size] ||= []
46
+ @brackets[@ary.size] << val
13
47
  end
14
48
 
15
49
  def <<(buffer)
16
50
  @ary << buffer.dup unless buffer.empty?
17
51
  buffer.clear
52
+ buffer.init
18
53
  self
19
54
  end
20
55
 
21
56
  def to_string
22
- @ary.map! { |e| _to_string(e) }
57
+ @ary.map!(&:to_tcl_string)
23
58
  self
24
59
  end
25
60
 
26
61
  def to_list
27
- @ary.map { |e| _to_list(e) }.join(' ')
62
+ @ary.map(&:to_tcl_list).join(' ')
63
+ end
64
+
65
+ def replace
66
+ @ary.size.times do |n|
67
+ @ary[n] = yield(@ary[n]) unless @ary[n].brace?
68
+ end
69
+ # @ary.map! { |m| m.brace? ? m : yield(m) }
70
+ self
28
71
  end
29
72
 
30
73
  def map!(&block)
74
+ @ary.each(&:init)
31
75
  @ary.map!(&block)
32
76
  self
33
77
  end
@@ -48,28 +92,15 @@ module Tcl
48
92
  end
49
93
  end
50
94
 
95
+ def to_h
96
+ raise(TclArgumentError, 'list must have an even number of elements') if
97
+ @ary.size.odd?
98
+ Hash[@ary.each_slice(2).to_a]
99
+ end
100
+
51
101
  protected
52
102
 
53
103
  attr_accessor :ary
54
-
55
- private
56
-
57
- def _to_string(str)
58
- if str[0] == '{' && str[-1] == '}'
59
- str = str[1..-2]
60
- elsif str[0] == '"' && str[-1] == '"'
61
- str = str[1..-2]
62
- end
63
- str
64
- end
65
-
66
- def _to_list(str)
67
- if str == '' || str.match(/\s/)
68
- "{#{str}}"
69
- else
70
- str
71
- end
72
- end
73
104
  end
74
105
  end
75
106
  end
@@ -0,0 +1,28 @@
1
+ module Tcl
2
+ module Ruby
3
+ class OptionParser
4
+ # options_format
5
+ # array of string
6
+ # xxxx or xxxx?
7
+ # ? indicates that value has one argument
8
+ def self.parse(options, args)
9
+ ops = options.map do |e|
10
+ v = e.sub(/\?/, '')
11
+ ["-#{v}", v, e[-1] == '?']
12
+ end
13
+ ret = {}
14
+ loop do
15
+ r = ops.each do |o|
16
+ next unless args[0] == o[0]
17
+ args.shift
18
+ ret[o[1]] = true
19
+ ret[o[1]] = args.shift if o[2]
20
+ break false
21
+ end
22
+ break if r
23
+ end
24
+ ret
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,68 +3,100 @@ require 'strscan'
3
3
  module Tcl
4
4
  module Ruby
5
5
  class Interpreter
6
+ EX_CHAR_CHECK = lambda do |s, t|
7
+ (t && !s.check(/\s|\z/)) || (!t && !s.check(/\s|\z|;/))
8
+ end.curry
9
+
6
10
  def parse(str, to_list = false)
7
- s = StringScanner.new(str)
8
- r = ListArray.new
9
- pdepth = ddepth = bdepth = 0
10
- buffer = ''
11
- ret = nil
12
- until s.empty?
13
- if s.scan(/\\./m)
14
- buffer << s[0] unless s[0][1] =~ /\s/
15
- elsif !to_list && s.scan(/\r\n|\r|\n|;/)
16
- if pdepth == 0 && ddepth == 0 && bdepth == 0
17
- r << buffer
18
- ret = command(r) unless r.empty?
19
- r = ListArray.new
20
- else
21
- buffer << s[0]
22
- end
23
- elsif s.scan(/\s+/)
24
- if pdepth == 0 && ddepth == 0 && bdepth == 0
25
- r << buffer
26
- else
27
- buffer << s[0]
28
- end
29
- else
30
- buffer <<
31
- if s.scan(/{/)
32
- # pdepth += 1 if ddepth == 0
33
- pdepth += 1 if buffer == '' || pdepth != 0
34
- s[0]
35
- elsif s.scan(/}/)
36
- ret = s[0] # pdepth -= 1 if ddepth == 0
37
- pdepth -= 1 if pdepth != 0
38
- raise(ParseError, 'extra characters after close-brace') if
39
- buffer[0] == '{' && pdepth == 0 && !s.check(/\s|\z/)
40
- ret
41
- elsif !to_list && s.scan(/\[/)
42
- bdepth += 1 if pdepth == 0
43
- s[0]
44
- elsif !to_list && s.scan(/\]/)
45
- bdepth -= 1 if pdepth == 0
46
- s[0]
47
- elsif s.scan(/"/)
48
- ret = s[0]
49
- ddepth = 1 - ddepth if buffer == '' || buffer[0] == '"'
50
- raise(ParseError, 'extra characters after close-quote') if
51
- buffer[0] == '"' && ddepth == 0 && !s.check(/\s|\z/)
52
- ret
53
- elsif s.scan(/\S/)
54
- s[0]
55
- else
56
- raise(ParseError, "parse error #{s.rest}")
57
- end
11
+ str2 = (str + "\n").gsub(/\\\n\s*/, ' ') # replace \ & \n & extra ws
12
+ @s = StringScanner.new(str2)
13
+ @list_array = ListArray.new
14
+ @pstack = [] # stack for brace, bracket, quote
15
+ @buffer = '' # ListElement.new('')'' # buffer for list element
16
+ @commands = []
17
+ until @s.empty?
18
+ if @s.scan(/\\./) then @buffer << @s[0]
19
+ elsif @s.scan(/\#/) then parse_comments
20
+ elsif !to_list && @s.scan(/\r\n|\r|\n|;/) then parse_command_ends
21
+ elsif @s.scan(/\s/) then parse_blanks
22
+ elsif @s.scan(/\S/)
23
+ @buffer << @s[0]
24
+ analyze_parentheses(to_list, EX_CHAR_CHECK[@s]) if
25
+ @buffer.parenthesis?
58
26
  end
59
27
  end
60
- r << buffer
61
- raise(ParseError, 'unmatched parenthesises') if
62
- ddepth != 0 || pdepth != 0 || bdepth != 0
63
- if to_list
64
- r.to_string
28
+ check_pstack
29
+ to_list ? @list_array.to_string : command(@commands)
30
+ end
31
+
32
+ private
33
+
34
+ def check_pstack
35
+ raise(ParseError, "unmatched #{@pstack.last}s") if @pstack.any?
36
+ end
37
+
38
+ def parse_command_ends
39
+ bl = @s[0]
40
+ if @pstack.empty?
41
+ @list_array << @buffer
42
+ @list_array.to_string
43
+ @commands << @list_array.dup
44
+ @list_array.clear
65
45
  else
66
- ret = command(r) unless r.empty?
67
- ret
46
+ @buffer << bl
47
+ end
48
+ end
49
+
50
+ def parse_blanks
51
+ @pstack.empty? ? @list_array << @buffer : @buffer << @s[0]
52
+ end
53
+
54
+ def parse_comments
55
+ if @buffer.empty? && @list_array.empty?
56
+ @s.scan(/.+$/)
57
+ else
58
+ @buffer << @s[0]
59
+ end
60
+ end
61
+
62
+ def matched_parentheses(id, has_ex_char)
63
+ if @pstack.last == id
64
+ r = @pstack.pop
65
+ if @pstack.empty? && has_ex_char
66
+ raise(ParseError, "extra characters after close-#{id}")
67
+ end
68
+ r
69
+ end
70
+ end
71
+
72
+ def analyze_braces(to_list, extra_characters_check)
73
+ if @buffer[-1] == '{'
74
+ @pstack.push(:brace) if @pstack.last != :quote
75
+ else
76
+ matched_parentheses(:brace, extra_characters_check[to_list])
77
+ end
78
+ end
79
+
80
+ def analyze_quotes(to_list, extra_characters_check)
81
+ unless matched_parentheses(:quote, extra_characters_check[to_list])
82
+ @pstack.push :quote if @pstack.last != :brace
83
+ end
84
+ end
85
+
86
+ def analyze_brackets
87
+ if @buffer[-1] == '[' && @pstack.last != :brace
88
+ @pstack.push :bracket
89
+ elsif @pstack.last == :bracket
90
+ @pstack.pop
91
+ end
92
+ end
93
+
94
+ def analyze_parentheses(to_list, extra_characters_check)
95
+ bl = @buffer[-1]
96
+ case bl
97
+ when '{', '}' then analyze_braces(to_list, extra_characters_check)
98
+ when '[', ']' then analyze_brackets unless to_list
99
+ when '"' then analyze_quotes(to_list, extra_characters_check)
68
100
  end
69
101
  end
70
102
  end
@@ -0,0 +1,39 @@
1
+ class String
2
+ def brace?
3
+ @brace ||= self[0] == '{'
4
+ end
5
+
6
+ def bracket?
7
+ @bracket ||= self[0] == '['
8
+ end
9
+
10
+ def quote?
11
+ @quote ||= self[0] == '"'
12
+ end
13
+
14
+ def parenthesis?
15
+ brace? || bracket? || quote?
16
+ end
17
+
18
+ def to_tcl_string
19
+ if parenthesis?
20
+ if (brace? && self[-1] == '}') || (quote? && self[-1] == '"')
21
+ b = self[1..-2]
22
+ clear << b
23
+ end
24
+ end
25
+ self
26
+ end
27
+
28
+ def to_tcl_list
29
+ if self == '' || match(/\s/)
30
+ "{#{self}}"
31
+ else
32
+ self
33
+ end
34
+ end
35
+
36
+ def init
37
+ @brace = @bracket = @quote = nil
38
+ end
39
+ end
data/lib/tcl/ruby/util.rb CHANGED
@@ -7,10 +7,12 @@ module Tcl
7
7
  @v_stack = []
8
8
  @hooks = {}
9
9
  @proc = {}
10
+ @files = {}
10
11
  end
11
12
 
12
13
  def variables(arg)
13
- raise(TclVariableNotFoundError, "can't read $#{arg}, no such variables") unless @variables.key?(arg)
14
+ raise TclVariableNotFoundError.new(arg, 'no such variables') unless
15
+ @variables.key?(arg)
14
16
  @variables[arg]
15
17
  end
16
18
 
@@ -22,6 +24,35 @@ module Tcl
22
24
  def delete_hook(name)
23
25
  @hooks.delete(name.to_s)
24
26
  end
27
+
28
+ def commands
29
+ r = []
30
+ r.concat private_methods.select { |e| e.match(/^___/) }
31
+ .map { |e| e[3..-1] }
32
+ r.concat @proc.keys
33
+ r.concat @hooks.keys
34
+ r
35
+ end
36
+
37
+ private
38
+
39
+ def parse_index_format(a)
40
+ case a
41
+ when /end-(\d+)/ then -1 - Regexp.last_match(1).to_i
42
+ when /end/ then -1
43
+ else
44
+ r = a.to_i
45
+ r < 0 ? 0 : r
46
+ end
47
+ end
48
+
49
+ def get_fp(id, delete: false)
50
+ if @files.key?(id)
51
+ delete ? @files.delete(id) : @files[id]
52
+ else
53
+ raise(CommandError, "cannnot find channel named #{id}")
54
+ end
55
+ end
25
56
  end
26
57
  end
27
58
  end
@@ -1,5 +1,5 @@
1
1
  module Tcl
2
2
  module Ruby
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.1.1'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcl-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kiyonori Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-02 00:00:00.000000000 Z
11
+ date: 2016-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -87,10 +87,14 @@ files:
87
87
  - lib/tcl/ruby/command.rb
88
88
  - lib/tcl/ruby/commands/array.rb
89
89
  - lib/tcl/ruby/commands/basic.rb
90
+ - lib/tcl/ruby/commands/inout.rb
90
91
  - lib/tcl/ruby/commands/list.rb
92
+ - lib/tcl/ruby/commands/string.rb
91
93
  - lib/tcl/ruby/error.rb
92
94
  - lib/tcl/ruby/list_array.rb
95
+ - lib/tcl/ruby/option_parser.rb
93
96
  - lib/tcl/ruby/parser.rb
97
+ - lib/tcl/ruby/string.rb
94
98
  - lib/tcl/ruby/util.rb
95
99
  - lib/tcl/ruby/version.rb
96
100
  - tcl-ruby.gemspec