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.
@@ -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