KeyMangler example app
[scpubgit/DKit.git] / lib / DX / Tcl.pm
CommitLineData
91d1f239 1package DX::Tcl;
2
3use strictures 1;
4use curry;
5use DX::Solver;
6use DX::Lib::FS;
7use Term::ReadLine;
8use Devel::Dwarn;
9use Tcl;
10use Safe::Isa;
11use Moo;
12
13has solver => (is => 'lazy', builder => sub {
14 my $solver = DX::Solver->new(observation_policy => sub { 1 });
15 DX::Lib::FS->new->load_into($solver);
16 $solver;
17});
18
19has tcl => (is => 'lazy', builder => sub {
20 my ($self) = @_;
21 my $tcl = Tcl->new;
22 $tcl->CreateCommand('DX::_call' => $self->curry::weak::handle_tcl_call);
23 our $Tcl_Data ||= do { local $/; my $data = <DATA>; close(DATA); $data };
24 $tcl->Eval($Tcl_Data);
25 $tcl->call(namespace => import => 'DX::*');
26 return $tcl;
27});
28
29sub handle_tcl_call {
30 my ($self, undef, undef, undef, $call, @args) = @_;
31 return $self->${\"_tcl_${call}"}(@args);
32 #Dwarn(\@stuff);
33 return 1;
34}
35
36sub _tcl_rule_names {
37 my ($self) = @_;
38 return [ map /(.*)\/(\d+)/, keys %{$self->solver->rule_set->rules} ];
39}
40
41sub _tcl_add_rule {
42 my ($self, $name, $args, $raw_body) = @_;
43 my @args = $self->tcl->SplitList($args);
44 #warn $raw_body;
45 my $body = $self->_expand_tcl_data($raw_body);
46 #Dwarn([ $name, \@args, @$body ]);
47 $self->solver->add_rule($name => \@args => @$body);
48 return;
49}
50
51sub _tcl_first_result {
52 my ($self, $type, $raw_query) = @_;
53 my $query = $self->_expand_tcl_data($raw_query);
54 my $rs = $self->solver->$type(@$query);
55 my $result = $rs->next;
56 return $result ? $self->_implode_result($result) : 'false';
57}
58
59sub _tcl_each_result {
60 my ($self, $type, $raw_query) = @_;
61 my $query = $self->_expand_tcl_data($raw_query);
62 my $rs = $self->solver->$type(@$query);
63 return [ map $self->_implode_result($_), $rs->results ];
64}
65
66sub _tcl_ensure_result {
67 my ($self, $raw_query) = @_;
68 my $query = $self->_expand_tcl_data($raw_query);
69 my $result = $self->solver->ensure(@$query);
70 return $result ? $self->_implode_result($result) : 'false';
71}
72
73sub _implode_result {
74 my ($self, $result) = @_;
75 my $imploded = $self->_implode_hashref($result->all_values);
76 if (my @act = $result->actions) {
77 my @flat_act = map +($_->[0], $self->_implode_hashref($_->[1])),
78 map [ %{$_->as_structure} ], @act;
79 unshift @$imploded, +(_actions => \@flat_act);
80 }
81 return $imploded;
82}
83
84sub _implode_hashref {
85 my ($self, $values) = @_;
86 my @flat;
87 my %meta;
88 foreach my $key (sort keys %$values) {
89 my $v = $values->{$key};
90 if ($v->$_does('DX::Role::Set')) {
91 $meta{$key}{is_list} = 1;
92 push @flat, ($key => [ map "$_", $v->all ]);
93 } elsif (ref($v) and ref($v) eq 'ARRAY') {
94 $meta{$key}{is_list} = 1;
95 push @flat, ($key => [ map "$_", @$v ]);
96 } else {
97 push @flat, ($key => "$v");
98 }
99 }
100 my @flat_meta = map +($_ => [ %{$meta{$_}} ]), sort keys %meta;
101 return [ (@flat_meta ? (_meta => \@flat_meta) : ()), @flat ];
102}
103
104sub _expand_tcl_data {
105 my ($self, $data) = @_;
106 my ($type, @rest) = $self->tcl->SplitList($data);
107 die "No type" unless $type;
108 if ($type eq 'seq') {
109 return [ map $self->_expand_tcl_data($_), @rest ];
110 } elsif ($type eq 'call') {
111 my ($name, @args) = @rest;
112 return [ $name => map $self->_expand_tcl_data($_), @args ];
113 } elsif ($type eq 'var') {
114 return $rest[0];
115 } elsif ($type eq 'value') {
116 return \$rest[0]
117 }
118 die "Unknown type ${type}";
119}
120
121sub run { shift->repl }
122
123sub repl {
124 my ($self) = @_;
125 my $rl = Term::ReadLine->new;
126 my $tcl = $self->tcl;
127 my $cmd;
128
129 while (defined(my $line = $rl->readline(length($cmd)?'> ':'$ '))) {
130 $cmd .= "${line}\n";
131 my $out;
132 if ($tcl->call(info => complete => $cmd)) {
133 if (eval { $out = $tcl->Eval($cmd); 1 }) {
134 print $out;
135 if (length($out) and $out !~ /\n$/) { print "\n" }
136 } else {
137 $tcl->Eval(q{puts $::errorInfo});
138 }
139 $cmd = '';
140 }
141 }
142}
143
1441;
145
146__DATA__
147namespace eval DX {
148
149 variable current_body {}
150
151 proc rule {name args body} {
152 setup_eval_scope
153 _call add_rule $name $args [expand_body $body]
154 }
155
156 proc query {body {out ""}} {
157 setup_eval_scope
158 set res [_call first_result query [expand_body $body]]
159 return [format_response $res $out]
160 }
161
162 proc query* {body {out ""}} {
163 setup_eval_scope
164 set each_res [_call each_result query [expand_body $body]]
165 if [string length $out] {
166 set join_by ""
167 } else {
168 set join_by "--\n"
169 }
170 return [join [lmap res $each_res {format_response $res $out}] $join_by]
171 }
172
173 proc solve {body {out ""}} {
174 setup_eval_scope
175 set res [_call first_result solve [expand_body $body]]
176 return [format_response $res $out]
177 }
178
179 proc ensure {body {out ""}} {
180 setup_eval_scope
181 set res [_call ensure_result [expand_body $body]]
182 return [format_response $res $out]
183 }
184
185 proc format_response {res out {i ""}} {
186 if {[llength $res] < 2} {
187 return "${i}$res\n";
188 }
189 if [string length $out] {
190 return [dict with res $out]
191 }
192 set flat ""
193 if [dict exists $res _actions] {
194 foreach {n v} [dict get $res _actions] {
195 append flat "$n -> \n";
196 append flat [format_response $v "" " "]
197 }
198 append flat "->\n";
199 }
200 dict for {k v} $res {
201 if ![string match _* $k] {
202 if {[dict exists $res _meta $k is_list]
203 && [dict get $res _meta $k is_list]} {
204 append flat "$i$k:\n"
205 foreach l $v { append flat "$i $l\n" }
206 } else {
207 append flat "$i$k: $v\n"
208 }
209 }
210 }
211 return $flat
212 }
213
214 proc expand_body body {
215 variable current_body
216 set save_body $current_body
217 set current_body {seq}
218 namespace eval ::DX::Eval $body
219 set final_body $current_body
220 set current_body $save_body
221 return $final_body
222 }
223
224 proc body_add {name raw_args} {
225 variable current_body
226 lappend current_body [concat call $name [::DX::mangle_args $raw_args]]
227 }
228
229 proc setup_eval_scope {} {
230 set rules [ _call rule_names ];
231 foreach {name arity} $rules {
232 if ![llength [info procs ::DX::Eval::$name]] {
233 namespace inscope ::DX::Eval proc $name args [
234 concat DX::body_add $name {$args}
235 ]
236 }
237 }
238 }
239
240 proc mangle_arg {arg} {
241 if [regexp {^'(.*)'$} $arg -> value] {
242 return [list value $value]
243 }
244 if {[llength $arg] > 1} {
245 return [expand_body $arg]
246 }
247 return [list var $arg]
248 }
249
250 proc mangle_args {raw} {
251 return [lmap x $raw { mangle_arg $x }]
252 }
253
254 namespace eval ::DX::Eval {
255 proc findall args { DX::body_add findall $args }
256 proc foreach args { DX::body_add foreach $args }
257 proc not args { DX::body_add not $args }
258 }
259
260 namespace export rule query query* solve ensure
261}