carbon-compiler 0.2.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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +17 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +39 -0
  6. data/.travis.yml +5 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +41 -0
  11. data/Rakefile +9 -0
  12. data/Vagrantfile +84 -0
  13. data/carbon-compiler.gemspec +28 -0
  14. data/lib/carbon/compiler.rb +20 -0
  15. data/lib/carbon/compiler/directive.rb +48 -0
  16. data/lib/carbon/compiler/directive/import.rb +17 -0
  17. data/lib/carbon/compiler/errors.rb +7 -0
  18. data/lib/carbon/compiler/location.rb +136 -0
  19. data/lib/carbon/compiler/metanostic.rb +123 -0
  20. data/lib/carbon/compiler/metanostic/defaults.rb +41 -0
  21. data/lib/carbon/compiler/metanostic/defaults.yml +138 -0
  22. data/lib/carbon/compiler/metanostic/diagnostic.rb +112 -0
  23. data/lib/carbon/compiler/metanostic/list.rb +109 -0
  24. data/lib/carbon/compiler/metanostic/mode.rb +162 -0
  25. data/lib/carbon/compiler/metanostic/state.rb +174 -0
  26. data/lib/carbon/compiler/metanostic/template.erb +11 -0
  27. data/lib/carbon/compiler/node.rb +18 -0
  28. data/lib/carbon/compiler/node/base.rb +213 -0
  29. data/lib/carbon/compiler/node/definition.rb +19 -0
  30. data/lib/carbon/compiler/node/definition/class.rb +24 -0
  31. data/lib/carbon/compiler/node/definition/class/element.rb +18 -0
  32. data/lib/carbon/compiler/node/definition/directive.rb +22 -0
  33. data/lib/carbon/compiler/node/definition/directive/function.rb +18 -0
  34. data/lib/carbon/compiler/node/definition/enum.rb +20 -0
  35. data/lib/carbon/compiler/node/definition/enum/element.rb +17 -0
  36. data/lib/carbon/compiler/node/definition/function.rb +44 -0
  37. data/lib/carbon/compiler/node/definition/function/body.rb +17 -0
  38. data/lib/carbon/compiler/node/definition/function/name.rb +18 -0
  39. data/lib/carbon/compiler/node/definition/function/parameter.rb +18 -0
  40. data/lib/carbon/compiler/node/definition/function/parameters.rb +17 -0
  41. data/lib/carbon/compiler/node/definition/module.rb +23 -0
  42. data/lib/carbon/compiler/node/definition/struct.rb +24 -0
  43. data/lib/carbon/compiler/node/definition/struct/element.rb +18 -0
  44. data/lib/carbon/compiler/node/etype.rb +66 -0
  45. data/lib/carbon/compiler/node/etype/option.rb +25 -0
  46. data/lib/carbon/compiler/node/etype/star.rb +13 -0
  47. data/lib/carbon/compiler/node/expression.rb +18 -0
  48. data/lib/carbon/compiler/node/expression/assignment.rb +22 -0
  49. data/lib/carbon/compiler/node/expression/call.rb +22 -0
  50. data/lib/carbon/compiler/node/expression/call/access.rb +24 -0
  51. data/lib/carbon/compiler/node/expression/call/attribute.rb +23 -0
  52. data/lib/carbon/compiler/node/expression/call/enum.rb +25 -0
  53. data/lib/carbon/compiler/node/expression/call/module.rb +24 -0
  54. data/lib/carbon/compiler/node/expression/call/parameters.rb +17 -0
  55. data/lib/carbon/compiler/node/expression/call/self.rb +17 -0
  56. data/lib/carbon/compiler/node/expression/call/unified.rb +27 -0
  57. data/lib/carbon/compiler/node/expression/literal.rb +83 -0
  58. data/lib/carbon/compiler/node/expression/operation.rb +20 -0
  59. data/lib/carbon/compiler/node/expression/operation/and.rb +20 -0
  60. data/lib/carbon/compiler/node/expression/operation/neq.rb +21 -0
  61. data/lib/carbon/compiler/node/expression/operation/normal.rb +20 -0
  62. data/lib/carbon/compiler/node/expression/operation/or.rb +20 -0
  63. data/lib/carbon/compiler/node/expression/unit.rb +16 -0
  64. data/lib/carbon/compiler/node/name.rb +27 -0
  65. data/lib/carbon/compiler/node/root.rb +23 -0
  66. data/lib/carbon/compiler/node/statement.rb +24 -0
  67. data/lib/carbon/compiler/node/statement/catch.rb +20 -0
  68. data/lib/carbon/compiler/node/statement/condition.rb +14 -0
  69. data/lib/carbon/compiler/node/statement/else.rb +17 -0
  70. data/lib/carbon/compiler/node/statement/elsif.rb +18 -0
  71. data/lib/carbon/compiler/node/statement/finally.rb +17 -0
  72. data/lib/carbon/compiler/node/statement/for.rb +18 -0
  73. data/lib/carbon/compiler/node/statement/if.rb +18 -0
  74. data/lib/carbon/compiler/node/statement/let.rb +18 -0
  75. data/lib/carbon/compiler/node/statement/match.rb +14 -0
  76. data/lib/carbon/compiler/node/statement/return.rb +17 -0
  77. data/lib/carbon/compiler/node/statement/try.rb +19 -0
  78. data/lib/carbon/compiler/node/statement/while.rb +19 -0
  79. data/lib/carbon/compiler/parser.rb +63 -0
  80. data/lib/carbon/compiler/parser/common.rb +79 -0
  81. data/lib/carbon/compiler/parser/expressions.rb +39 -0
  82. data/lib/carbon/compiler/parser/expressions/precedence.rb +134 -0
  83. data/lib/carbon/compiler/parser/expressions/primary.rb +120 -0
  84. data/lib/carbon/compiler/parser/firsts.rb +74 -0
  85. data/lib/carbon/compiler/parser/helpers.rb +61 -0
  86. data/lib/carbon/compiler/parser/root.rb +57 -0
  87. data/lib/carbon/compiler/parser/root/class.rb +34 -0
  88. data/lib/carbon/compiler/parser/root/directive.rb +87 -0
  89. data/lib/carbon/compiler/parser/root/enum.rb +45 -0
  90. data/lib/carbon/compiler/parser/root/function.rb +90 -0
  91. data/lib/carbon/compiler/parser/root/struct.rb +34 -0
  92. data/lib/carbon/compiler/parser/root/trait.rb +44 -0
  93. data/lib/carbon/compiler/parser/statements.rb +86 -0
  94. data/lib/carbon/compiler/parser/statements/if.rb +50 -0
  95. data/lib/carbon/compiler/parser/statements/match.rb +39 -0
  96. data/lib/carbon/compiler/parser/statements/try.rb +49 -0
  97. data/lib/carbon/compiler/project.rb +37 -0
  98. data/lib/carbon/compiler/project/file.rb +64 -0
  99. data/lib/carbon/compiler/scanner.rb +82 -0
  100. data/lib/carbon/compiler/scanner/main.rb +76 -0
  101. data/lib/carbon/compiler/scanner/token.rb +58 -0
  102. data/lib/carbon/compiler/version.rb +8 -0
  103. data/lib/carbon/compiler/visitor.rb +13 -0
  104. data/lib/carbon/compiler/visitor/base.rb +52 -0
  105. data/lib/carbon/compiler/visitor/generation.rb +45 -0
  106. data/lib/carbon/compiler/visitor/generation/asserts.rb +30 -0
  107. data/lib/carbon/compiler/visitor/generation/class.rb +93 -0
  108. data/lib/carbon/compiler/visitor/generation/context.rb +75 -0
  109. data/lib/carbon/compiler/visitor/generation/expressions.rb +82 -0
  110. data/lib/carbon/compiler/visitor/generation/expressions/assignment.rb +105 -0
  111. data/lib/carbon/compiler/visitor/generation/expressions/calls.rb +89 -0
  112. data/lib/carbon/compiler/visitor/generation/function.rb +68 -0
  113. data/lib/carbon/compiler/visitor/generation/statements.rb +131 -0
  114. data/lib/carbon/compiler/visitor/generation/struct.rb +115 -0
  115. data/lib/carbon/compiler/visitor/preparation.rb +86 -0
  116. data/lib/carbon/compiler/visitor/preparation/expressions.rb +26 -0
  117. data/lib/carbon/compiler/visitor/preparation/function.rb +55 -0
  118. data/lib/carbon/compiler/visitor/preparation/statements.rb +73 -0
  119. data/lib/carbon/compiler/visitor/preparation/struct.rb +37 -0
  120. data/program.ca +16 -0
  121. data/test.rb +21 -0
  122. metadata +234 -0
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "carbon/compiler/metanostic/defaults"
5
+ require "carbon/compiler/metanostic/diagnostic"
6
+ require "carbon/compiler/metanostic/list"
7
+ require "carbon/compiler/metanostic/mode"
8
+ require "carbon/compiler/metanostic/state"
9
+
10
+ module Carbon
11
+ module Compiler
12
+ # A "Metanostic." A "metanostic" has information about what a diagnostic
13
+ # could be about. For example, a `Diagnostic/Unknown` diagnostic has
14
+ # metanostic information; the name itself, the default mode, and the
15
+ # allowed modes are a part of that metanostic information. Diagnostics
16
+ # are generated from both metanostic information and environmental
17
+ # information.
18
+ class Metanostic
19
+ include Comparable
20
+ # The name of the metanostic. This is used as a unique identifier for
21
+ # the metanostic. This is a string, and is normally represented as a
22
+ # path of CamelCase names. For example, a name might be
23
+ # `Diagnostic/Unknown`.
24
+ #
25
+ # @return [::String]
26
+ attr_reader :name
27
+ # The allowed modes of a metanostic or diagnostic. This limits how a
28
+ # diagnostic can be categorized. This is a bitfield of modes. If a
29
+ # metanostic can be any mode, this is `Metanostic::Mode::ALL`; otherwise,
30
+ # it is a union of all of the allowed modes of a diagnostic.
31
+ #
32
+ # @see Metanostic::Mode
33
+ # @return [::Integer]
34
+ attr_reader :allowed
35
+ # The default mode of derived diagnostics. This is and must be an
36
+ # allowed diagnostic.
37
+ #
38
+ # @see #allowed
39
+ # @see Metanostic::Mode
40
+ # @return [::Integer]
41
+ attr_reader :default
42
+ # The default message of derived diagnostics, if the diagnostic does not
43
+ # provide one. This is used to provide a generic message.
44
+ #
45
+ # @return [::String]
46
+ attr_reader :message
47
+
48
+ # Initialize the diagnostic with the given data.
49
+ #
50
+ # @param data [::Hash{::Symbol,::String => ::Object}]
51
+ # @option data [::String] :name The name of the metanostic.
52
+ # @option data [::Integer] :allowed The allowed modes of derived
53
+ # diagnostics.
54
+ # @option data [::Integer] :default The default mode of derived
55
+ # diagnostics.
56
+ # @option data [::String] :message The default message of derived
57
+ # diagnostics.
58
+ # @raise [::KeyError] If one of the option keys is not present.
59
+ def initialize(data)
60
+ @name = data.fetch(:name) { data.fetch("name") }
61
+ @allowed = data.fetch(:allowed) { data.fetch("allowed") }
62
+ self.default = data.fetch(:default) { data.fetch("default") }
63
+ @message = data.fetch(:message) { data.fetch("message") }
64
+ end
65
+
66
+ # Compares this with another metanostic. This should only be used for
67
+ # equality; it makes no sense otherwise.
68
+ #
69
+ # @param other [Metanostic] The metanostic to compare to.
70
+ # @return [::Numeric]
71
+ def <=>(other)
72
+ fail ArgumentError, "Expected Metanostic" unless other.is_a?(Metanostic)
73
+ to_a <=> other.to_a
74
+ end
75
+
76
+ # Returns a numeric representation of this class for use of hashing.
77
+ #
78
+ # @return [::Numeric]
79
+ def hash
80
+ to_a.hash
81
+ end
82
+
83
+ # Returns an array representation of this class. This is frozen.
84
+ #
85
+ # @return [(::Class, ::String, ::Integer, ::Integer, ::String)]
86
+ def to_a
87
+ [self.class, @name, @allowed, @default, @message].freeze
88
+ end
89
+
90
+ # Pretty inspect.
91
+ #
92
+ # @return [::String]
93
+ def inspect
94
+ "#<#{self.class} #{@name}(#{Mode.to_s(@default)}) " \
95
+ "[#{Mode.to_s(@allowed)}]>"
96
+ end
97
+
98
+ # Sets the default mode of the metanostic to the given mode. The
99
+ # metanostic must have the corresponding allowed mode bit set.
100
+ #
101
+ # @see Metanostic::Mode
102
+ # @raise [DiagnosticError] If the metanostic cannot have the given mode
103
+ # as the default.
104
+ # @return [::Integer] The new mode.
105
+ def default=(mode)
106
+ unless can_be?(mode)
107
+ fail DiagnosticError, "#{mode} is not allowed as a default"
108
+ end
109
+ @default = mode
110
+ end
111
+
112
+ # Checks to see if the metanostic or any derived diagnostics can have the
113
+ # given mode.
114
+ #
115
+ # @see Metanostic::Mode
116
+ # @param mode [Integer] The mode.
117
+ # @return [Boolean]
118
+ def can_be?(mode)
119
+ Mode.singular?(mode) && Mode.is?(allowed, mode)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require "yaml"
4
+
5
+ module Carbon
6
+ module Compiler
7
+ class Metanostic
8
+ # Deals with loading default of metanostics. It mostly takes defaults
9
+ # from a defaults.yml file in the same directory as this file.
10
+ module Defaults
11
+ # Load a file and return the metanostics contained within the files.
12
+ #
13
+ # @param file [String] The file to load from. The file must contain
14
+ # a YAML-formatted list of diagnostics.
15
+ # @return [{String => Metanostic}]
16
+ def self.load_file(file)
17
+ data = YAML.load_file(file)
18
+ metanostics = {}
19
+ data["metanostics"].each do |name, metanostic|
20
+ meta = { name: name,
21
+ default: Mode.from(metanostic["default"]),
22
+ allowed: Mode.from(metanostic["allowed"]),
23
+ message: metanostic["message"] }
24
+ metanostics[name] = Metanostic.new(meta)
25
+ end
26
+
27
+ metanostics
28
+ end
29
+
30
+ # Returns a frozen hash of all the default metanostics. Since it is
31
+ # frozen, it is cached.
32
+ #
33
+ # @return [{String => Metanostic}]
34
+ def self.defaults
35
+ @_defaults ||=
36
+ load_file(::File.expand_path("../defaults.yml", __FILE__)).freeze
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,138 @@
1
+ metanostics:
2
+ Test/All:
3
+ allowed: [all]
4
+ default: warning
5
+ Test/Ignore:
6
+ allowed: [all]
7
+ default: ignore
8
+ Test/Info:
9
+ allowed: [all]
10
+ default: info
11
+ Test/Warning:
12
+ allowed: [all]
13
+ default: warning
14
+ Test/Error:
15
+ allowed: [all]
16
+ default: error
17
+ Test/Panic:
18
+ allowed: [all]
19
+ default: panic
20
+ System/Error:
21
+ allowed: [panic]
22
+ default: panic
23
+ message: >-
24
+ The compiler encountered an unrecoverable error and must exit
25
+ note: >-
26
+ Results from internal usage of an unknown diagnostic so far.
27
+ Trace/Location:
28
+ allowed: [all]
29
+ default: warning
30
+ message: previous location is here
31
+ note: >-
32
+ Used for tracing the location of something.
33
+ Diagnostic/Unknown:
34
+ allowed: [all]
35
+ default: error
36
+ message: unknown diagnostic used
37
+ Diagnostic/Mode/Invalid:
38
+ allowed: [all]
39
+ default: error
40
+ message: an invalid mode was given
41
+ Syntax/Token/Unknown:
42
+ allowed: [panic]
43
+ default: panic
44
+ message: unknown token %s
45
+ Syntax/Token/Unexpected:
46
+ allowed: [panic]
47
+ default: panic
48
+ message: unexpected %s, expected one of %s
49
+ Module/Definition/MultipleData:
50
+ allowed: [error, panic]
51
+ default: error
52
+ message: too many data definitions in file
53
+ note: >-
54
+ The compiler preceeds with error checking using the first data
55
+ definition in the file. However, the compiler never preceeds to the
56
+ emitting stage.
57
+ Module/Definition/MultipleNames:
58
+ allowed: [error, panic]
59
+ default: error
60
+ message: too many module name definitions in file
61
+ note: >-
62
+ The compiler preceeds with error checking using the first name
63
+ definition in the file. However, the compiler never preceeds to the
64
+ emitting stage.
65
+ Module/Definition/MismatchedName:
66
+ allowed: [all]
67
+ default: error
68
+ message: expected name %s, found name %s
69
+ note: >-
70
+ The default behavior is to use the name in the file. If the mode
71
+ is anything under panic, the name in the file is set as the name of
72
+ the module.
73
+ Module/Name/Invalid:
74
+ allowed: [error, panic]
75
+ default: error
76
+ message: the module name had an invalid component
77
+ note: >-
78
+ This typically doesn't happen except in directives, where a directive
79
+ was expecting a "normal" module name instead of the directive's
80
+ "extended" module name.
81
+ Module/Name/InvalidExtended:
82
+ allowed: [error, panic]
83
+ default: error
84
+ message: the star component of a module name was in an invalid position
85
+ note: >-
86
+ This only happens in an extended module name for a directive. The
87
+ star of a module name must be at the end.
88
+ Module/Name/IgnoredGenerics:
89
+ allowed: [all]
90
+ default: warning
91
+ message: the module name includes generic information that will be discarded
92
+ Module/Name/UnexpectedGeneric:
93
+ allowed: [error, panic]
94
+ default: error
95
+ message: the module name includes generic information that was not expected
96
+ Diagnostic/Unknown:
97
+ allowed: [all]
98
+ default: error
99
+ message: an unknown directive was used
100
+ note: >-
101
+ The default behavior is to ignore the directive.
102
+ Directive/Parameter/Invalid:
103
+ allowed: [error, panic]
104
+ default: error
105
+ message: an invalid parameter for a directive was used
106
+ note: >-
107
+ Due to technical restraints, this also includes parameters that don't
108
+ exist.
109
+ Directive/Parameter/Excessive:
110
+ allowed: [warning, error, panic]
111
+ default: warning
112
+ message: expected %s, got %s
113
+ note: >-
114
+ The default behavior is to ignore the excessive parameters.
115
+ Enum/Definition/Duplicate:
116
+ allowed: [error, panic]
117
+ default: error
118
+ message: one or more elements in the enum were duplicated
119
+ Enum/Definition/ConflictingTypes:
120
+ allowed: [error, panic]
121
+ default: error
122
+ message: one or more elements had differing types
123
+ Struct/Definition/Duplicate:
124
+ allowed: [error, panic]
125
+ default: error
126
+ message: duplicate name %s
127
+ Function/Duplicate:
128
+ allowed: [error, panic]
129
+ default: error
130
+ message: a function was duplicated
131
+ Expression/Type/Mismatch:
132
+ allowed: [ignore, warning, error, panic]
133
+ default: error
134
+ message: found type %s, expected type %s
135
+ Statement/Let/Redefine:
136
+ allowed: [ignore, warning, error, panic]
137
+ default: error
138
+ message: attempted to redefine local %s
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Carbon
5
+ module Compiler
6
+ class Metanostic
7
+ # A diagnostic. This is an actual diagnostic, spawned off of a metanostic
8
+ # and an issue in compiling a file. This contains information about the
9
+ # related metanostic, the location of the diagnostic, an associated
10
+ # message, and the mode the diagnostic is considered.
11
+ class Diagnostic
12
+ TEMPLATESTR = File.read(File.expand_path("../template.erb", __FILE__))
13
+ .gsub(/\r\n|\r|\n/, "\n")
14
+ TEMPLATE = ERB.new(TEMPLATESTR, nil, ">")
15
+
16
+ include Comparable
17
+ # The associated metanostic. This contains information about
18
+ # information the name and the allowed modes of this diagnostic.
19
+ #
20
+ # @return [Metanostic]
21
+ attr_reader :metanostic
22
+ # The associated location. If the location is not given, it defaults
23
+ # to {Location.default}.
24
+ #
25
+ # @return [Location]
26
+ attr_reader :location
27
+ # The message for the diagnostic. If none is given, it defaults to the
28
+ # metanostic's message.
29
+ #
30
+ # @return [String]
31
+ attr_reader :message
32
+ # The mode for the diagnostic. This changes how the diagnostic is
33
+ # treated.
34
+ #
35
+ # @return [Integer]
36
+ attr_reader :mode
37
+
38
+ # Initializes the diagnostic, and then freezes it.
39
+ #
40
+ # @param data [{Symbol => Object}] Origin date for the diagnostic.
41
+ # @option data [Metanostic] :meta The associated metanostic.
42
+ # See {#metanostic}.
43
+ # @option data [Location] :location The location. See {#location}.
44
+ # @option data [Integer] :mode The mode. See {#mode}.
45
+ # @option data [String?] :message The message. See {#message}.
46
+ # @raise [KeyError] If one of the above options was not present.
47
+ def initialize(data)
48
+ @metanostic = data.fetch(:meta)
49
+ @location = data.fetch(:location)
50
+ @mode = data.fetch(:mode)
51
+ @list = data.fetch(:list)
52
+ @stack = data.fetch(:stack) { caller[3..8] }
53
+ @message = data[:message] || @metanostic.message
54
+ to_a
55
+ freeze
56
+ end
57
+
58
+ # Outputs this diagnostic to the given IO. If the mode is
59
+ # {Mode::IGNORE}, and {Carbon.verbose} is less than `1`, then the
60
+ # diagnostic is not output.
61
+ #
62
+ # @param io [#<<]
63
+ # @return [void]
64
+ def output(io)
65
+ return if mode == Mode::IGNORE && Carbon.verbose < 1
66
+ io << TEMPLATE.result(binding) << "\n"
67
+ end
68
+
69
+ # Compares this instance with another instance. It does so by comparing
70
+ # the array representations of both; therefore, this makes no sense
71
+ # other than equality.
72
+ #
73
+ # @param other [Diagnostic] The diagnostic to compare.
74
+ # @return [Numeric]
75
+ def <=>(other)
76
+ fail ArgumentError, "Expected Diagnostic" unless \
77
+ other.is_a?(Diagnostic)
78
+ to_a <=> other.to_a
79
+ end
80
+
81
+ # Returns a numeric representation of this class for use of hashing.
82
+ #
83
+ # @return [Numeric]
84
+ def hash
85
+ to_a.hash
86
+ end
87
+
88
+ # An array representation of this class. This is frozen and cached.
89
+ #
90
+ # @return [(Class, Metanostic, Location, Integer, String)]
91
+ def to_a
92
+ @array ||= [self.class, @metanostic, @location, @mode,
93
+ @message].freeze
94
+ end
95
+
96
+ private
97
+
98
+ def distance
99
+ @location.column.end - @location.column.begin
100
+ end
101
+
102
+ def lines
103
+ (@location.line.begin - 1)..(@location.line.end - 1)
104
+ end
105
+
106
+ def file
107
+ @list.files[@location.file]
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Carbon
5
+ module Compiler
6
+ class Metanostic
7
+ # A list of diagnostics. This handles setting up the modes of
8
+ # new, emitted diagnostics, and reporting diagnostic types.
9
+ class List
10
+ include Enumerable
11
+ extend Forwardable
12
+ # The current state. This contains information about what modes each
13
+ # diagnostic should inherit.
14
+ #
15
+ # @return [State]
16
+ attr_reader :state
17
+ # The default hash of metanostics.
18
+ #
19
+ # @see Defaults.defaults
20
+ # @return [{::Hash => Metanostic}]
21
+ attr_reader :defaults
22
+ # The project files for the metanostic list. This is to provide context
23
+ # for diagnostic errors.
24
+ #
25
+ # @retrun [{::String => Project::File}]
26
+ attr_reader :files
27
+
28
+ THRESHOLDS = {
29
+ "warnings" => 100,
30
+ "errors" => 20
31
+ }.freeze
32
+
33
+ delegate [:each, :size, :count, :length, :clear] => :@list
34
+
35
+ # Initialize the list.
36
+ def initialize
37
+ @defaults = Metanostic::Defaults.defaults
38
+ @state = State.new
39
+ @list = Concurrent::Array.new
40
+ @files = Concurrent::Hash.new
41
+ end
42
+
43
+ # Returns a list of diagnostics that have the {Mode::PANIC} mode.
44
+ #
45
+ # @return [<Diagnostic>]
46
+ def panics
47
+ select { |d| d.metanostic.default == Mode::PANIC }
48
+ end
49
+
50
+ # Returns a list of diagnostics that have the {Mode::ERROR} mode.
51
+ #
52
+ # @return [<Diagnostic>]
53
+ def errors
54
+ select { |d| d.metanostic.default == Mode::ERROR }
55
+ end
56
+
57
+ # Returns a list of diagnostics that have the {Mode::WARNING} mode.
58
+ #
59
+ # @return [<Diagnostic>]
60
+ def warnings
61
+ select { |d| d.metanostic.default == Mode::WARNING }
62
+ end
63
+
64
+ # Emits a diagnostic to the list. It sets up the data to pass to the
65
+ # diagnostic. If the diagnostic cannot be found, it instead emits a
66
+ # `System/Error` diagnostic.
67
+ def emit(name, location = Location.default, format = [])
68
+ mode, metanostic = @state.fetch(name) do
69
+ return emit("System/Error", location, [name])
70
+ end
71
+
72
+ message = format.is_a?(::String) ? format :
73
+ sprintf(metanostic.message, *format)
74
+
75
+ stack = caller[2..7]
76
+ diagnostic = Diagnostic.new(meta: metanostic, location: location,
77
+ message: message, mode: mode, list: self, stack: stack)
78
+ @list << diagnostic
79
+ diagnostic.output($stderr)
80
+ diagnostic_check
81
+ end
82
+
83
+ alias_method :<<, :emit
84
+ alias_method :push, :emit
85
+
86
+ # Outputs all of the diagnostics in this list. Does nothing if
87
+ # {Carbon.quiet?} is true.
88
+ #
89
+ # @see Diagnostic#output
90
+ # @param io [#<<] The IO device to output to.
91
+ # @return [void]
92
+ def output(io)
93
+ return if Carbon.quiet?
94
+ each { |i| i.output(io, @files) }
95
+ end
96
+
97
+ private
98
+
99
+ def diagnostic_check
100
+ fail DiagnosticError, "Exceeded warning threshold" if \
101
+ warnings.size >= THRESHOLDS.fetch("warnings")
102
+ fail DiagnosticError, "Exceeded error threshold" if \
103
+ errors.size >= THRESHOLDS.fetch("errors")
104
+ fail DiagnosticError, "Exceeded panic threshold" if panics.size >= 1
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end