sketch some actual shell internal modules
Matt S Trout [Sat, 27 Feb 2016 21:24:25 +0000 (21:24 +0000)]
bin/dx [new file with mode: 0644]
lib/DX/PropositionSequence.pm [new file with mode: 0644]
lib/DX/QueryState.pm [new file with mode: 0644]
lib/DX/ShellFrontend.pm [new file with mode: 0644]
lib/DX/ShellSession.pm [new file with mode: 0644]
lib/DX/ShellState.pm [new file with mode: 0644]
lib/DX/Types.pm

diff --git a/bin/dx b/bin/dx
new file mode 100644 (file)
index 0000000..fac99ee
--- /dev/null
+++ b/bin/dx
@@ -0,0 +1,13 @@
+use strictures 2;
+use Module::Runtime qw(use_module);
+BEGIN { *u = \&use_module }
+
+use_module('DX::ShellFrontend')->new(
+  session_mode => 'shell',
+  session => use_module('DX::ShellSession')->new(
+    shell_state => use_module('DX::ShellState')->new(
+      mode => 'shell',
+      template_query_state => use_module('DX::QueryState')->new
+    )
+  ),
+)->repl
diff --git a/lib/DX/PropositionSequence.pm b/lib/DX/PropositionSequence.pm
new file mode 100644 (file)
index 0000000..2becac1
--- /dev/null
@@ -0,0 +1,17 @@
+package DX::PropositionSequence;
+
+use Types::Standard qw(ArrayRef);
+use DX::Class;
+
+has members => (is => 'ro', isa => ArrayRef[Proposition], required => 1);
+
+sub new_empty {
+  shift->new(members => []);
+}
+
+sub but_append_proposition {
+  my ($self, $prop) = @_;
+  $self->but(members => [ @{$self->members}, $prop ];
+}
+
+1;
diff --git a/lib/DX/QueryState.pm b/lib/DX/QueryState.pm
new file mode 100644 (file)
index 0000000..90d72bc
--- /dev/null
@@ -0,0 +1,9 @@
+package DX::QueryState;
+
+use DX::Class;
+
+#has proposition_sequence => (
+#  is => 'ro', isa => PropositionSequence, required => 1
+#);
+
+1;
diff --git a/lib/DX/ShellFrontend.pm b/lib/DX/ShellFrontend.pm
new file mode 100644 (file)
index 0000000..1b91bf6
--- /dev/null
@@ -0,0 +1,63 @@
+package DX::ShellFrontend;
+
+use IO::Handle;
+use Caroline;
+use DX::Class;
+
+has session => (
+  is => 'ro', required => 1,
+  handles => [ qw(is_complete_command_string eval_command_string) ]
+);
+
+has session_mode => (is => 'rwp', required => 1);
+
+has readline => (is => 'lazy', builder => sub { Caroline->new });
+
+sub BUILD { STDOUT->autoflush(1) }
+
+sub repl {
+  my ($self) = @_;
+  while (1) {
+    last unless $self->rep
+  }
+}
+
+sub rep {
+  my ($self) = @_;
+  return unless defined(my $command = $self->read_command);
+  my @result = $self->eval_command_string($command);
+  $self->process_result(@result);
+  return 1;
+}
+
+sub read_command {
+  my ($self) = @_;
+  my $base_prompt = $self->session_mode eq 'shell' ? '$ ' : '? ';
+  my $rl = $self->readline;
+  return unless defined(my $command = $rl->readline($base_prompt));
+  while (not $self->is_complete_command_string($command)) {
+    $command .= $rl->readline('> ');
+  }
+  $rl->history_add($command);
+  return $command;
+}
+
+sub process_result {
+  my ($self, @result) = @_;
+  foreach my $res (@result) {
+    my ($type, $payload) = @$res;
+    $self->${\($self->can("process_${type}_result")||die)}($payload);
+  }
+}
+
+sub process_mode_result {
+  my ($self, $mode) = @_;
+  $self->_set_session_mode($mode);
+}
+
+sub process_output_result {
+  my ($self, $output) = @_;
+  print $output;
+}
+
+1;
diff --git a/lib/DX/ShellSession.pm b/lib/DX/ShellSession.pm
new file mode 100644 (file)
index 0000000..dd4c2a9
--- /dev/null
@@ -0,0 +1,50 @@
+package DX::ShellSession;
+
+use Tcl;
+use Scalar::Util qw(weaken);
+use DX::Class;
+
+has shell_state => (is => 'rwp', required => 1, isa => ShellState);
+
+has tcl => (is => 'lazy', builder => sub {
+  my ($self) = @_;
+  weaken $self;
+  my $tcl = Tcl->new;
+  $tcl->CreateCommand('?' => sub {
+    $self->apply_to_state([ 'new_query_state' ], [ mode => 'query' ])
+  });
+  $tcl->CreateCommand('?+' => sub {
+    $self->apply_to_state([ mode => 'query' ])
+  });
+  $tcl->CreateCommand('.' => sub {
+    $self->apply_to_state([ mode => 'shell' ])
+  });
+  return $tcl;
+});
+
+sub apply_to_state {
+  my ($self, @to_apply) = @_;
+  my $state = $self->shell_state;
+  our @Result;
+  foreach my $to_apply (@to_apply) {
+    my ($change, @args) = @$to_apply;
+    ($state, my @this_result) = $state->${\"with_${change}"}(@args);
+    push @Result, @this_result;
+  }
+  $self->_set_shell_state($state);
+  return;
+}
+
+sub is_complete_command_string {
+  my ($self, $string) = @_;
+  return !!$self->tcl->icall(info => complete => $string);
+}
+
+sub eval_command_string {
+  my ($self, $string) = @_;
+  local our @Result;
+  $self->tcl->Eval($string);
+  return @Result;
+}
+
+1;
diff --git a/lib/DX/ShellState.pm b/lib/DX/ShellState.pm
new file mode 100644 (file)
index 0000000..665d9bc
--- /dev/null
@@ -0,0 +1,32 @@
+package DX::ShellState;
+
+use DX::Class;
+
+has template_query_state => (
+  is => 'ro', required => 1, isa => QueryState
+);
+
+has current_query_state => (
+  is => 'ro', predicate => 1, isa => QueryState
+);
+
+has mode => (is => 'ro', required => 1);
+
+sub new_query_state { $_[0]->template_query_state }
+
+sub with_new_query_state {
+  my ($self) = @_;
+  $self->but(
+    current_query_state => $self->new_query_state
+  );
+}
+
+sub with_mode {
+  my ($self, $new_mode) = @_;
+  return (
+    $self->but(mode => $new_mode),
+    [ mode => $new_mode ],
+  );
+}
+
+1;
index 729fa60..47a307b 100644 (file)
@@ -6,6 +6,7 @@ use Type::Library
   -declare => (
     (our @CLASSES = qw(
       Hypothesis Scope ResolvedPropositionSet Proposition DependencyMap
+      PropositionSequence QueryState ShellState ShellSession
     )),
     (our @ROLES = qw(
       Step Action ActionPolicy Predicate Value