mulang 5.3.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|