mulang 5.3.0 → 6.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 +4 -4
- data/bin/console +2 -2
- data/bin/mulang +0 -0
- data/lib/locales/en.yml +36 -56
- data/lib/locales/es.yml +35 -55
- data/lib/locales/operators.yml +96 -0
- data/lib/locales/pt.yml +33 -53
- data/lib/mulang.rb +53 -4
- data/lib/mulang/code.rb +36 -6
- data/lib/mulang/expectation.rb +44 -24
- data/lib/mulang/expectation/i18n.rb +11 -45
- data/lib/mulang/language.rb +43 -10
- data/lib/mulang/sexp.rb +80 -0
- data/lib/mulang/tokens.rb +275 -0
- data/lib/mulang/version.rb +2 -2
- metadata +22 -6
data/lib/mulang.rb
CHANGED
@@ -9,16 +9,65 @@ module Mulang
|
|
9
9
|
def self.bin_path
|
10
10
|
File.join(__dir__, '..', 'bin', 'mulang')
|
11
11
|
end
|
12
|
-
def self.analyse(analysis)
|
13
|
-
|
14
|
-
|
12
|
+
def self.analyse(analysis, **options)
|
13
|
+
arg, mode = Mulang::RunMode.for analysis, options
|
14
|
+
Open3.popen2(bin_path, arg) do |input, output, _thread|
|
15
|
+
input.puts mode.input(analysis).to_json
|
15
16
|
input.close
|
16
|
-
JSON.parse output.read
|
17
|
+
result = JSON.parse output.read
|
18
|
+
mode.output result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
module RunMode
|
25
|
+
def self.for(analysis, options)
|
26
|
+
serialization = options[:serialization]
|
27
|
+
many = analysis.is_a?(Array)
|
28
|
+
if many
|
29
|
+
[encode_serialization(serialization), Mulang::RunMode::Natural]
|
30
|
+
elsif serialization
|
31
|
+
[encode_serialization(serialization), Mulang::RunMode::ForcedMany]
|
32
|
+
else
|
33
|
+
["-s", Mulang::RunMode::Natural]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.encode_serialization(option)
|
38
|
+
case option
|
39
|
+
when nil then '-S'
|
40
|
+
when :bracket then '-B'
|
41
|
+
when :brace then '-C'
|
42
|
+
else raise "Unsupported serialization #{option}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Natural
|
47
|
+
def self.input(analysis)
|
48
|
+
analysis
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.output(result)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ForcedMany
|
57
|
+
def self.input(analysis)
|
58
|
+
[analysis]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.output(result)
|
62
|
+
result[0]
|
63
|
+
end
|
17
64
|
end
|
18
65
|
end
|
19
66
|
end
|
20
67
|
|
68
|
+
require_relative './mulang/tokens'
|
21
69
|
require_relative './mulang/inspection'
|
22
70
|
require_relative './mulang/expectation'
|
23
71
|
require_relative './mulang/code'
|
24
72
|
require_relative './mulang/language'
|
73
|
+
require_relative './mulang/sexp'
|
data/lib/mulang/code.rb
CHANGED
@@ -1,12 +1,29 @@
|
|
1
1
|
module Mulang
|
2
2
|
class Code
|
3
|
+
attr_accessor :language, :content
|
3
4
|
def initialize(language, content)
|
4
5
|
@language = language
|
5
6
|
@content = content
|
6
7
|
end
|
7
8
|
|
8
|
-
def
|
9
|
-
@language.
|
9
|
+
def identifiers(**options)
|
10
|
+
@language.identifiers @content, **options
|
11
|
+
end
|
12
|
+
|
13
|
+
def ast(**options)
|
14
|
+
@language.ast @content, **options
|
15
|
+
end
|
16
|
+
|
17
|
+
def ast_analysis(**options)
|
18
|
+
@language.ast_analysis @content, **options
|
19
|
+
end
|
20
|
+
|
21
|
+
def transformed_asts(operations, **options)
|
22
|
+
@language.transformed_asts @content, operations, **options
|
23
|
+
end
|
24
|
+
|
25
|
+
def transformed_asts_analysis(operations, **options)
|
26
|
+
@language.transformed_asts_analysis @content, operations, **options
|
10
27
|
end
|
11
28
|
|
12
29
|
def sample
|
@@ -17,8 +34,8 @@ module Mulang
|
|
17
34
|
{ sample: sample, spec: spec }
|
18
35
|
end
|
19
36
|
|
20
|
-
def analyse(spec)
|
21
|
-
Mulang.analyse analysis(spec)
|
37
|
+
def analyse(spec, **options)
|
38
|
+
Mulang.analyse analysis(spec), **options
|
22
39
|
end
|
23
40
|
|
24
41
|
def expect(binding='*', inspection)
|
@@ -47,12 +64,25 @@ module Mulang
|
|
47
64
|
new Mulang::Language::External.new(&tool), content
|
48
65
|
end
|
49
66
|
|
50
|
-
def self.
|
51
|
-
|
67
|
+
def self.analyse_many(codes, spec, **options)
|
68
|
+
run_many(codes, **options) { |it| it.analysis(spec) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.ast_many(codes, **options)
|
72
|
+
run_many(codes, key: 'outputAst', **options) { |it| it.ast_analysis(**options) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.transformed_asts_many(codes, operations, **options)
|
76
|
+
run_many(codes, key: 'transformedAsts', **options) { |it| it.transformed_asts_analysis(operations, **options) }
|
52
77
|
end
|
53
78
|
|
54
79
|
private
|
55
80
|
|
81
|
+
def self.run_many(codes, key: nil, **options)
|
82
|
+
result = Mulang.analyse(codes.map { |it| yield it }, **options)
|
83
|
+
key ? result.map { |it| it[key] } : result
|
84
|
+
end
|
85
|
+
|
56
86
|
def expectation_results_for(result)
|
57
87
|
raise result['reason'] if result['tag'] == 'AnalysisFailed'
|
58
88
|
result['expectationResults']
|
data/lib/mulang/expectation.rb
CHANGED
@@ -1,43 +1,63 @@
|
|
1
1
|
module Mulang::Expectation
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
LOGIC_SMELLS = %w(
|
3
|
+
HasRedundantReduction
|
4
|
+
UsesCut
|
5
|
+
UsesFail
|
6
|
+
UsesUnificationOperator
|
7
|
+
)
|
8
|
+
|
9
|
+
FUNCTIONAL_SMELLS = %w(
|
10
|
+
HasRedundantGuards
|
11
|
+
HasRedundantParameter
|
12
|
+
ShouldUseOtherwise
|
13
|
+
)
|
14
|
+
|
15
|
+
OBJECT_ORIENTED_SMELLS = %w(
|
5
16
|
DoesNilTest
|
6
17
|
DoesNullTest
|
7
|
-
|
8
|
-
|
18
|
+
HasTooManyMethods
|
19
|
+
OverridesEqualOrHashButNotBoth
|
20
|
+
ReturnsNil
|
21
|
+
ReturnsNull
|
22
|
+
)
|
23
|
+
|
24
|
+
IMPERATIVE_SMELLS = %w(
|
9
25
|
HasAssignmentCondition
|
26
|
+
HasAssignmentReturn
|
27
|
+
HasEmptyRepeat
|
28
|
+
HasRedundantLocalVariableReturn
|
29
|
+
HasRedundantRepeat
|
30
|
+
)
|
31
|
+
|
32
|
+
EXPRESSIVENESS_SMELLS = %w(
|
33
|
+
HasMisspelledBindings
|
34
|
+
HasMisspelledIdentifiers
|
35
|
+
HasTooShortIdentifiers
|
36
|
+
HasWrongCaseBinding
|
37
|
+
HasWrongCaseIdentifiers
|
38
|
+
)
|
39
|
+
|
40
|
+
GENERIC_SMELLS = %w(
|
41
|
+
DiscardsExceptions
|
42
|
+
DoesConsolePrint
|
43
|
+
DoesTypeTest
|
10
44
|
HasCodeDuplication
|
11
45
|
HasDeclarationTypos
|
12
46
|
HasEmptyIfBranches
|
13
|
-
|
47
|
+
HasEqualIfBranches
|
14
48
|
HasLongParameterList
|
15
|
-
HasMisspelledBindings
|
16
|
-
HasMisspelledIdentifiers
|
17
49
|
HasRedundantBooleanComparison
|
18
|
-
HasRedundantGuards
|
19
50
|
HasRedundantIf
|
20
51
|
HasRedundantLambda
|
21
|
-
HasRedundantLocalVariableReturn
|
22
|
-
HasRedundantParameter
|
23
|
-
HasRedundantReduction
|
24
|
-
HasRedundantRepeat
|
25
|
-
HasTooManyMethods
|
26
52
|
HasTooShortBindings
|
27
|
-
HasTooShortIdentifiers
|
28
53
|
HasUnreachableCode
|
29
54
|
HasUsageTypos
|
30
|
-
HasWrongCaseBinding
|
31
|
-
HasWrongCaseIdentifiers
|
32
55
|
IsLongCode
|
33
|
-
OverridesEqualOrHashButNotBoth
|
34
|
-
ReturnsNil
|
35
|
-
ReturnsNull
|
36
56
|
ShouldInvertIfCondition
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
57
|
+
ShouldUseStrictComparators
|
58
|
+
)
|
59
|
+
|
60
|
+
SMELLS = GENERIC_SMELLS + EXPRESSIVENESS_SMELLS + IMPERATIVE_SMELLS + OBJECT_ORIENTED_SMELLS + FUNCTIONAL_SMELLS + LOGIC_SMELLS
|
41
61
|
|
42
62
|
def self.guess_type(expectation)
|
43
63
|
if expectation[:binding] == '<<custom>>'
|
@@ -1,47 +1,5 @@
|
|
1
1
|
module Mulang::Expectation::I18n
|
2
2
|
class << self
|
3
|
-
DEFAULT_TOKENS = {
|
4
|
-
keyword_EntryPoint: 'program',
|
5
|
-
keyword_Fail: 'fail',
|
6
|
-
keyword_False: 'false',
|
7
|
-
keyword_findall: 'findall',
|
8
|
-
keyword_For: 'for',
|
9
|
-
keyword_Forall: 'forall',
|
10
|
-
keyword_Foreach: 'foreach',
|
11
|
-
keyword_If: 'if',
|
12
|
-
keyword_Is: 'is',
|
13
|
-
keyword_Not: 'not',
|
14
|
-
keyword_Null: 'null',
|
15
|
-
keyword_Repeat: 'repeat',
|
16
|
-
keyword_Switch: 'switch',
|
17
|
-
keyword_True: 'true',
|
18
|
-
keyword_While: 'while',
|
19
|
-
keyword_Yield: 'yield',
|
20
|
-
operator_And: '&&',
|
21
|
-
operator_BackwardComposition: '.',
|
22
|
-
operator_Divide: '/',
|
23
|
-
operator_Equal: '==',
|
24
|
-
operator_ForwardComposition: '>>',
|
25
|
-
operator_GreatherOrEqualThan: '>=',
|
26
|
-
operator_GreatherThan: '>',
|
27
|
-
operator_Hash: 'hash',
|
28
|
-
operator_LessOrEqualThan: '<=',
|
29
|
-
operator_LessThan: '<',
|
30
|
-
operator_Minus: '-',
|
31
|
-
operator_Multiply: '*',
|
32
|
-
operator_Negation: '!',
|
33
|
-
operator_NotEqual: '!=',
|
34
|
-
operator_Or: '||',
|
35
|
-
operator_Otherwise: 'otherwise',
|
36
|
-
operator_Plus: '+',
|
37
|
-
operator_Modulo: '%',
|
38
|
-
operator_BitwiseOr: '|',
|
39
|
-
operator_BitwiseAnd: '&',
|
40
|
-
operator_BitwiseXor: '^',
|
41
|
-
operator_BitwiseLeftShift: '<<',
|
42
|
-
operator_BitwiseRightShift: '>>'
|
43
|
-
}.transform_values { |v| CGI::escapeHTML(v) }.freeze
|
44
|
-
|
45
3
|
def translate(e, tokens = nil)
|
46
4
|
translate!(e, tokens)
|
47
5
|
rescue
|
@@ -71,7 +29,7 @@ module Mulang::Expectation::I18n
|
|
71
29
|
end
|
72
30
|
|
73
31
|
def t_binding(binding)
|
74
|
-
binding == '*' ? ::I18n.t("mulang.expectation.solution") : "<
|
32
|
+
binding == '*' ? ::I18n.t("mulang.expectation.solution") : "<code>#{Mulang::Inspection.parse_binding_name binding}</code>"
|
75
33
|
end
|
76
34
|
|
77
35
|
def t_must(parsed)
|
@@ -79,7 +37,7 @@ module Mulang::Expectation::I18n
|
|
79
37
|
end
|
80
38
|
|
81
39
|
def t_target(parsed)
|
82
|
-
"<
|
40
|
+
"<code>#{parsed.target.value}</code>" if parsed.target
|
83
41
|
end
|
84
42
|
|
85
43
|
def t_matching(tokens, parsed)
|
@@ -87,7 +45,15 @@ module Mulang::Expectation::I18n
|
|
87
45
|
end
|
88
46
|
|
89
47
|
def with_tokens(tokens, params)
|
90
|
-
|
48
|
+
hash = if tokens.nil?
|
49
|
+
{}
|
50
|
+
elsif tokens.is_a?(Hash)
|
51
|
+
tokens
|
52
|
+
else
|
53
|
+
params.merge(Mulang::Tokens::TOKENS.indifferent_get(tokens))
|
54
|
+
end
|
55
|
+
|
56
|
+
params.merge(Mulang::Tokens::DEFAULT_TOKENS.merge(hash))
|
91
57
|
end
|
92
58
|
end
|
93
59
|
end
|
data/lib/mulang/language.rb
CHANGED
@@ -1,19 +1,52 @@
|
|
1
1
|
module Mulang::Language
|
2
|
-
class
|
3
|
-
def
|
4
|
-
|
2
|
+
class Base
|
3
|
+
def identifiers(content, **options)
|
4
|
+
Mulang.analyse(identifiers_analysis(content, **options), **options)['outputIdentifiers'] rescue nil
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def identifiers_analysis(content, **options)
|
8
|
+
base_analysis content, {includeOutputIdentifiers: true}, **options
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def transformed_asts(content, operations, **options)
|
12
|
+
Mulang.analyse(transformed_asts_analysis(content, operations, **options), **options)['transformedAsts'] rescue nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def transformed_asts_analysis(content, operations, **options)
|
16
|
+
base_analysis content, {transformationSpecs: operations}, **options
|
17
|
+
end
|
18
|
+
|
19
|
+
def normalization_options(**options)
|
20
|
+
options.except(:serialization).presence
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def base_analysis(content, spec, **options)
|
12
26
|
{
|
13
|
-
sample:
|
14
|
-
spec: {
|
27
|
+
sample: sample(content),
|
28
|
+
spec: {
|
29
|
+
expectations: [],
|
30
|
+
smellsSet: { tag: 'NoSmells' },
|
31
|
+
includeOutputAst: false,
|
32
|
+
normalizationOptions: normalization_options(options)
|
33
|
+
}.merge(spec).compact
|
15
34
|
}
|
16
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Native < Base
|
39
|
+
def initialize(language)
|
40
|
+
@language = language
|
41
|
+
end
|
42
|
+
|
43
|
+
def ast(content, **options)
|
44
|
+
Mulang.analyse(ast_analysis(content, **options), **options)['outputAst'] rescue nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def ast_analysis(content, **options)
|
48
|
+
base_analysis content, {includeOutputAst: true}, **options
|
49
|
+
end
|
17
50
|
|
18
51
|
def sample(content)
|
19
52
|
{
|
@@ -24,12 +57,12 @@ module Mulang::Language
|
|
24
57
|
end
|
25
58
|
end
|
26
59
|
|
27
|
-
class External
|
60
|
+
class External < Base
|
28
61
|
def initialize(&tool)
|
29
62
|
@tool = block_given? ? tool : proc { |it| it }
|
30
63
|
end
|
31
64
|
|
32
|
-
def ast(content)
|
65
|
+
def ast(content, **args)
|
33
66
|
@tool.call(content) rescue nil
|
34
67
|
end
|
35
68
|
|
data/lib/mulang/sexp.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Mulang
|
2
|
+
module Sexp
|
3
|
+
|
4
|
+
# Basic S-expression
|
5
|
+
|
6
|
+
def ms(tag, *contents)
|
7
|
+
if contents.empty?
|
8
|
+
{tag: tag}
|
9
|
+
elsif contents.size == 1
|
10
|
+
{tag: tag, contents: contents.first}
|
11
|
+
else
|
12
|
+
{tag: tag, contents: contents}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Basic elements
|
17
|
+
|
18
|
+
def primitive(operator)
|
19
|
+
ms(:Primitive, operator)
|
20
|
+
end
|
21
|
+
|
22
|
+
def sequence(*contents)
|
23
|
+
if contents.empty?
|
24
|
+
none
|
25
|
+
elsif contents.size == 1
|
26
|
+
contents[0]
|
27
|
+
else
|
28
|
+
ms(:Sequence, *contents)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def none
|
33
|
+
ms(:None)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Callables
|
37
|
+
|
38
|
+
def simple_function(name, args, body)
|
39
|
+
callable :Function, name, args, body
|
40
|
+
end
|
41
|
+
|
42
|
+
def simple_method(name, args, body)
|
43
|
+
callable :Method, name, args, body
|
44
|
+
end
|
45
|
+
|
46
|
+
def primitive_method(name, args, body)
|
47
|
+
callable :PrimitiveMethod, name, args, body
|
48
|
+
end
|
49
|
+
|
50
|
+
def callable(type, name, args, body)
|
51
|
+
{
|
52
|
+
tag: type,
|
53
|
+
contents: [
|
54
|
+
name,
|
55
|
+
[
|
56
|
+
[ args, {tag: :UnguardedBody, contents: body }]
|
57
|
+
]
|
58
|
+
]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Applications
|
63
|
+
|
64
|
+
def primitive_send(sender, op, args)
|
65
|
+
ms(:Send, sender, primitive(op), args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def simple_send(sender, message, args)
|
69
|
+
ms(:Send, sender, ms(:Reference, message), args)
|
70
|
+
end
|
71
|
+
|
72
|
+
def application(name, args)
|
73
|
+
ms :Application, [ms(:Reference, name), args]
|
74
|
+
end
|
75
|
+
|
76
|
+
def binary_application(operator, left, right)
|
77
|
+
application operator, [left, right]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|