musa-dsl 0.14.31 → 0.21.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/Gemfile +0 -1
- data/README.md +5 -1
- data/lib/musa-dsl.rb +54 -11
- data/lib/musa-dsl/core-ext.rb +7 -13
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
- data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
- data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
- data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
- data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
- data/lib/musa-dsl/core-ext/with.rb +26 -0
- data/lib/musa-dsl/datasets.rb +8 -3
- data/lib/musa-dsl/datasets/dataset.rb +3 -0
- data/lib/musa-dsl/datasets/delta-d.rb +12 -0
- data/lib/musa-dsl/datasets/e.rb +61 -0
- data/lib/musa-dsl/datasets/gdv.rb +51 -411
- data/lib/musa-dsl/datasets/gdvd.rb +179 -0
- data/lib/musa-dsl/datasets/helper.rb +41 -0
- data/lib/musa-dsl/datasets/p.rb +68 -0
- data/lib/musa-dsl/datasets/packed-v.rb +19 -0
- data/lib/musa-dsl/datasets/pdv.rb +22 -15
- data/lib/musa-dsl/datasets/ps.rb +113 -0
- data/lib/musa-dsl/datasets/score.rb +210 -0
- data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
- data/lib/musa-dsl/datasets/score/render.rb +31 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
- data/lib/musa-dsl/datasets/v.rb +23 -0
- data/lib/musa-dsl/generative.rb +5 -5
- data/lib/musa-dsl/generative/backboner.rb +274 -0
- data/lib/musa-dsl/generative/darwin.rb +102 -96
- data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
- data/lib/musa-dsl/generative/markov.rb +56 -53
- data/lib/musa-dsl/generative/variatio.rb +234 -222
- data/lib/musa-dsl/logger.rb +1 -0
- data/lib/musa-dsl/logger/logger.rb +31 -0
- data/lib/musa-dsl/matrix.rb +1 -0
- data/lib/musa-dsl/matrix/matrix.rb +210 -0
- data/lib/musa-dsl/midi.rb +2 -2
- data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
- data/lib/musa-dsl/midi/midi-voices.rb +187 -182
- data/lib/musa-dsl/music.rb +5 -5
- data/lib/musa-dsl/music/chord-definition.rb +54 -50
- data/lib/musa-dsl/music/chord-definitions.rb +13 -9
- data/lib/musa-dsl/music/chords.rb +236 -238
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
- data/lib/musa-dsl/music/scales.rb +331 -332
- data/lib/musa-dsl/musicxml.rb +1 -0
- data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
- data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
- data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
- data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
- data/lib/musa-dsl/neumalang.rb +1 -1
- data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
- data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
- data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
- data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
- data/lib/musa-dsl/neumalang/process.citrus +21 -0
- data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
- data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
- data/lib/musa-dsl/neumas.rb +5 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
- data/lib/musa-dsl/neumas/neumas.rb +37 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
- data/lib/musa-dsl/repl.rb +1 -1
- data/lib/musa-dsl/repl/repl.rb +122 -110
- data/lib/musa-dsl/sequencer.rb +1 -1
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +554 -308
- data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +75 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
- data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
- data/lib/musa-dsl/series.rb +1 -1
- data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
- data/lib/musa-dsl/series/base-series.rb +171 -168
- data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
- data/lib/musa-dsl/series/holder-serie.rb +1 -1
- data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
- data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
- data/lib/musa-dsl/series/proxy-serie.rb +6 -6
- data/lib/musa-dsl/series/queue-serie.rb +5 -5
- data/lib/musa-dsl/series/series.rb +2 -0
- data/lib/musa-dsl/transcription.rb +4 -0
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
- data/lib/musa-dsl/transcription/transcription.rb +55 -0
- data/lib/musa-dsl/transport.rb +6 -6
- data/lib/musa-dsl/transport/clock.rb +26 -26
- data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
- data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
- data/lib/musa-dsl/transport/input-midi-clock.rb +89 -80
- data/lib/musa-dsl/transport/timer-clock.rb +72 -71
- data/lib/musa-dsl/transport/timer.rb +28 -26
- data/lib/musa-dsl/transport/transport.rb +111 -93
- data/musa-dsl.gemspec +3 -3
- metadata +73 -24
- data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
- data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
- data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
- data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
- data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
- data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
- data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
- data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
- data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
- data/lib/musa-dsl/generative/rules.rb +0 -282
- data/lib/musa-dsl/neuma.rb +0 -1
- 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,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
|
data/lib/musa-dsl/repl.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
require_relative 'repl/repl'
|
data/lib/musa-dsl/repl/repl.rb
CHANGED
@@ -1,149 +1,161 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
module Musa
|
4
|
-
|
5
|
-
|
4
|
+
module REPL
|
5
|
+
class REPL
|
6
|
+
@@repl_mutex = Mutex.new
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
redirect_stderr ||= false
|
8
|
+
def initialize(binder, port: nil, after_eval: nil, logger: nil)
|
9
|
+
port ||= 1327
|
10
10
|
|
11
|
-
|
11
|
+
@logger = logger || Musa::Logger::Logger.new
|
12
12
|
|
13
|
-
|
14
|
-
binder.receiver.sequencer.respond_to?(:on_block_error)
|
13
|
+
@block_source = nil
|
15
14
|
|
16
|
-
binder.receiver.sequencer
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
original_stderr = $stderr
|
41
|
-
|
42
|
-
$stdout = connection
|
43
|
-
$stderr = connection if redirect_stderr
|
44
|
-
|
45
|
-
@block_source = buffer.string
|
46
|
-
|
47
|
-
begin
|
48
|
-
send_echo @block_source
|
49
|
-
|
50
|
-
binder.eval @block_source, "(repl)", 1
|
51
|
-
rescue StandardError, ScriptError => e
|
52
|
-
send_exception e
|
53
|
-
else
|
54
|
-
after_eval.call @block_source if after_eval
|
55
|
-
end
|
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
|
56
42
|
|
57
|
-
|
58
|
-
|
43
|
+
when '#end'
|
44
|
+
@@repl_mutex.synchronize do
|
45
|
+
@block_source = buffer.string
|
46
|
+
|
47
|
+
begin
|
48
|
+
send_echo @block_source, output: @connection
|
49
|
+
binder.eval @block_source, "(repl)", 1
|
50
|
+
|
51
|
+
rescue StandardError, ScriptError => e
|
52
|
+
@logger.warn('REPL') { 'code execution error' }
|
53
|
+
@logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
|
54
|
+
|
55
|
+
send_exception e, output: @connection
|
56
|
+
else
|
57
|
+
after_eval.call @block_source if after_eval
|
58
|
+
end
|
59
|
+
end
|
60
|
+
else
|
61
|
+
buffer.puts line
|
59
62
|
end
|
60
|
-
else
|
61
|
-
buffer.puts line
|
62
63
|
end
|
64
|
+
|
65
|
+
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
|
66
|
+
@logger.warn('REPL') { 'lost connection' }
|
67
|
+
@logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
|
68
|
+
|
69
|
+
ensure
|
70
|
+
@logger.debug("REPL") { "closing connection (running #{@run})" }
|
71
|
+
@connection.close
|
63
72
|
end
|
64
|
-
rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
|
65
|
-
warn e.message
|
66
|
-
end
|
67
73
|
|
68
|
-
|
74
|
+
end
|
69
75
|
end
|
76
|
+
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
77
|
+
@logger.warn('REPL') { 'connection failure while getting server port; will retry...' }
|
78
|
+
@logger.warn('REPL') { e.full_message(highlight: true, order: :top) }
|
79
|
+
retry
|
80
|
+
|
70
81
|
end
|
71
|
-
rescue Errno::ECONNRESET, Errno::EPIPE => e
|
72
|
-
warn e.message
|
73
|
-
retry
|
74
82
|
end
|
75
83
|
end
|
76
|
-
end
|
77
84
|
|
78
|
-
|
79
|
-
|
85
|
+
def stop
|
86
|
+
@run = false
|
80
87
|
|
81
|
-
|
82
|
-
|
88
|
+
@main_thread.terminate
|
89
|
+
Thread.pass
|
83
90
|
|
84
|
-
|
85
|
-
@client_threads.clear
|
86
|
-
end
|
91
|
+
@main_thread = nil
|
87
92
|
|
88
|
-
|
93
|
+
@client_threads.each { |t| t.terminate; Thread.pass }
|
94
|
+
@client_threads.clear
|
95
|
+
end
|
89
96
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
97
|
+
private
|
98
|
+
|
99
|
+
def send_echo(e, output:)
|
100
|
+
send output: output, command: '//echo'
|
101
|
+
send output: output, content: e
|
102
|
+
send output: output, command: '//end'
|
103
|
+
end
|
104
|
+
|
105
|
+
def send_exception(e, output:)
|
95
106
|
|
96
|
-
|
107
|
+
@logger.error('REPL') { e.full_message(highlight: true, order: :top) }
|
97
108
|
|
98
|
-
|
109
|
+
send output: output, command: '//error'
|
99
110
|
|
100
|
-
|
111
|
+
selected_backtrace_locations = e.backtrace_locations.select { |bt| bt.path == '(repl)' }
|
101
112
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
113
|
+
if e.is_a?(ScriptError)
|
114
|
+
send output: output, content: e.class.name
|
115
|
+
send output: output, command: '//backtrace'
|
116
|
+
send output: output, content: e.message
|
106
117
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
118
|
+
elsif selected_backtrace_locations.empty?
|
119
|
+
send output: output, content: "#{e.class.name}: #{e.message}"
|
120
|
+
send output: output, command: '//backtrace'
|
121
|
+
send output: output, content: e.backtrace_locations.first.to_s
|
111
122
|
|
112
|
-
|
113
|
-
|
123
|
+
else
|
124
|
+
lines = @block_source.split("\n")
|
114
125
|
|
115
|
-
|
126
|
+
lineno = selected_backtrace_locations.first.lineno
|
116
127
|
|
117
|
-
|
118
|
-
|
119
|
-
|
128
|
+
source_before = lines[lineno - 2] if lineno >= 2
|
129
|
+
source_error = lines[lineno - 1]
|
130
|
+
source_after = lines[lineno]
|
120
131
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
132
|
+
send output: output, content: '***'
|
133
|
+
send output: output, content: "[#{lineno - 1}] #{source_before}" if source_before
|
134
|
+
send output: output, content: "[#{lineno}] #{source_error} \t\t<<< ERROR !!!"
|
135
|
+
send output: output, content: "[#{lineno + 1}] #{source_after}" if source_after
|
136
|
+
send output: output, content: '***'
|
137
|
+
send output: output, content: e.class.name
|
138
|
+
send output: output, content: e.message
|
139
|
+
send output: output, command: '//backtrace'
|
140
|
+
selected_backtrace_locations.each do |bt|
|
141
|
+
send output: output, content: bt.to_s
|
142
|
+
end
|
131
143
|
end
|
144
|
+
send output: output, content: ' '
|
145
|
+
send output: output, command: '//end'
|
132
146
|
end
|
133
|
-
send content: ' '
|
134
|
-
send command: '//end'
|
135
|
-
end
|
136
147
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
148
|
+
def send(output:, content: nil, command: nil)
|
149
|
+
output.puts escape(content) if content
|
150
|
+
output.puts command if command
|
151
|
+
end
|
141
152
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
153
|
+
def escape(text)
|
154
|
+
if text.start_with? '//'
|
155
|
+
"//#{text}"
|
156
|
+
else
|
157
|
+
text
|
158
|
+
end
|
147
159
|
end
|
148
160
|
end
|
149
161
|
end
|