mulang 5.3.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- Open3.popen2(bin_path, '-s') do |input, output, _thread|
14
- input.puts analysis.to_json
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'
@@ -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 ast
9
- @language.ast @content
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.ast(ast)
51
- new Mulang::Language::External.new, ast
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']
@@ -1,43 +1,63 @@
1
1
  module Mulang::Expectation
2
- SMELLS = %w(
3
- DiscardsExceptions
4
- DoesConsolePrint
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
- DoesTypeTest
8
- HasAssignmentReturn
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
- HasEmptyRepeat
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
- ShouldUseOtherwise
38
- UsesCut
39
- UsesFail
40
- UsesUnificationOperator)
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") : "<strong>#{Mulang::Inspection.parse_binding_name binding}</strong>"
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
- "<strong>#{parsed.target.value}</strong>" if parsed.target
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
- params.merge(DEFAULT_TOKENS).merge(tokens || {})
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
@@ -1,19 +1,52 @@
1
1
  module Mulang::Language
2
- class Native
3
- def initialize(language)
4
- @language = language
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 ast(content)
8
- Mulang.analyse(ast_analysis(content))['intermediateLanguage'] rescue nil
7
+ def identifiers_analysis(content, **options)
8
+ base_analysis content, {includeOutputIdentifiers: true}, **options
9
9
  end
10
10
 
11
- def ast_analysis(content)
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: { tag: 'CodeSample', language: @language, content: content },
14
- spec: { expectations: [], smellsSet: { tag: 'NoSmells' }, includeIntermediateLanguage: true }
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
 
@@ -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