mumuki-prolog-runner 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 13d4e23a78247d40578287655fbd10a6ed83a2fd
4
+ data.tar.gz: 335636f3b50c7b702dc2e80538e13a10cb7b2a6b
5
+ SHA512:
6
+ metadata.gz: 08ac692949314e4641f3cc96718577fb776233e1c6fb4ad5240f0640fa444b9a0f59223039e080b696b5dd033edf05871eeff90e3c07793a952003b1cf4c9f23
7
+ data.tar.gz: 5ed272405a23dff488b813261ae7af1209b9ac805ffb84a21382f06a94b84bb3f6c3409325d8ef2a9ab22e11bc5d5a0888b11b2bac6ae83235e94619b953a249
@@ -0,0 +1,63 @@
1
+ require 'json'
2
+ require 'mumukit/inspection'
3
+
4
+ class Mumukit::Inspection::PlainInspection
5
+ def to_term
6
+ "inspection('#{type}')"
7
+ end
8
+ end
9
+
10
+ class Mumukit::Inspection::TargetedInspection
11
+ def to_term
12
+ "inspection('#{type}',#{Integer(target)})" rescue "inspection('#{type}','#{target}')"
13
+ end
14
+ end
15
+
16
+ class Mumukit::Inspection::NegatedInspection
17
+ def to_term
18
+ "not(#{@inspection.to_term})"
19
+ end
20
+ end
21
+
22
+ class PrologExpectationsHook < Mumukit::Templates::FileHook
23
+ isolated true
24
+
25
+ def command_line(filename)
26
+ "swipl -f #{filename} --quiet -t main 2>&1"
27
+ end
28
+
29
+ def compile_file_content(request)
30
+ ExpectationsFile.new(request).render
31
+ end
32
+
33
+ def post_process_file(file, result, status)
34
+ JSON.parse(result)['expectationResults'] rescue []
35
+ end
36
+ end
37
+
38
+ class ExpectationsFile
39
+ def initialize(request)
40
+ @content = request.content
41
+ @expectation_terms = self.class.expectations_to_terms(request.expectations)
42
+ end
43
+
44
+ def self.expectations_to_terms(expectations)
45
+ '[' + expectations.map do |e|
46
+ "expectation('#{e['binding']}',#{inspection_to_term(e['inspection'])})"
47
+ end.join(',') + ']'
48
+ end
49
+
50
+ def self.inspection_to_term(s)
51
+ Mumukit::Inspection.parse(s).to_term
52
+ end
53
+
54
+ def render
55
+ ERB.new(File.read(template_path)).result(binding)
56
+ end
57
+
58
+ private
59
+
60
+ def template_path
61
+ File.expand_path '../main.pl.erb', __FILE__
62
+ end
63
+ end
@@ -0,0 +1,71 @@
1
+ class PrologFeedbackHook < Mumukit::Hook
2
+ def run!(request, results)
3
+ content = request.content
4
+ test_results = results.test_results[0]
5
+
6
+ PrologExplainer.new.explain(content, test_results)
7
+ end
8
+
9
+ class PrologExplainer < Mumukit::Explainer
10
+ def explain_wrong_distinct_operator(content, _)
11
+ (/.{0,9}(\/=|<>|!=).{0,9}/.match content).try do |it|
12
+ {near: it[0]}
13
+ end
14
+ end
15
+
16
+ def explain_wrong_gte_operator(content, _)
17
+ (/.{0,9}(=>).{0,9}/.match content).try do |it|
18
+ {near: it[0]}
19
+ end
20
+ end
21
+
22
+ def explain_wrong_lte_operator(content, _)
23
+ (/.{0,9}(<=).{0,9}/.match content).try do |it|
24
+ {near: it[0]}
25
+ end
26
+ end
27
+
28
+
29
+ def explain_clauses_not_together(_, test_results)
30
+ (/Clauses of .*:(.*) are not together in the source-file/.match test_results).try do |it|
31
+ {target: it[1]}
32
+ end
33
+ end
34
+
35
+ def explain_singleton_variables(_, test_results)
36
+ (/Singleton variables: \[(.*)\]/.match test_results).try do |it|
37
+ {target: it[1]}
38
+ end
39
+ end
40
+
41
+ def explain_test_failed(_, test_results)
42
+ /test (.*): failed/ =~ test_results
43
+ end
44
+
45
+ def explain_not_sufficiently_instantiated(_, test_results)
46
+ (/received error: (.*): Arguments are not sufficiently instantiated/.match test_results).try do |it|
47
+ {target: it[1]}
48
+ end
49
+ end
50
+
51
+ def explain_operator_error(_, test_results)
52
+ /ERROR: (.*): Syntax error: Operator expected/ =~ test_results
53
+ end
54
+
55
+ def explain_missing_dot_error(_, test_results)
56
+ /ERROR: (.*): Syntax error: Operator priority clash/ =~ test_results
57
+ end
58
+
59
+
60
+ def explain_wrong_comma(_, test_results)
61
+ /ERROR: (.*): Full stop in clause-body\? Cannot redefine ,\/2/ =~ test_results
62
+ end
63
+
64
+ def explain_missing_predicate(_, test_results)
65
+ (/.*:(.*): Undefined procedure: .*:(.*)/.match test_results).try do |it|
66
+ {target: it[1],
67
+ missing: it[2]} unless it[1].include? 'unit body'
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/main.pl.erb ADDED
@@ -0,0 +1,107 @@
1
+ run_expectations(Expectations, Results) :-
2
+ maplist(run_expectation, Expectations, Results).
3
+
4
+ run_expectation(Expectation, result(Expectation, Result)) :- eval_expectation(Expectation,Result).
5
+
6
+ eval_expectation(expectation(Binding, Inspection), true) :-
7
+ call_expectation(Binding, Inspection), !.
8
+ eval_expectation(_, false).
9
+
10
+ call_expectation(Binding, inspection('HasBinding')) :-
11
+ usesPredicate(pred(Binding, _), _).
12
+ call_expectation(Binding, inspection('HasForall')) :-
13
+ hasForall(pred(Binding, _)).
14
+ call_expectation(Binding, inspection('HasFindall')) :-
15
+ hasFindall(pred(Binding, _)).
16
+ call_expectation(Binding, inspection('HasNot')) :-
17
+ hasNot(pred(Binding, _)).
18
+ call_expectation(Binding, inspection('HasUsage', Other)) :-
19
+ usesPredicate(pred(Binding, _), pred(Other, _)).
20
+ call_expectation(Binding, inspection('HasDirectRecursion')) :-
21
+ recursive(pred(Binding, _)).
22
+ call_expectation(Binding, inspection('HasArity', Arity)) :-
23
+ usesPredicate(pred(Binding, Arity), _).
24
+ call_expectation(Binding, not(Inspection)) :-
25
+ \+ call_expectation(Binding, Inspection).
26
+ call_expectation(Binding, inspection('HasCut')) :-
27
+ hasCut(pred(Binding, _)).
28
+
29
+ <%= @content %>
30
+
31
+ predToHead(pred(PredicateName, Arity), Head) :-
32
+ between(0, 9, Arity),
33
+ functor(Head, PredicateName, Arity).
34
+
35
+ clauseElements(Predicate, Elements):-
36
+ predToHead(Predicate, Head),
37
+ clause(Head, Clause),
38
+ clauseToList(Clause, Elements).
39
+
40
+ clauseToList((A,B), [A | BToList]):- clauseToList(B, BToList).
41
+ clauseToList(A, [A]):- functor(A,Name,_), Name \= (',').
42
+
43
+ usesPredicate(Predicate, UsedPredicate):-
44
+ clauseElements(Predicate, Elements),
45
+ member(ClauseElement, Elements),
46
+ isOrContains(ClauseElement, UsedPredicate).
47
+
48
+ isOrContains(ClauseElement, Predicate):- predToHead(Predicate, ClauseElement).
49
+ isOrContains(ClauseElement, Predicate):-
50
+ ClauseElement =.. [_|Args],
51
+ member(Arg, Args),
52
+ not(var(Arg)),
53
+ isOrContains(Arg, Predicate).
54
+
55
+ %% This would require a parametrized expectation
56
+ numberOfClauses(Predicate, Amount):-
57
+ predToHead(Predicate, Head),
58
+ findall(_,clause(Head,_), Clauses),
59
+ length(Clauses, Amount).
60
+
61
+ recursive(Predicate):-
62
+ usesPredicate(Predicate, Predicate).
63
+
64
+ hasForall(Predicate):-
65
+ usesPredicate(Predicate, pred(forall,2)).
66
+
67
+ hasNot(Predicate):-
68
+ usesPredicate(Predicate, pred(not,1)).
69
+
70
+ hasFindall(Predicate):-
71
+ usesPredicate(Predicate, pred(findall,3)).
72
+
73
+ hasCut(Predicate):-
74
+ usesPredicate(Predicate, pred(!,0)).
75
+
76
+
77
+ main(_) :-
78
+ Expectations = <%= @expectation_terms %>,
79
+ run_expectations(Expectations, Results),
80
+ write_json(Results).
81
+
82
+ write_json(Results) :-
83
+ write('{"expectationResults":['),
84
+ write_results(Results),
85
+ write(']}').
86
+
87
+ write_results([]).
88
+ write_results([X1]) :-
89
+ write_result(X1).
90
+ write_results([X1,X2|Xs]) :-
91
+ write_result(X1),
92
+ write(','),
93
+ write_results([X2|Xs]).
94
+
95
+ write_result(result(expectation(Binding, Inspection), Result)) :-
96
+ swrite_inspection(Inspection, SInspection),
97
+ writef('{"expectation":{"binding":"%w", "inspection":"%w"},"result":%w}',
98
+ [Binding, SInspection, Result]).
99
+
100
+ swrite_inspection(inspection(X), S) :-
101
+ swritef(S, "%w", [X]).
102
+ swrite_inspection(inspection(X, Y), S) :-
103
+ swritef(S, "%w:%w", [X, Y]).
104
+ swrite_inspection(not(Inspection), S2) :-
105
+ swrite_inspection(Inspection, S1),
106
+ swritef(S2, "Not:%w", [S1]).
107
+
@@ -0,0 +1,16 @@
1
+ class PrologMetadataHook < Mumukit::Hook
2
+ def metadata
3
+ {language: {
4
+ name: 'prolog',
5
+ icon: {type: 'devicon', name: 'prolog'},
6
+ version: 'swi-prolog 6.6.4',
7
+ extension: 'pl',
8
+ ace_mode: 'prolog',
9
+ prompt: '?'
10
+ },
11
+ test_framework: {
12
+ name: 'plunit',
13
+ test_extension: 'pl'
14
+ }}
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'i18n'
2
+
3
+ I18n.load_path += Dir[File.join('.', 'locales', '*.yml')]
4
+
5
+ require 'mumukit'
6
+
7
+ Mumukit.runner_name = 'prolog'
8
+ Mumukit.configure do |config|
9
+ config.docker_image = 'mumuki/mumuki-plunit-worker'
10
+ config.content_type = 'markdown'
11
+ end
12
+
13
+ require_relative 'test_hook'
14
+ require_relative 'query_hook'
15
+ require_relative 'expectations_hook'
16
+ require_relative 'metadata_hook'
17
+ require_relative 'feedback_hook'
18
+
19
+
data/lib/query_hook.rb ADDED
@@ -0,0 +1,79 @@
1
+ class PrologQueryHook < Mumukit::Templates::FileHook
2
+ isolated true
3
+
4
+ def command_line(filename)
5
+ "swipl -f #{filename} --quiet -t main 2>&1"
6
+ end
7
+
8
+ def compile_file_content(req)
9
+ <<PROLOG
10
+ #{req.extra}
11
+ #{req.content}
12
+
13
+ main(_):-
14
+ run_query('#{req.query.gsub('\\', '\\' * 3 )}').
15
+
16
+ run_query(Query):-
17
+ catch(findall(Result, (atom_to_term(Query, Term, Result), Term), ResultSet),
18
+ error(TypeError,_),
19
+ (handleQueryError(TypeError, Query), halt(100)) ),
20
+ prettyWriteResultSet(ResultSet).
21
+
22
+ handleQueryError(type_error(callable,_), Query):-
23
+ writef('ERROR: run_query/1: Expected Callable predicate but instead got %w\\n', [Query]).
24
+
25
+ handleQueryError(syntax_error(TypeSyntaxError), Query):-
26
+ writef('ERROR: run_query/1: Syntax Error: %w in %w\\n', [TypeSyntaxError, Query]).
27
+
28
+ handleQueryError(signal(_,Number), _):-
29
+ SignalStatus is 128 + Number,
30
+ halt(SignalStatus).
31
+
32
+ handleQueryError(GeneralError, Query):-
33
+ writef('ERROR: run_query/1: %w in \\'%w\\'\\n', [GeneralError, Query]).
34
+
35
+ prettyWriteResultSet([]):-
36
+ writeln('no.').
37
+
38
+ prettyWriteResultSet([OneResult]):-
39
+ prettyWriteOneResult(OneResult),
40
+ writeln('.').
41
+
42
+ prettyWriteResultSet([OneResult | ResultSet]):-
43
+ ResultSet \\= [],
44
+ prettyWriteOneResult(OneResult),
45
+ writeln(' ;'),
46
+ prettyWriteResultSet(ResultSet).
47
+
48
+ prettyWriteOneResult([]):-
49
+ write('yes').
50
+
51
+ prettyWriteOneResult([OneBinding]):-
52
+ writeBinding(OneBinding).
53
+
54
+ prettyWriteOneResult([OneBinding | OneResult]):-
55
+ OneResult \\= [],
56
+ writeBinding(OneBinding),
57
+ writeln(','),
58
+ prettyWriteOneResult(OneResult).
59
+
60
+ writeBinding(OneBinding):-
61
+ OneBinding=..[(=), VarName, Value],
62
+ writef('%w = %w', [VarName, Value]).
63
+
64
+ writeBinding(NotABinding):-
65
+ not(NotABinding=..[(=) | _ ]),
66
+ writef('ERROR: writeBinding/1: Expected Binding, but no equals was found in: %w\\n', [NotABinding]),
67
+ halt(101).
68
+
69
+ PROLOG
70
+ end
71
+
72
+ end
73
+
74
+
75
+
76
+
77
+
78
+
79
+
data/lib/test_hook.rb ADDED
@@ -0,0 +1,31 @@
1
+ class PrologTestHook < Mumukit::Templates::FileHook
2
+ isolated true
3
+
4
+ def post_process_file(file, result, status)
5
+ if /ERROR: #{file.path}:.*: Syntax error: .*/ =~ result
6
+ [format_code(result), :failed]
7
+ elsif /Caught signal 24 \(xcpu\)/ =~ result
8
+ [format_code(I18n.t(:time_exceeded)), :failed]
9
+ else
10
+ [format_code(result), status]
11
+ end
12
+ end
13
+
14
+ def command_line(filename)
15
+ "swipl -f #{filename} --quiet -t run_tests 2>&1"
16
+ end
17
+
18
+ def format_code(code)
19
+ "```\n#{code}\n```"
20
+ end
21
+
22
+ def compile_file_content(request)
23
+ <<EOF
24
+ :- begin_tests(mumuki_submission_test, []).
25
+ #{request.test}
26
+ #{request.content}
27
+ #{request.extra}
28
+ :- end_tests(mumuki_submission_test).
29
+ EOF
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mumuki-prolog-runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Franco Leonardo Bulgarelli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mumukit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: codeclimate-test-reporter
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mumukit-bridge
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ description:
98
+ email:
99
+ - franco@mumuki.org
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - lib/expectations_hook.rb
105
+ - lib/feedback_hook.rb
106
+ - lib/main.pl.erb
107
+ - lib/metadata_hook.rb
108
+ - lib/prolog_runner.rb
109
+ - lib/query_hook.rb
110
+ - lib/test_hook.rb
111
+ homepage: http://github.com/mumuki/mumuki-prolog-server
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.4.8
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Prolog Runner for Mumuki
135
+ test_files: []