musa-dsl 0.14.29 → 0.21.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/Gemfile +0 -1
  4. data/README.md +5 -1
  5. data/lib/musa-dsl.rb +54 -11
  6. data/lib/musa-dsl/core-ext.rb +7 -13
  7. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
  8. data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
  9. data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
  10. data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
  11. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
  12. data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
  13. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
  14. data/lib/musa-dsl/core-ext/with.rb +26 -0
  15. data/lib/musa-dsl/datasets.rb +8 -3
  16. data/lib/musa-dsl/datasets/dataset.rb +3 -0
  17. data/lib/musa-dsl/datasets/delta-d.rb +12 -0
  18. data/lib/musa-dsl/datasets/e.rb +61 -0
  19. data/lib/musa-dsl/datasets/gdv.rb +51 -411
  20. data/lib/musa-dsl/datasets/gdvd.rb +179 -0
  21. data/lib/musa-dsl/datasets/helper.rb +41 -0
  22. data/lib/musa-dsl/datasets/p.rb +68 -0
  23. data/lib/musa-dsl/datasets/packed-v.rb +19 -0
  24. data/lib/musa-dsl/datasets/pdv.rb +22 -15
  25. data/lib/musa-dsl/datasets/ps.rb +113 -0
  26. data/lib/musa-dsl/datasets/score.rb +210 -0
  27. data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
  28. data/lib/musa-dsl/datasets/score/render.rb +31 -0
  29. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
  30. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
  31. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
  32. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
  33. data/lib/musa-dsl/datasets/v.rb +23 -0
  34. data/lib/musa-dsl/generative.rb +5 -5
  35. data/lib/musa-dsl/generative/backboner.rb +274 -0
  36. data/lib/musa-dsl/generative/darwin.rb +102 -96
  37. data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
  38. data/lib/musa-dsl/generative/markov.rb +56 -53
  39. data/lib/musa-dsl/generative/variatio.rb +234 -222
  40. data/lib/musa-dsl/logger.rb +1 -0
  41. data/lib/musa-dsl/logger/logger.rb +31 -0
  42. data/lib/musa-dsl/matrix.rb +1 -0
  43. data/lib/musa-dsl/matrix/matrix.rb +210 -0
  44. data/lib/musa-dsl/midi.rb +2 -2
  45. data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
  46. data/lib/musa-dsl/midi/midi-voices.rb +187 -182
  47. data/lib/musa-dsl/music.rb +5 -5
  48. data/lib/musa-dsl/music/chord-definition.rb +54 -50
  49. data/lib/musa-dsl/music/chord-definitions.rb +13 -9
  50. data/lib/musa-dsl/music/chords.rb +236 -238
  51. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
  52. data/lib/musa-dsl/music/scales.rb +331 -332
  53. data/lib/musa-dsl/musicxml.rb +1 -0
  54. data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
  55. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
  56. data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
  57. data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
  58. data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
  59. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
  60. data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
  61. data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
  62. data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
  63. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
  64. data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
  65. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
  66. data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
  67. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
  68. data/lib/musa-dsl/neumalang.rb +1 -1
  69. data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
  70. data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
  71. data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
  72. data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
  73. data/lib/musa-dsl/neumalang/process.citrus +21 -0
  74. data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
  75. data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
  76. data/lib/musa-dsl/neumas.rb +5 -0
  77. data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
  78. data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
  79. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
  80. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
  81. data/lib/musa-dsl/neumas/neumas.rb +37 -0
  82. data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
  83. data/lib/musa-dsl/repl.rb +1 -1
  84. data/lib/musa-dsl/repl/repl.rb +128 -105
  85. data/lib/musa-dsl/sequencer.rb +1 -1
  86. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +552 -308
  89. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  90. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +77 -0
  91. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
  92. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
  93. data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
  94. data/lib/musa-dsl/series.rb +1 -1
  95. data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
  96. data/lib/musa-dsl/series/base-series.rb +171 -168
  97. data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
  98. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  99. data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
  100. data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
  101. data/lib/musa-dsl/series/proxy-serie.rb +6 -6
  102. data/lib/musa-dsl/series/queue-serie.rb +5 -5
  103. data/lib/musa-dsl/series/series.rb +2 -0
  104. data/lib/musa-dsl/transcription.rb +4 -0
  105. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
  106. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
  107. data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
  108. data/lib/musa-dsl/transcription/transcription.rb +55 -0
  109. data/lib/musa-dsl/transport.rb +6 -6
  110. data/lib/musa-dsl/transport/clock.rb +26 -26
  111. data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
  112. data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
  113. data/lib/musa-dsl/transport/input-midi-clock.rb +82 -80
  114. data/lib/musa-dsl/transport/timer-clock.rb +72 -71
  115. data/lib/musa-dsl/transport/timer.rb +28 -26
  116. data/lib/musa-dsl/transport/transport.rb +111 -93
  117. data/musa-dsl.gemspec +3 -3
  118. metadata +73 -24
  119. data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
  120. data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
  121. data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
  122. data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
  123. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
  124. data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
  125. data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
  126. data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
  127. data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
  128. data/lib/musa-dsl/generative/rules.rb +0 -282
  129. data/lib/musa-dsl/neuma.rb +0 -1
  130. data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -0,0 +1,21 @@
1
+ grammar Musa::Neumalang::Neumalang::Parser::Grammar::Process
2
+ include Musa::Neumalang::Neumalang::Parser::Grammar::Vectors
3
+
4
+ rule process
5
+ process_of_vectors | process_of_packed_vectors
6
+ end
7
+
8
+ rule process_of_vectors
9
+ (first:raw_vector
10
+ (optional_separation
11
+ bar optional_separation durations:raw_number optional_separation bar optional_separation
12
+ rest:raw_vector)+) <Musa::Neumalang::Neumalang::Parser::ProcessOfVectors>
13
+ end
14
+
15
+ rule process_of_packed_vectors
16
+ (first:raw_packed_vector
17
+ (optional_separation
18
+ bar optional_separation durations:raw_number optional_separation bar optional_separation
19
+ rest:raw_packed_vector)+) <Musa::Neumalang::Neumalang::Parser::ProcessOfVectors>
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ grammar Musa::Neumalang::Neumalang::Parser::Grammar::Terminals
2
+ rule everything_except_comment
3
+ ~/((\*\/)|(\/\*))/m
4
+ end
5
+
6
+ rule everything_except_braces
7
+ ~/({|})/m
8
+ end
9
+
10
+ rule everything_except_double_quote
11
+ ~/(\")/m
12
+ end
13
+
14
+ rule comment
15
+ (lcomment complex_comment rcomment)
16
+ end
17
+
18
+ rule complex_comment
19
+ everything_except_comment? (lcomment complex_comment rcomment)* everything_except_comment?
20
+ end
21
+
22
+ rule attribute_change
23
+ space | dot | rbracket | rpar | rbrace | eol | eos
24
+ end
25
+
26
+ rule true 'true' end
27
+ rule false 'false' end
28
+ rule nil 'nil' end
29
+
30
+ rule optional_separation (spaces | comment)* end
31
+ rule separation (spaces | comment)+ end
32
+
33
+ rule double_quote '"' end
34
+ rule single_quote '\'' end
35
+ rule dot '.' end
36
+ rule mid_dot '·' end
37
+ rule comma ',' end
38
+ rule colon ':' end
39
+ rule double_colon '::' end
40
+ rule bar '|' end
41
+ rule double_bar '||' end
42
+ rule asterisk '*' end
43
+ rule slash '/' end
44
+ rule lpar '(' end
45
+ rule rpar ')' end
46
+ rule lbrace '{' end
47
+ rule rbrace '}' end
48
+ rule lbracket '[' end
49
+ rule rbracket ']' end
50
+ rule lacute '<' end
51
+ rule racute '>' end
52
+ rule at '@' end
53
+ rule ampersand '&' end
54
+ rule equal '=' end
55
+ rule lcomment '/*' end
56
+ rule rcomment '*/' end
57
+ rule hsh '#' end
58
+ rule underscore '_' end
59
+ rule minus '-' end
60
+ rule plus '+' end
61
+
62
+ rule eol /$/ end
63
+ rule eos /\Z/ end
64
+
65
+ rule space /[[:space:]]/ end
66
+ rule spaces /[[:space:]]/+ end
67
+ end
@@ -0,0 +1,23 @@
1
+ grammar Musa::Neumalang::Neumalang::Parser::Grammar::Vectors
2
+ include Musa::Neumalang::Neumalang::Parser::Grammar::Datatypes
3
+
4
+ rule vector
5
+ (optional_separation raw_vector) <Musa::Neumalang::Neumalang::Parser::Vector>
6
+ end
7
+
8
+ rule raw_vector
9
+ (lpar (optional_separation raw_number)+ optional_separation rpar) <Musa::Neumalang::Neumalang::Parser::RawVector>
10
+ end
11
+
12
+ rule packed_vector
13
+ (optional_separation raw_packed_vector) <Musa::Neumalang::Neumalang::Parser::PackedVector>
14
+ end
15
+
16
+ rule raw_packed_vector
17
+ (lpar (optional_separation key_value)+ optional_separation rpar) <Musa::Neumalang::Neumalang::Parser::RawPackedVector>
18
+ end
19
+
20
+ rule key_value
21
+ (raw_symbol colon optional_separation raw_number) { [ capture(:raw_symbol).value, capture(:raw_number).value ] }
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'neumas/neuma-gdv-decoder'
2
+ require_relative 'neumas/neuma-gdvd-decoder'
3
+
4
+ require_relative 'neumas/array-to-neumas'
5
+ require_relative 'neumas/string-to-neumas'
@@ -0,0 +1,34 @@
1
+ require_relative '../series'
2
+ require_relative '../neumalang'
3
+
4
+ module Musa
5
+ module Extension
6
+ module Neumas
7
+ refine Array do
8
+ def to_neumas
9
+ if length > 1
10
+ MERGE(*collect { |e| convert_to_neumas(e) })
11
+ else
12
+ convert_to_neumas(first)
13
+ end
14
+ end
15
+
16
+ alias_method :neumas, :to_neumas
17
+ alias_method :n, :to_neumas
18
+
19
+ private
20
+
21
+ def convert_to_neumas(e)
22
+ case e
23
+ when Musa::Neumas::Neuma::Serie then e
24
+ when Musa::Neumas::Neuma::Parallel then _SE([e], extends: Musa::Neumas::Neuma::Serie)
25
+ when String then e.to_neumas
26
+ else
27
+ raise ArgumentError, "Don't know how to convert to neumas #{e}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,63 @@
1
+ require_relative 'neumas'
2
+
3
+ module Musa::Neumas
4
+ module Decoders
5
+ class ProtoDecoder
6
+ def subcontext
7
+ self
8
+ end
9
+
10
+ def decode(_element)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+
15
+ class DifferentialDecoder < ProtoDecoder
16
+ def decode(gdvd)
17
+ process gdvd
18
+ end
19
+
20
+ def process(_gdvd)
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+
25
+ class Decoder < DifferentialDecoder
26
+ def initialize(base, transcriptor: nil)
27
+ @base = base
28
+ @last = base.clone
29
+
30
+ @transcriptor = transcriptor
31
+ end
32
+
33
+ attr_accessor :transcriptor
34
+ attr_reader :base
35
+
36
+ def base=(base)
37
+ @base = base
38
+ @last = base.clone
39
+ end
40
+
41
+ def subcontext
42
+ Decoder.new @base
43
+ end
44
+
45
+ def decode(attributes)
46
+ result = apply process(attributes), on: @last
47
+
48
+ @last = result.clone
49
+
50
+ if @transcriptor
51
+ @transcriptor.transcript(result)
52
+ else
53
+ result
54
+ end
55
+ end
56
+
57
+ def apply(_action, on:)
58
+ raise NotImplementedError
59
+ end
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,57 @@
1
+ require_relative 'neuma-decoder'
2
+
3
+ module Musa::Neumas
4
+ module Decoders
5
+ class NeumaDecoder < Decoder # to get a GDV
6
+ def initialize(scale, base_duration: nil, transcriptor: nil, base: nil)
7
+ @base_duration = base_duration
8
+ @base_duration ||= base[:duration] if base
9
+ @base_duration ||= Rational(1,4)
10
+
11
+ base ||= { grade: 0, octave: 0, duration: @base_duration, velocity: 1 }
12
+
13
+ @scale = scale
14
+
15
+ super base, transcriptor: transcriptor
16
+ end
17
+
18
+ attr_accessor :scale, :base_duration
19
+
20
+ def process(gdvd)
21
+ gdvd = gdvd.clone
22
+
23
+ gdvd.base_duration = @base_duration
24
+
25
+ appogiatura_gdvd = gdvd[:modifiers]&.delete :appogiatura
26
+
27
+ if appogiatura_gdvd
28
+ appogiatura_gdvd = appogiatura_gdvd.clone
29
+ appogiatura_gdvd.base_duration = @base_duration
30
+
31
+ gdvd[:modifiers][:appogiatura] = appogiatura_gdvd
32
+ end
33
+
34
+ gdvd
35
+ end
36
+
37
+ def subcontext
38
+ NeumaDecoder.new @scale, base_duration: @base_duration, transcriptor: @transcriptor, base: @last
39
+ end
40
+
41
+ def apply(gdvd, on:)
42
+ gdv = gdvd.to_gdv @scale, previous: on
43
+
44
+ appogiatura_action = gdvd.dig(:modifiers, :appogiatura)
45
+ gdv[:appogiatura] = appogiatura_action.to_gdv @scale, previous: on if appogiatura_action
46
+
47
+ gdv
48
+ end
49
+
50
+ def inspect
51
+ "GDV NeumaDecoder: @last = #{@last}"
52
+ end
53
+
54
+ alias to_s inspect
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'neuma-decoder'
2
+
3
+ module Musa::Neumas
4
+ module Decoders
5
+ class NeumaDifferentialDecoder < DifferentialDecoder # to get a GDVd
6
+ def initialize(base_duration: nil)
7
+ @base_duration = base_duration || Rational(1,4)
8
+ end
9
+
10
+ def process(gdvd)
11
+ gdvd.clone.tap { |_| _.base_duration = @base_duration }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'string-to-neumas'
2
+
3
+ using Musa::Extension::Neumas
4
+
5
+ module Musa
6
+ module Neumas
7
+ module Neuma
8
+ module Parallel
9
+ include Neuma
10
+ end
11
+
12
+ module Serie
13
+ include Neuma
14
+ end
15
+
16
+ def |(other)
17
+ if is_a?(Parallel)
18
+ clone.tap { |_| _[:parallel] << convert_to_parallel_element(other) }.extend(Parallel)
19
+ else
20
+ { kind: :parallel,
21
+ parallel: [clone, convert_to_parallel_element(other)]
22
+ }.extend(Parallel)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def convert_to_parallel_element(e)
29
+ case e
30
+ when String then { kind: :serie, serie: e.to_neumas }.extend(Neuma)
31
+ else
32
+ raise ArgumentError, "Don't know how to convert to neumas #{e}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../neumalang'
2
+ require_relative '../generative/generative-grammar'
3
+
4
+ module Musa
5
+ module Extension
6
+ module Neumas
7
+ refine String do
8
+ def to_neumas(decode_with: nil, debug: nil)
9
+ Musa::Neumalang::Neumalang.parse(self, decode_with: decode_with, debug: debug)
10
+ end
11
+
12
+ def to_neumas_to_node(decode_with: nil, debug: nil)
13
+ to_neumas(decode_with: decode_with, debug: debug).to_node
14
+ end
15
+
16
+ def |(other)
17
+ case other
18
+ when String
19
+ { kind: :parallel,
20
+ parallel: [{ kind: :serie, serie: self.to_neumas },
21
+ { kind: :serie, serie: other.to_neumas }] }.extend(Musa::Neumas::Neuma::Parallel)
22
+ else
23
+ raise ArgumentError, "Don't know how to parallelize #{other}"
24
+ end
25
+ end
26
+
27
+
28
+ alias_method :neumas, :to_neumas
29
+ alias_method :n, :to_neumas
30
+ alias_method :nn, :to_neumas_to_node
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1 +1 @@
1
- require 'musa-dsl/repl/repl'
1
+ require_relative 'repl/repl'
@@ -1,142 +1,165 @@
1
1
  require 'socket'
2
2
 
3
3
  module Musa
4
- class REPL
5
- @@repl_mutex = Mutex.new
4
+ module REPL
5
+ class REPL
6
+ @@repl_mutex = Mutex.new
6
7
 
7
- def initialize(binder, port: nil, redirect_stderr: nil, after_eval: nil)
8
- port ||= 1327
9
- redirect_stderr ||= false
8
+ def initialize(binder, port: nil, after_eval: nil, logger: nil)
9
+ port ||= 1327
10
10
 
11
- @block_source = nil
11
+ @logger = logger || Musa::Logger::Logger.new
12
12
 
13
- if binder.receiver.respond_to?(:sequencer) &&
14
- binder.receiver.sequencer.respond_to?(:on_block_error)
13
+ @block_source = nil
15
14
 
16
- binder.receiver.sequencer.on_block_error do |e|
17
- send_exception e
15
+ if binder.receiver.respond_to?(:sequencer) &&
16
+ binder.receiver.sequencer.respond_to?(:on_error)
17
+
18
+ binder.receiver.sequencer.on_error do |e|
19
+ send_exception e, output: @connection
20
+ end
18
21
  end
19
- end
20
22
 
21
- @client_threads = []
22
-
23
- @main_thread = Thread.new do
24
- @server = TCPServer.new(port)
25
- begin
26
- while connection = @server.accept
27
- @client_threads << Thread.new do
28
- buffer = nil
29
-
30
- begin
31
- while line = connection.gets
32
- line.chomp!
33
- case line
34
- when '#begin'
35
- buffer = StringIO.new
36
- when '#end'
37
- @@repl_mutex.synchronize do
38
- original_stdout = $stdout
39
- original_stderr = $stderr
40
-
41
- $stdout = connection
42
- $stderr = connection if redirect_stderr
43
-
44
- @block_source = buffer.string
45
-
46
- begin
47
- send_echo @block_source
48
-
49
- binder.eval @block_source, "(repl)", 1
50
- rescue StandardError, ScriptError => e
51
- send_exception e
52
- else
53
- after_eval.call @block_source if after_eval
23
+ @client_threads = []
24
+ @run = true
25
+
26
+ @main_thread = Thread.new do
27
+ @server = TCPServer.new(port)
28
+ begin
29
+ while (@connection = @server.accept) && @run
30
+ @client_threads << Thread.new do
31
+ buffer = nil
32
+
33
+ begin
34
+ while (line = @connection.gets) && @run
35
+
36
+ @logger.warn('REPL') { 'input line is nil; will close connection...' } if line.nil?
37
+
38
+ line.chomp!
39
+ case line
40
+ when '#begin'
41
+ buffer = StringIO.new
42
+ when '#end'
43
+ @@repl_mutex.synchronize do
44
+ @block_source = buffer.string
45
+
46
+ begin
47
+ send_echo @block_source, output: @connection
48
+ binder.eval @block_source, "(repl)", 1
49
+
50
+ rescue StandardError, ScriptError => e
51
+ @logger.debug('REPL') { 'code execution error' }
52
+ @logger.debug('REPL') { e.full_message(highlight: true, order: :top) }
53
+
54
+ send_exception e, output: @connection
55
+ else
56
+ after_eval.call @block_source if after_eval
57
+ end
54
58
  end
55
-
56
- $stdout = original_stdout
57
- $stderr = original_stderr if redirect_stderr
59
+ else
60
+ buffer.puts line
58
61
  end
59
- else
60
- buffer.puts line
61
62
  end
63
+
64
+ rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
65
+ @logger.warn('REPL') { 'lost connection' }
66
+ @logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
67
+
68
+ rescue Exception, SystemExit, Interrupt
69
+ puts "EXCEPTION / SYSTEM EXIT / INTERUPT"
70
+ $stdout.flush
71
+ @logger.warn('REPL') { 'received interruption; will close connection...' }
72
+ raise
73
+
74
+ ensure
75
+ @logger.debug("REPL") { "closing connection (running #{@run})" }
76
+ @connection.close
62
77
  end
63
- rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
64
- warn e.message
65
- end
66
78
 
67
- connection.close
79
+ end
68
80
  end
81
+ rescue Errno::ECONNRESET, Errno::EPIPE => e
82
+ @logger.warn('REPL') { 'connection failure while getting server port; will retry...' }
83
+ @logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
84
+ retry
85
+
69
86
  end
70
- rescue Errno::ECONNRESET, Errno::EPIPE => e
71
- warn e.message
72
- retry
73
87
  end
74
88
  end
75
- end
76
89
 
77
- def stop
78
- # TODO
79
- end
90
+ def stop
91
+ @run = false
80
92
 
81
- private
93
+ @main_thread.terminate
94
+ Thread.pass
95
+ @main_thread = nil
82
96
 
83
- def send_echo(e)
84
- send command: '//echo'
85
- send content: e
86
- send command: '//end'
87
- end
97
+ @client_threads.each { |t| t.terminate; Thread.pass }
98
+ @client_threads.clear
99
+ end
100
+
101
+ private
102
+
103
+ def send_echo(e, output:)
104
+ send output: output, command: '//echo'
105
+ send output: output, content: e
106
+ send output: output, command: '//end'
107
+ end
108
+
109
+ def send_exception(e, output:)
88
110
 
89
- def send_exception(e)
111
+ @logger.error('REPL') { e.full_message(highlight: true, order: :top) }
90
112
 
91
- send command: '//error'
113
+ send output: output, command: '//error'
92
114
 
93
- selected_backtrace_locations = e.backtrace_locations.select { |bt| bt.path == '(repl)' }
115
+ selected_backtrace_locations = e.backtrace_locations.select { |bt| bt.path == '(repl)' }
94
116
 
95
- if e.is_a?(ScriptError)
96
- send content: e.class.name
97
- send command: '//backtrace'
98
- send content: e.message
117
+ if e.is_a?(ScriptError)
118
+ send output: output, content: e.class.name
119
+ send output: output, command: '//backtrace'
120
+ send output: output, content: e.message
99
121
 
100
- elsif selected_backtrace_locations.empty?
101
- send content: "#{e.class.name}: #{e.message}"
102
- send command: '//backtrace'
103
- send content: e.backtrace_locations.first.to_s
122
+ elsif selected_backtrace_locations.empty?
123
+ send output: output, content: "#{e.class.name}: #{e.message}"
124
+ send output: output, command: '//backtrace'
125
+ send output: output, content: e.backtrace_locations.first.to_s
104
126
 
105
- else
106
- lines = @block_source.split("\n")
127
+ else
128
+ lines = @block_source.split("\n")
107
129
 
108
- lineno = selected_backtrace_locations.first.lineno
130
+ lineno = selected_backtrace_locations.first.lineno
109
131
 
110
- source_before = lines[lineno - 2] if lineno >= 2
111
- source_error = lines[lineno - 1]
112
- source_after = lines[lineno]
132
+ source_before = lines[lineno - 2] if lineno >= 2
133
+ source_error = lines[lineno - 1]
134
+ source_after = lines[lineno]
113
135
 
114
- send content: '***'
115
- send content: "[#{lineno - 1}] #{source_before}" if source_before
116
- send content: "[#{lineno}] #{source_error} \t\t<<< ERROR !!!"
117
- send content: "[#{lineno + 1}] #{source_after}" if source_after
118
- send content: '***'
119
- send content: e.class.name
120
- send content: e.message
121
- send command: '//backtrace'
122
- selected_backtrace_locations.each do |bt|
123
- send content: bt.to_s
136
+ send output: output, content: '***'
137
+ send output: output, content: "[#{lineno - 1}] #{source_before}" if source_before
138
+ send output: output, content: "[#{lineno}] #{source_error} \t\t<<< ERROR !!!"
139
+ send output: output, content: "[#{lineno + 1}] #{source_after}" if source_after
140
+ send output: output, content: '***'
141
+ send output: output, content: e.class.name
142
+ send output: output, content: e.message
143
+ send output: output, command: '//backtrace'
144
+ selected_backtrace_locations.each do |bt|
145
+ send output: output, content: bt.to_s
146
+ end
124
147
  end
148
+ send output: output, content: ' '
149
+ send output: output, command: '//end'
125
150
  end
126
- send content: ' '
127
- send command: '//end'
128
- end
129
151
 
130
- def send(content: nil, command: nil)
131
- puts escape(content) if content
132
- puts command if command
133
- end
152
+ def send(output:, content: nil, command: nil)
153
+ output.puts escape(content) if content
154
+ output.puts command if command
155
+ end
134
156
 
135
- def escape(text)
136
- if text.start_with? '//'
137
- "//#{text}"
138
- else
139
- text
157
+ def escape(text)
158
+ if text.start_with? '//'
159
+ "//#{text}"
160
+ else
161
+ text
162
+ end
140
163
  end
141
164
  end
142
165
  end