carbon-core 0.1.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +38 -0
  5. data/.travis.yml +4 -0
  6. data/.yardopts +1 -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 +14 -0
  12. data/carbon.gemspec +30 -0
  13. data/lib/carbon.rb +54 -0
  14. data/lib/carbon/concrete.rb +43 -0
  15. data/lib/carbon/concrete/build.rb +63 -0
  16. data/lib/carbon/concrete/index.rb +324 -0
  17. data/lib/carbon/concrete/item.rb +37 -0
  18. data/lib/carbon/concrete/item/base.rb +153 -0
  19. data/lib/carbon/concrete/item/data.rb +22 -0
  20. data/lib/carbon/concrete/item/function.rb +97 -0
  21. data/lib/carbon/concrete/item/internal.rb +83 -0
  22. data/lib/carbon/concrete/item/struct.rb +65 -0
  23. data/lib/carbon/concrete/item/struct/element.rb +42 -0
  24. data/lib/carbon/concrete/item/trait.rb +72 -0
  25. data/lib/carbon/concrete/item/trait/expectation.rb +55 -0
  26. data/lib/carbon/concrete/request.rb +137 -0
  27. data/lib/carbon/concrete/type.rb +260 -0
  28. data/lib/carbon/concrete/type/function.rb +91 -0
  29. data/lib/carbon/concrete/type/generic.rb +118 -0
  30. data/lib/carbon/concrete/type/name.rb +147 -0
  31. data/lib/carbon/concrete/type/parse.rb +172 -0
  32. data/lib/carbon/concrete/type/part.rb +100 -0
  33. data/lib/carbon/core.rb +61 -0
  34. data/lib/carbon/core/int.rb +87 -0
  35. data/lib/carbon/core/integer.rb +109 -0
  36. data/lib/carbon/core/integer/cast.rb +83 -0
  37. data/lib/carbon/core/integer/math.rb +198 -0
  38. data/lib/carbon/core/integer/misc.rb +145 -0
  39. data/lib/carbon/core/integer/pole.rb +133 -0
  40. data/lib/carbon/core/integer/ship.rb +71 -0
  41. data/lib/carbon/core/integer/sign.rb +52 -0
  42. data/lib/carbon/core/integer/type.rb +42 -0
  43. data/lib/carbon/core/integer/zero.rb +52 -0
  44. data/lib/carbon/core/pointer.rb +54 -0
  45. data/lib/carbon/core/pointer/access.rb +123 -0
  46. data/lib/carbon/core/pointer/cast.rb +55 -0
  47. data/lib/carbon/core/pointer/math.rb +187 -0
  48. data/lib/carbon/core/pointer/memory.rb +85 -0
  49. data/lib/carbon/core/pointer/type.rb +23 -0
  50. data/lib/carbon/tacky.rb +21 -0
  51. data/lib/carbon/tacky/block.rb +96 -0
  52. data/lib/carbon/tacky/builder.rb +310 -0
  53. data/lib/carbon/tacky/context.rb +66 -0
  54. data/lib/carbon/tacky/function.rb +137 -0
  55. data/lib/carbon/tacky/instruction.rb +170 -0
  56. data/lib/carbon/tacky/parameter.rb +23 -0
  57. data/lib/carbon/tacky/reference.rb +23 -0
  58. data/lib/carbon/tacky/value.rb +40 -0
  59. data/lib/carbon/version.rb +9 -0
  60. metadata +186 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df956cb1d06f05220b2f44f38aac8279b6238d3a
4
+ data.tar.gz: fd88b72d02ccd3d2706e750c43c061a4572196c9
5
+ SHA512:
6
+ metadata.gz: 71426b13ade11afea88c848cb15b4cbabea156b5162c516f1f53d96a57d02b8a4dd56754d522d53f7826891c3f229084e735287895ce89c61b0336945962c82b
7
+ data.tar.gz: be6a57e2bbf58f024b808cb4f530e55d99221e8c8fd9a614bce8d6069edba375ff8368f28203c3095c3a7113ddd7f63d3bdf322cf904ff443fa80c001c568e23
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /core.rb
11
+ /prof/
12
+ /scripts/
13
+ *.clib
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,38 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+
5
+ Style/AlignParameters:
6
+ EnforcedStyle: with_fixed_indentation
7
+ Style/SignalException:
8
+ EnforcedStyle: only_fail
9
+ Style/AccessModifierIndentation:
10
+ EnforcedStyle: outdent
11
+ Style/ConditionalAssignment:
12
+ Enabled: false
13
+ Style/Encoding:
14
+ Enabled: true
15
+ EnforcedStyle: always
16
+ AutoCorrectEncodingComment: "# encoding: utf-8\n"
17
+ Style/MultilineBlockChain:
18
+ Enabled: false
19
+ Style/MultilineMethodCallIndentation:
20
+ Enabled: false
21
+ Style/ParallelAssignment:
22
+ Enabled: false
23
+ Style/RedundantFreeze:
24
+ Enabled: false
25
+ Style/RegexpLiteral:
26
+ EnforcedStyle: mixed
27
+ Style/StringLiterals:
28
+ EnforcedStyle: double_quotes
29
+ Style/AndOr:
30
+ EnforcedStyle: conditionals
31
+ Style/AlignHash:
32
+ EnforcedLastArgumentHashStyle: ignore_implicit
33
+ Style/AlignArray:
34
+ Enabled: false
35
+ Style/IndentArray:
36
+ Enabled: false
37
+ Style/Alias:
38
+ EnforcedStyle: prefer_alias_method
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1 @@
1
+ -mmarkdown
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at redjazz96@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ source "https://rubygems.org"
5
+
6
+ # Specify your gem's dependencies in carbon.gemspec
7
+ gemspec
8
+ gem "yardstick"
9
+ gem "concurrent-ruby"
10
+ gem "simplecov", require: false
11
+ gem "simplecov-json", require: false
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jeremy Rodi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # Carbon
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/carbon`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'carbon'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install carbon
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/carbon. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/gem_tasks"
5
+ require "rspec/core/rake_task"
6
+ require "yard"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+ YARD::Rake::YardocTask.new(:"doc:build") do |t|
10
+ t.options = ["-mmarkdown"]
11
+ end
12
+
13
+ task default: :spec
14
+ task doc: [:"doc:build"]
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "carbon/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "carbon-core"
9
+ spec.version = Carbon::VERSION
10
+ spec.authors = ["Jeremy Rodi"]
11
+ spec.email = ["redjazz96@gmail.com"]
12
+
13
+ spec.summary = "The Core logic of the Carbon language."
14
+ spec.homepage = "https://github.com/carbon-lang/core"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`
18
+ .split("\x0")
19
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_dependency "base58", "~> 0.1"
28
+ spec.add_dependency "msgpack", "~> 1.0"
29
+ spec.add_dependency "ice_nine", "~> 0.11"
30
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "ice_nine"
5
+ require "ice_nine/core_ext/object"
6
+ require "base58"
7
+ require "digest/sha2"
8
+ require "concurrent"
9
+ require "llvm"
10
+ require "llvm/core"
11
+ require "carbon/version"
12
+ require "carbon/tacky"
13
+ require "carbon/concrete"
14
+
15
+ # Carbon. The language.
16
+ module Carbon
17
+ # rubocop:disable Style/MethodName
18
+
19
+ # Creates a type. This uses {Carbon::Concrete::Type}, and assumes that the
20
+ # parameter must be a string to be parsed.
21
+ #
22
+ # @api public
23
+ # @example
24
+ # type = Carbon::Type("Carbon::Boolean")
25
+ # type # => #<Carbon::Concrete::Type Carbon::Boolean>
26
+ # @param string [::String] The string to parse.
27
+ # @return [Carbon::Concrete::Type] The resulting type.
28
+ def self.Type(string)
29
+ Carbon::Concrete::Type.from(string)
30
+ end
31
+ # rubocop:enable Style/MethodName
32
+
33
+ # Hashes a given string into the format used by Carbon. The format is a
34
+ # Base58-encoded SHA256 digest.
35
+ #
36
+ # @api semipublic
37
+ # @example
38
+ # Carbon.hash("") # => "gjNT5GbSC-81KmUPncw-hzQAGV3GU-eAXafmkMP-Bw2GMHWM"
39
+ # @param string [::String] The string to hash.
40
+ # @return [::String] The hashed string.
41
+ def self.hash(string)
42
+ digest = Digest::SHA2.hexdigest(string).hex
43
+ encoded = Base58.encode(digest)
44
+ encoded.scan(/.{1,9}/).join("-").freeze
45
+ end
46
+
47
+ # A Boolean type for Carbon. This is used as a handy shortcut.
48
+ #
49
+ # @api private
50
+ # @return [Carbon::Concrete::Type]
51
+ Boolean = Carbon::Type("Carbon::Boolean")
52
+
53
+ require "carbon/core"
54
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "oj"
5
+ require "zlib"
6
+ require "carbon/concrete/build"
7
+ require "carbon/concrete/item"
8
+ require "carbon/concrete/index"
9
+ require "carbon/concrete/request"
10
+ require "carbon/concrete/type"
11
+
12
+ module Carbon
13
+ # A concrete representation of the global state of a compilation. All of
14
+ # the things within this module should be serializable, so that libraries
15
+ # may be linked easily back into the process.
16
+ module Concrete
17
+ # Loads a dumped object into an instance. The dumped object should be a
18
+ # Zlib compressed (with Deflate) string. The dumped object
19
+ # is decompressed and loaded, then passed to {.from}.
20
+ #
21
+ # @api private
22
+ # @see .dump
23
+ # @param source [::String] The dumped object.
24
+ # @return [::Object] The represented object.
25
+ def self.load(source)
26
+ raw = Zlib::Inflate.inflate(source)
27
+ Oj.load(raw)
28
+ end
29
+
30
+ # Dumps an object to a string. If the object responds to `#to_basic`, the
31
+ # result of that method call is used instead. The object is then
32
+ # serialized, and then compressed using Zlib (Deflate).
33
+ #
34
+ # @api private
35
+ # @see .load
36
+ # @param index [::Object] The object to dump.
37
+ # @return [::String] The dumped object.
38
+ def self.dump(index)
39
+ raw = Oj.dump(index, circular: true, class_cache: true)
40
+ Zlib::Deflate.deflate(raw, 9)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Carbon
5
+ module Concrete
6
+ # Handles building requests. This is the step after all requests are
7
+ # determined from the dependency build stage. The entire purpose of this
8
+ # class is to consolidate state information for LLVM module building.
9
+ #
10
+ # @api semiprivate
11
+ class Build
12
+ # The types that have been defined. This is a mapping of the
13
+ # {Concrete::Type} to an arry of the {Concrete::Item} and the
14
+ # corresponding `LLVM::Type`. This is so that no information is lost
15
+ # when converting the item to the type.
16
+ #
17
+ # @return [{Concrete::Type => (Concrete::Item, ::LLVM::Type)}]
18
+ attr_reader :types
19
+
20
+ # The functions that have been defined. This is a mapping of the
21
+ # {Concrete::Type} to the `LLVM::Function` that was defined.
22
+ #
23
+ # @return [{Concrete::Type => ::LLVM::Function}]
24
+ attr_reader :functions
25
+
26
+ # The LLVM module that is being used for compilation.
27
+ #
28
+ # @return [::LLVM::Module]
29
+ attr_reader :module
30
+
31
+ # Initialize the build process. This only initializes instance
32
+ # variables.
33
+ #
34
+ # @param requests [::Enumerable<Concrete::Request>] An enumerable over
35
+ # each request that needs to be built. The requests should be
36
+ # resolved, i.e. the generics for the request should resolve to a
37
+ # valid type.
38
+ # @param index [Concrete::Index] The index that is being built from.
39
+ def initialize(requests, index)
40
+ @requests = requests
41
+ @index = index
42
+ end
43
+
44
+ # Performs the build process. Builds each request in order by calling
45
+ # {Concrete::Item::Base#call} on each.
46
+ #
47
+ # @return [::LLVM::Module] The built module.
48
+ def call
49
+ @types = {}
50
+ @functions = {}
51
+ @module = ::LLVM::Module.new("__carbon_main")
52
+
53
+ @requests.each do |request|
54
+ item = @index.fetch(request.intern)
55
+ generics = item.generics.zip(request.generics).to_h
56
+ item.call(self, generics)
57
+ end
58
+
59
+ @module
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,324 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "tsort"
5
+ require "forwardable"
6
+
7
+ module Carbon
8
+ module Concrete
9
+ # A list of all of the "items" in the global context of Carbon. Items are
10
+ # mostly functions and data definitions. This keeps track of them and
11
+ # their dependencies, so that they can be built later. Indexes are also
12
+ # designed to be easily serialized if needed (which is likely the case,
13
+ # for libraries).
14
+ class Index
15
+ include TSort
16
+ extend Forwardable
17
+
18
+ # We can't use these methods because we don't fit the bill - we don't
19
+ # define a `tsort_each_node`, since we don't need it. We only use
20
+ # `each_strongly_connected_component_from`, which only requires
21
+ # `tsort_each_child`. We could use the module function
22
+ # `TSort.each_strongly_connected_component_from`, but that only appears
23
+ # in Ruby versions 2.1.0 and higher. It's not guarenteed to be there.
24
+ # However, everything else in TSort is expected to be there, even since
25
+ # Ruby 1.8.7, and do exactly as expected.
26
+ undef_method :tsort
27
+ undef_method :each_strongly_connected_component
28
+ undef_method :strongly_connected_components
29
+
30
+ # Initialize the index with the given data.
31
+ #
32
+ # @api public
33
+ # @param data [{::Symbol => ::Object}] The data to initialize with.
34
+ # @option data [::Hash] :items ({}) The definitions.
35
+ # @option data [::String] :id (#id) The ID of the index. This is
36
+ # completely derived from the index itself; or, rather, what's defined
37
+ # on the index. See {#id}.
38
+ def initialize(data = {})
39
+ @items = Concurrent::Hash.new.merge(data.fetch(:items, {}))
40
+ @mutex = Mutex.new
41
+ @id = data.fetch(:id) { id }
42
+ end
43
+
44
+ # @overload fetch(name, default = CANARY)
45
+ # Retrieves the item with the given symbol name. If no item with the
46
+ # given name exists, it tries the following: first, if a block is
47
+ # given, the item name is yielded to the block, and the result is
48
+ # returned. Second, if the second argument is passed (i.e. isn't
49
+ # `CANARY`), then that is returned instead. Third, if neither is
50
+ # true, then the method raises `KeyError`.
51
+ #
52
+ # @api public
53
+ # @example Successful fetch.
54
+ # index.fetch("Carbon::Integer") # => #<Carbon::Concrete::Item::Base>
55
+ # @example Failed fetch with argument.
56
+ # index.fetch("Carbon::Foo", false) # => false
57
+ # @example Failed fetch with block.
58
+ # index.fetch("Carbon::Foo") { |n| n.sub("F", "B") }
59
+ # # => "Carbon::Boo"
60
+ # @example Failed fetch with both.
61
+ # index.fetch("Carbon::Foo", false) { |n| n.sub("F", "B") }
62
+ # # => "Carbon::Boo"
63
+ # @param name [::Object] The key of the item to fetch.
64
+ # @param default [::Object] The value to return if the key doesn't match
65
+ # an item and a block wasn't given.
66
+ # @yield [name] If the key doesn't match an item, the method yields.
67
+ # @yieldparam name [::Object] The name of the item.
68
+ # @return [::Object] The item, if the key matched; otherwise, the result
69
+ # of the block, if given; otherwise, the value of the `default`
70
+ # parameter.
71
+ # @raise [KeyError] If the key doesn't match an item and neither a
72
+ # block nor the second argument were passed.
73
+ def fetch(*options, &block)
74
+ @mutex.synchronize { @items.fetch(*options, &block) }
75
+ end
76
+
77
+ # @overload [](key)
78
+ # Retrieves the item with the given symbol name. If no item exists,
79
+ # it returns `nil`.
80
+ #
81
+ # @api public
82
+ # @example Successful retrieval.
83
+ # index["Carbon::Integer"] # => #<Carbon::Concrete::Item::Base>
84
+ # @example Unsuccessful retrieval.
85
+ # index["Carbon::Foo"] # => nil
86
+ # @param key [::Object] The key of the item to lookup.
87
+ # @return [::Object] The item, if it exists;
88
+ # @return [nil] Otherwise.
89
+ def [](*options)
90
+ @mutex.synchronize { @items[*options] }
91
+ end
92
+
93
+ # @overload key?(key)
94
+ # Determines whether or not the key exists, and an item is paired
95
+ # with it.
96
+ #
97
+ # @api public
98
+ # @example Existing key.
99
+ # index.key?("Carbon::Integer") # => true
100
+ # @example Non-existant key.
101
+ # index.key?("Carbon::Foo") # => false
102
+ # @param key [::Object] The key of the item to check.
103
+ # @return [::Boolean] Whether the key exists or not.
104
+ def key?(*options)
105
+ @mutex.synchronize { @items.key?(*options) }
106
+ end
107
+
108
+ # Returns all of the keys in the index. The keys and the array itself
109
+ # are both frozen.
110
+ #
111
+ # @api public
112
+ # @example
113
+ # index.keys # => ["Carbon::Boolean", "Carbon::Integer"]
114
+ # @return [<::String>] The keys in the index.
115
+ def keys
116
+ @mutex.synchronize { @items.keys.deep_freeze! }
117
+ end
118
+
119
+ # Returns all of the values (actual items) in the index. The items, and
120
+ # the array containing them, are frozen.
121
+ #
122
+ # @api public
123
+ # @example
124
+ # index.values.map(&:class) # => [Carbon::Concrete::Item::Internal, ...]
125
+ # @return [<Item::Base>] The values in the index.
126
+ def values
127
+ @mutex.synchornize { @items.values.freeze }
128
+ end
129
+
130
+ # The digest of the ID. This uses the SHA256 base58 digest of the items
131
+ # defined on the current index. This includes both defined and merged
132
+ # items.
133
+ #
134
+ # @api public
135
+ # @example
136
+ # index.items # => {}
137
+ # index.id # => "gjNT5GbSC-81KmUPncw-hzQAGV3GU-eAXafmkMP-Bw2GMHWM"
138
+ # @return [::String]
139
+ def id
140
+ @id ||= Carbon.hash(items.keys.join("\n"))
141
+ end
142
+
143
+ # The items of the index. This is a frozen duplicate of the items.
144
+ # Therefore, this is not guarenteed to be up to date.
145
+ #
146
+ # @api public
147
+ # @example Items
148
+ # index.items
149
+ # # => {"Carbon::Integer" => #<Carbon::Concrete::Item::Base>}
150
+ # @return [{::String => Item::Base}] The items of the index.
151
+ def items
152
+ @mutex.synchronize { @items.dup.deep_freeze! }
153
+ end
154
+
155
+ # Adds a defined item to the index. The item just has to respond to
156
+ # the item API (see {Concrete::Item}). This clears the index's cache,
157
+ # such that the ID ({#id}) and item list ({#items}) are reset and have
158
+ # to be recalculated.
159
+ #
160
+ # @api public
161
+ # @example Adding an item.
162
+ # index << item
163
+ # index.key?(item.intern) # => true
164
+ # @example Merging an index.
165
+ # other.is_a?(Carbon::Concrete::Index) # => true
166
+ # index << other
167
+ # index.items.keys.to_set <= other.items.keys.to_set
168
+ # @note
169
+ # If the item is an index instead, it passes it to {#merge} and
170
+ # returns. This usage is not recommended for {#add}, but can be
171
+ # used for {#<<}.
172
+ # @param item [Concrete::Item::Base] The item to add.
173
+ # @return [self]
174
+ def add(item)
175
+ return merge(item) if item.is_a?(Index)
176
+ @mutex.synchronize do
177
+ @items[item.intern] = item
178
+ clear!
179
+ end
180
+ end
181
+
182
+ alias_method :<<, :add
183
+
184
+ # Merges items from another index into the merged items of this index.
185
+ # This clears the index's cache, such that the ID ({#id}) have to be
186
+ # recalculated.
187
+ #
188
+ # @api public
189
+ # @example Merging.
190
+ # index.merge(other)
191
+ # index.items.keys.to_set <= other.items.keys.to_set
192
+ # @param index [Index] The index to merge.
193
+ # @return [self]
194
+ def merge(index)
195
+ @mutex.synchronize do
196
+ @items.merge(index.items)
197
+ clear!
198
+ end
199
+ end
200
+
201
+ # Used only for {#define}. This maps item "names" to the classes that
202
+ # represent them.
203
+ #
204
+ # @api private
205
+ # @return [{::Symbol => Item::Base}]
206
+ ITEMS = {
207
+ internal: Concrete::Item::Internal,
208
+ function: Concrete::Item::Function,
209
+ struct: Concrete::Item::Struct,
210
+ trait: Concrete::Item::Trait
211
+ }.freeze
212
+
213
+ # Defines a type. This is mostly a shorthand method. The purpose of
214
+ # this is to make it easy to define items on the index. The only
215
+ # parameter, data, is a hash. The key is the name of the type of the
216
+ # item; this corresponds to the keys in the {ITEMS} hash. The value is
217
+ # the {Concrete::Type} that is being defined. The method then yields a
218
+ # hash that is later used to define the item.
219
+ #
220
+ # @api semipublic
221
+ # @example
222
+ # index.class # => Carbon::Concrete::Index
223
+ # index.define(internal: Carbon::Boolean) do |bool|
224
+ # bool[:size] = 1
225
+ # bool[:kind] = :integer
226
+ # bool[:implements] << Carbon::Type("Carbon::Numeric")
227
+ # bool[:implements] << Carbon::Type("Carbon::Boolean")
228
+ # end
229
+ # internal = index[Carbon::Boolean]
230
+ # internal.name # => "Carbon::Boolean"
231
+ # @param data [{::Symbol => Concrete::Type}] The name and
232
+ # item type information. There should only be one key
233
+ # pair; any more will cause an error.
234
+ # @yield [data] To construct the data. At the end of the block, the data
235
+ # should contain all the information needed to create the item.
236
+ # @yieldparam data [{::Symbol => ::Object}] The data to be passed to the
237
+ # item's intializer.
238
+ # @raise [ArgumentError] if more than one key-value pair is provided for
239
+ # the `data` argument.
240
+ # @return [void]
241
+ def define(data)
242
+ fail ArgumentError, "Expected only one pair" if data.size > 1
243
+ data.each do |itype, name|
244
+ item = ITEMS.fetch(itype)
245
+ opts = item.from(Carbon::Type(name))
246
+ yield opts
247
+ self << item.new(opts)
248
+ end
249
+ end
250
+
251
+ # Yields the builds that need to be built for the given item. This uses
252
+ # the TSort module in order to determine all of the dependencies the
253
+ # given item has, and yields each; and finally, yields the last item.
254
+ # The item may not have any generics, even if they are fully formed
255
+ # generics (e.g. have a definition). If any items have a cyclic
256
+ # depdendency, it fails.
257
+ #
258
+ # @api semipublic
259
+ # @example Build request.
260
+ # request.intern # => "Main"
261
+ # request.generics # => []
262
+ # index.build(request).to_a # => [...]
263
+ # @param item [Item::Base] The item to build. The {Item::Base} module
264
+ # contains behavior that is required by this method (namely,
265
+ # {Item::Base#corrected_dependencies}, {Item::Base#intern}, and
266
+ # {Item::Base#generics}).
267
+ # @return [::Enumerable] The build items, if no block is given.
268
+ # @return [void] If a block is given.
269
+ # @yield [dep] Multiple times, each with a dependency.
270
+ # @yieldparam dep [Request] A request.
271
+ def build(item)
272
+ @mutex.lock
273
+ fail ArgumentError, "Passed item cannot be generic" if \
274
+ item.generics.any?
275
+ return to_enum(:build, item) unless block_given?
276
+ request = Request.new(item.intern, [])
277
+
278
+ build_from_request(request, &Proc.new)
279
+ ensure
280
+ @mutex.unlock
281
+ end
282
+
283
+ private
284
+
285
+ # rubocop:disable Metrics/MethodLength
286
+ def build_from_request(request)
287
+ components = to_enum(:each_strongly_connected_component_from, request)
288
+ components.each do |group|
289
+ fail TSort::Cyclic, "cyclic dependencies: #{group.join(', ')}" if \
290
+ group.size > 1
291
+ begin
292
+ @mutex.unlock
293
+ yield group.first
294
+ ensure
295
+ @mutex.lock
296
+ end
297
+ end
298
+ end
299
+ # rubocop:enable Metrics/MethodLength
300
+
301
+ # Iterates over all of the "children" of a node. This iterates over all
302
+ # of the corrected dependencies of a node, allowing the TSort module
303
+ # to build a dependency map.
304
+ #
305
+ # @api private
306
+ # @param node [#intern] The item.
307
+ # @yield [request] Yields each corrected dependency of the item.
308
+ # @yieldparam request [Request]
309
+ # @return [void]
310
+ def tsort_each_child(node, &block)
311
+ @items.fetch(node.intern).corrected_dependencies(node, &block)
312
+ end
313
+
314
+ # Clears the cache, forcing the id to be rebuilt on next call.
315
+ #
316
+ # @api private
317
+ # @return [self]
318
+ def clear!
319
+ @id = nil
320
+ self
321
+ end
322
+ end
323
+ end
324
+ end