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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +38 -0
- data/.travis.yml +4 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +14 -0
- data/carbon.gemspec +30 -0
- data/lib/carbon.rb +54 -0
- data/lib/carbon/concrete.rb +43 -0
- data/lib/carbon/concrete/build.rb +63 -0
- data/lib/carbon/concrete/index.rb +324 -0
- data/lib/carbon/concrete/item.rb +37 -0
- data/lib/carbon/concrete/item/base.rb +153 -0
- data/lib/carbon/concrete/item/data.rb +22 -0
- data/lib/carbon/concrete/item/function.rb +97 -0
- data/lib/carbon/concrete/item/internal.rb +83 -0
- data/lib/carbon/concrete/item/struct.rb +65 -0
- data/lib/carbon/concrete/item/struct/element.rb +42 -0
- data/lib/carbon/concrete/item/trait.rb +72 -0
- data/lib/carbon/concrete/item/trait/expectation.rb +55 -0
- data/lib/carbon/concrete/request.rb +137 -0
- data/lib/carbon/concrete/type.rb +260 -0
- data/lib/carbon/concrete/type/function.rb +91 -0
- data/lib/carbon/concrete/type/generic.rb +118 -0
- data/lib/carbon/concrete/type/name.rb +147 -0
- data/lib/carbon/concrete/type/parse.rb +172 -0
- data/lib/carbon/concrete/type/part.rb +100 -0
- data/lib/carbon/core.rb +61 -0
- data/lib/carbon/core/int.rb +87 -0
- data/lib/carbon/core/integer.rb +109 -0
- data/lib/carbon/core/integer/cast.rb +83 -0
- data/lib/carbon/core/integer/math.rb +198 -0
- data/lib/carbon/core/integer/misc.rb +145 -0
- data/lib/carbon/core/integer/pole.rb +133 -0
- data/lib/carbon/core/integer/ship.rb +71 -0
- data/lib/carbon/core/integer/sign.rb +52 -0
- data/lib/carbon/core/integer/type.rb +42 -0
- data/lib/carbon/core/integer/zero.rb +52 -0
- data/lib/carbon/core/pointer.rb +54 -0
- data/lib/carbon/core/pointer/access.rb +123 -0
- data/lib/carbon/core/pointer/cast.rb +55 -0
- data/lib/carbon/core/pointer/math.rb +187 -0
- data/lib/carbon/core/pointer/memory.rb +85 -0
- data/lib/carbon/core/pointer/type.rb +23 -0
- data/lib/carbon/tacky.rb +21 -0
- data/lib/carbon/tacky/block.rb +96 -0
- data/lib/carbon/tacky/builder.rb +310 -0
- data/lib/carbon/tacky/context.rb +66 -0
- data/lib/carbon/tacky/function.rb +137 -0
- data/lib/carbon/tacky/instruction.rb +170 -0
- data/lib/carbon/tacky/parameter.rb +23 -0
- data/lib/carbon/tacky/reference.rb +23 -0
- data/lib/carbon/tacky/value.rb +40 -0
- data/lib/carbon/version.rb +9 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -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
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-mmarkdown
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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"]
|
data/carbon.gemspec
ADDED
@@ -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
|
data/lib/carbon.rb
ADDED
@@ -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
|