oso-oso 0.2.2 → 0.4.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 +5 -5
- data/.rubocop.yml +7 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +13 -12
- data/Makefile +12 -9
- data/README.md +6 -9
- data/Rakefile +0 -1
- data/bin/oso +7 -0
- data/ext/oso-oso/lib/libpolar.dylib +0 -0
- data/ext/oso-oso/lib/libpolar.so +0 -0
- data/ext/oso-oso/lib/polar.dll +0 -0
- data/lib/oso/http.rb +1 -1
- data/lib/oso/oso.rb +6 -32
- data/lib/oso/path_mapper.rb +1 -1
- data/lib/oso/polar.rb +1 -0
- data/lib/oso/polar/errors.rb +25 -9
- data/lib/oso/polar/ffi.rb +6 -2
- data/lib/oso/polar/ffi/error.rb +2 -2
- data/lib/oso/polar/ffi/polar.rb +6 -16
- data/lib/oso/polar/ffi/query.rb +9 -0
- data/lib/oso/polar/host.rb +246 -0
- data/lib/oso/polar/polar.rb +117 -304
- data/lib/oso/polar/query.rb +92 -36
- data/lib/oso/version.rb +1 -1
- data/oso-oso.gemspec +11 -6
- metadata +30 -10
- data/bin/console +0 -33
- data/bin/setup +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 919322c6735cfeb3615da5dd85b4188c59e8716d
|
4
|
+
data.tar.gz: 1c881b365f4098e3bbdad3152168e82ab31df87f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b898b3636cdb1cfa5fe909634292cf030bd60ccf768e964cb258c7e1c076d34b52e953bf8b37b27e2cc42926c44efdbd7b600dc21fea63e7144834870d80db1f
|
7
|
+
data.tar.gz: 1e38944c4a55b81b3dbecbf1864be035632bd840e0f709380c8b14bfaecf657a1abb273b74b171aae35df9210beddb7e42f4c3547af74e482d0db4b214ca987f
|
data/.rubocop.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
oso-oso (0.
|
4
|
+
oso-oso (0.4.0)
|
5
5
|
ffi (~> 1.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -12,18 +12,18 @@ GEM
|
|
12
12
|
benchmark (0.1.0)
|
13
13
|
byebug (11.1.3)
|
14
14
|
coderay (1.1.3)
|
15
|
-
diff-lcs (1.
|
15
|
+
diff-lcs (1.4.4)
|
16
16
|
e2mmap (0.1.0)
|
17
17
|
ffi (1.13.1)
|
18
18
|
jaro_winkler (1.5.4)
|
19
19
|
maruku (0.7.3)
|
20
20
|
method_source (1.0.0)
|
21
21
|
mini_portile2 (2.4.0)
|
22
|
-
nokogiri (1.10.
|
22
|
+
nokogiri (1.10.10)
|
23
23
|
mini_portile2 (~> 2.4.0)
|
24
|
-
parallel (1.19.
|
25
|
-
parser (2.7.1.
|
26
|
-
ast (~> 2.4.
|
24
|
+
parallel (1.19.2)
|
25
|
+
parser (2.7.1.4)
|
26
|
+
ast (~> 2.4.1)
|
27
27
|
pry (0.13.1)
|
28
28
|
coderay (~> 1.1)
|
29
29
|
method_source (~> 1.0)
|
@@ -49,19 +49,19 @@ GEM
|
|
49
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
50
50
|
rspec-support (~> 3.9.0)
|
51
51
|
rspec-support (3.9.3)
|
52
|
-
rubocop (0.
|
52
|
+
rubocop (0.89.0)
|
53
53
|
parallel (~> 1.10)
|
54
|
-
parser (>= 2.7.
|
54
|
+
parser (>= 2.7.1.1)
|
55
55
|
rainbow (>= 2.2.2, < 4.0)
|
56
56
|
regexp_parser (>= 1.7)
|
57
57
|
rexml
|
58
|
-
rubocop-ast (>= 0.0.
|
58
|
+
rubocop-ast (>= 0.1.0, < 1.0)
|
59
59
|
ruby-progressbar (~> 1.7)
|
60
60
|
unicode-display_width (>= 1.4.0, < 2.0)
|
61
|
-
rubocop-ast (0.0
|
62
|
-
parser (>= 2.7.
|
61
|
+
rubocop-ast (0.3.0)
|
62
|
+
parser (>= 2.7.1.4)
|
63
63
|
ruby-progressbar (1.10.1)
|
64
|
-
solargraph (0.39.
|
64
|
+
solargraph (0.39.13)
|
65
65
|
backport (~> 1.1)
|
66
66
|
benchmark
|
67
67
|
bundler (>= 1.17.2)
|
@@ -88,6 +88,7 @@ DEPENDENCIES
|
|
88
88
|
pry-byebug (~> 3.9.0)
|
89
89
|
rake (~> 12.0)
|
90
90
|
rspec (~> 3.0)
|
91
|
+
rubocop (~> 0.89.0)
|
91
92
|
solargraph (~> 0.39.8)
|
92
93
|
yard (~> 0.9.25)
|
93
94
|
|
data/Makefile
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
|
2
|
-
export CARGO_FLAGS
|
3
|
-
TARGET_DIR := $(shell [ -z $${RELEASE} ] && echo "debug" || echo "release")
|
4
|
-
LIB_EXT := $(shell [ $$(uname) = "Linux" ] && echo "so" || echo "dylib")
|
1
|
+
.PHONY: rust install test lint typecheck repl
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
rust-build:
|
3
|
+
rust:
|
9
4
|
$(MAKE) -C ../.. rust-build
|
10
|
-
cp ../../target/$(TARGET_DIR)/libpolar.$(LIB_EXT) ext/oso-oso/lib/libpolar.$(LIB_EXT)
|
11
5
|
|
12
|
-
install: rust
|
6
|
+
install: rust
|
13
7
|
bundle install
|
14
8
|
|
15
9
|
test: install
|
16
10
|
bundle exec rake spec
|
11
|
+
|
12
|
+
lint:
|
13
|
+
bundle exec rubocop
|
14
|
+
|
15
|
+
typecheck:
|
16
|
+
bundle exec solargraph typecheck
|
17
|
+
|
18
|
+
repl: install
|
19
|
+
bundle exec oso
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# oso-oso
|
2
2
|
|
3
3
|
## Installation
|
4
4
|
|
@@ -18,13 +18,10 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Development
|
20
20
|
|
21
|
-
After checking out the repo, run `
|
22
|
-
`rake spec` to run the tests. You can also run `
|
23
|
-
|
21
|
+
After checking out the repo, run `bundle install` to install dependencies.
|
22
|
+
Then, run `bundle exec rake spec` to run the tests. You can also run `bundle
|
23
|
+
exec oso` for an interactive REPL that will allow you to experiment.
|
24
24
|
|
25
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
26
|
-
release a new version, update the version number in `version.rb`, and then run
|
27
|
-
`bundle exec rake release`, which will create a git tag for the version, push
|
28
|
-
git commits and tags, and push the `.gem` file to
|
29
|
-
[rubygems.org](https://rubygems.org).
|
25
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
30
26
|
|
27
|
+
New releases are minted and pushed to RubyGems via GitHub Actions workflows.
|
data/Rakefile
CHANGED
data/bin/oso
ADDED
Binary file
|
data/ext/oso-oso/lib/libpolar.so
CHANGED
Binary file
|
Binary file
|
data/lib/oso/http.rb
CHANGED
data/lib/oso/oso.rb
CHANGED
@@ -1,47 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'polar/polar'
|
4
|
+
|
3
5
|
module Oso
|
4
6
|
# Oso authorization API.
|
5
|
-
class Oso
|
7
|
+
class Oso < Polar::Polar
|
6
8
|
def initialize
|
7
|
-
|
9
|
+
super
|
8
10
|
register_class(Http, name: 'Http')
|
9
11
|
register_class(PathMapper, name: 'PathMapper')
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
def load_str(str)
|
17
|
-
polar.load_str(str)
|
18
|
-
end
|
19
|
-
|
20
|
-
def register_class(cls, name: nil) # rubocop:disable Naming/MethodParameterName
|
21
|
-
if block_given?
|
22
|
-
polar.register_class(cls, name: name, from_polar: Proc.new)
|
23
|
-
else
|
24
|
-
polar.register_class(cls, name: name)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def allow(actor:, action:, resource:)
|
29
|
-
polar.query_pred('allow', args: [actor, action, resource]).next
|
14
|
+
def allowed?(actor:, action:, resource:)
|
15
|
+
query_rule('allow', actor, action, resource).next
|
30
16
|
true
|
31
17
|
rescue StopIteration
|
32
18
|
false
|
33
19
|
end
|
34
|
-
|
35
|
-
def query_predicate(name, *args)
|
36
|
-
polar.query_pred(name, args: args)
|
37
|
-
end
|
38
|
-
|
39
|
-
def load_queued_files
|
40
|
-
polar.load_queued_files
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
attr_reader :polar
|
46
20
|
end
|
47
21
|
end
|
data/lib/oso/path_mapper.rb
CHANGED
@@ -4,7 +4,7 @@ module Oso
|
|
4
4
|
# Map from a template string with capture groups of the form
|
5
5
|
# `{name}` to a dictionary of the form `{name: captured_value}`
|
6
6
|
class PathMapper
|
7
|
-
def initialize(template
|
7
|
+
def initialize(template)
|
8
8
|
capture_group = /({([^}]+)})/
|
9
9
|
|
10
10
|
template = template.dup
|
data/lib/oso/polar.rb
CHANGED
data/lib/oso/polar/errors.rb
CHANGED
@@ -5,16 +5,12 @@ module Oso
|
|
5
5
|
# Base error type for Oso::Polar.
|
6
6
|
class Error < ::RuntimeError
|
7
7
|
attr_reader :stack_trace
|
8
|
-
|
8
|
+
|
9
9
|
# @param message [String]
|
10
10
|
# @param details [Hash]
|
11
11
|
def initialize(message = nil, details: nil)
|
12
12
|
@details = details
|
13
|
-
|
14
|
-
@stack_trace = details['stack_trace']
|
15
|
-
else
|
16
|
-
@stack_trace = nil
|
17
|
-
end
|
13
|
+
@stack_trace = details&.fetch('stack_trace', nil)
|
18
14
|
super(message)
|
19
15
|
end
|
20
16
|
end
|
@@ -40,12 +36,32 @@ module Oso
|
|
40
36
|
class DuplicateInstanceRegistrationError < PolarRuntimeError; end
|
41
37
|
class InvalidCallError < PolarRuntimeError; end
|
42
38
|
class InvalidConstructorError < PolarRuntimeError; end
|
39
|
+
class InvalidQueryTypeError < PolarRuntimeError; end
|
43
40
|
class InlineQueryFailedError < PolarRuntimeError; end
|
44
41
|
class NullByteInPolarFileError < PolarRuntimeError; end
|
45
42
|
class UnexpectedPolarTypeError < PolarRuntimeError; end
|
43
|
+
class PolarFileAlreadyLoadedError < PolarRuntimeError # rubocop:disable Style/Documentation
|
44
|
+
# @param file [String]
|
45
|
+
def initialize(file)
|
46
|
+
super("File #{file} has already been loaded.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
class PolarFileContentsChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
|
50
|
+
# @param file [String]
|
51
|
+
def initialize(file)
|
52
|
+
super("A file with the name #{file}, but different contents, has already been loaded.")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
class PolarFileNameChangedError < PolarRuntimeError # rubocop:disable Style/Documentation
|
56
|
+
# @param file [String]
|
57
|
+
# @param existing [String]
|
58
|
+
def initialize(file, existing)
|
59
|
+
super("A file with the same contents as #{file} named #{existing} has already been loaded.")
|
60
|
+
end
|
61
|
+
end
|
46
62
|
class PolarFileExtensionError < PolarRuntimeError # rubocop:disable Style/Documentation
|
47
|
-
def initialize
|
48
|
-
super(
|
63
|
+
def initialize(file)
|
64
|
+
super("Polar files must have .polar extension. Offending file: #{file}")
|
49
65
|
end
|
50
66
|
end
|
51
67
|
class PolarFileNotFoundError < PolarRuntimeError # rubocop:disable Style/Documentation
|
@@ -58,7 +74,7 @@ module Oso
|
|
58
74
|
# @param as [String]
|
59
75
|
# @param old [Class]
|
60
76
|
# @param new [Class]
|
61
|
-
def initialize(name:, old:, new:)
|
77
|
+
def initialize(name:, old:, new:)
|
62
78
|
super("Attempted to alias #{new} as '#{name}', but #{old} already has that alias.")
|
63
79
|
end
|
64
80
|
end
|
data/lib/oso/polar/ffi.rb
CHANGED
@@ -5,8 +5,12 @@ require 'ffi'
|
|
5
5
|
module Oso
|
6
6
|
module Polar
|
7
7
|
module FFI
|
8
|
-
LIB = ::FFI::Platform::LIBPREFIX
|
9
|
-
|
8
|
+
LIB = "#{::FFI::Platform::LIBPREFIX}polar.#{::FFI::Platform::LIBSUFFIX}"
|
9
|
+
RELEASE_PATH = File.expand_path(File.join(__dir__, "../../../ext/oso-oso/lib/#{LIB}"))
|
10
|
+
DEV_PATH = File.expand_path(File.join(__dir__, "../../../../../target/debug/#{LIB}"))
|
11
|
+
# If the lib exists in the ext/ dir, use it. Otherwise, fall back to
|
12
|
+
# checking the local Rust target dir.
|
13
|
+
LIB_PATH = File.file?(RELEASE_PATH) ? RELEASE_PATH : DEV_PATH
|
10
14
|
|
11
15
|
# Wrapper classes defined upfront to fix Ruby loading issues. Actual
|
12
16
|
# implementations live in the sibling `ffi/` directory and are `require`d
|
data/lib/oso/polar/ffi/error.rb
CHANGED
@@ -22,7 +22,7 @@ module Oso
|
|
22
22
|
#
|
23
23
|
# @return [::Oso::Polar::Error] if there's an FFI error.
|
24
24
|
# @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
|
25
|
-
def self.get # rubocop:disable Metrics/
|
25
|
+
def self.get # rubocop:disable Metrics/MethodLength
|
26
26
|
error = Rust.get
|
27
27
|
return ::Oso::Polar::FFIErrorNotFound if error.null?
|
28
28
|
|
@@ -48,7 +48,7 @@ module Oso
|
|
48
48
|
# @param msg [String]
|
49
49
|
# @param details [Hash<String, Object>]
|
50
50
|
# @return [::Oso::Polar::ParseError] the object converted into the expected format.
|
51
|
-
private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/
|
51
|
+
private_class_method def self.parse_error(kind, msg:, details:) # rubocop:disable Metrics/MethodLength
|
52
52
|
case kind
|
53
53
|
when 'ExtraToken'
|
54
54
|
::Oso::Polar::ParseError::ExtraToken.new(msg, details: details)
|
data/lib/oso/polar/ffi/polar.rb
CHANGED
@@ -11,11 +11,10 @@ module Oso
|
|
11
11
|
|
12
12
|
attach_function :new, :polar_new, [], FFI::Polar
|
13
13
|
attach_function :load_str, :polar_load, [FFI::Polar, :string, :string], :int32
|
14
|
-
attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar], FFI::Query
|
14
|
+
attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
|
15
15
|
attach_function :new_id, :polar_get_external_id, [FFI::Polar], :uint64
|
16
|
-
attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string], FFI::Query
|
17
|
-
attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string], FFI::Query
|
18
|
-
attach_function :new_query_from_repl, :polar_query_from_repl, [FFI::Polar], FFI::Query
|
16
|
+
attach_function :new_query_from_str, :polar_new_query, [FFI::Polar, :string, :uint32], FFI::Query
|
17
|
+
attach_function :new_query_from_term, :polar_new_query_from_term, [FFI::Polar, :string, :uint32], FFI::Query
|
19
18
|
attach_function :register_constant, :polar_register_constant, [FFI::Polar, :string, :string], :int32
|
20
19
|
attach_function :free, :polar_free, [FFI::Polar], :int32
|
21
20
|
end
|
@@ -41,7 +40,7 @@ module Oso
|
|
41
40
|
# @return [nil] if there are no remaining inline queries.
|
42
41
|
# @raise [FFI::Error] if the FFI call returns an error.
|
43
42
|
def next_inline_query
|
44
|
-
query = Rust.next_inline_query(self)
|
43
|
+
query = Rust.next_inline_query(self, 0)
|
45
44
|
query.null? ? nil : query
|
46
45
|
end
|
47
46
|
|
@@ -60,7 +59,7 @@ module Oso
|
|
60
59
|
# @return [FFI::Query]
|
61
60
|
# @raise [FFI::Error] if the FFI call returns an error.
|
62
61
|
def new_query_from_str(str)
|
63
|
-
query = Rust.new_query_from_str(self, str)
|
62
|
+
query = Rust.new_query_from_str(self, str, 0)
|
64
63
|
raise FFI::Error.get if query.null?
|
65
64
|
|
66
65
|
query
|
@@ -70,16 +69,7 @@ module Oso
|
|
70
69
|
# @return [FFI::Query]
|
71
70
|
# @raise [FFI::Error] if the FFI call returns an error.
|
72
71
|
def new_query_from_term(term)
|
73
|
-
query = Rust.new_query_from_term(self, JSON.dump(term))
|
74
|
-
raise FFI::Error.get if query.null?
|
75
|
-
|
76
|
-
query
|
77
|
-
end
|
78
|
-
|
79
|
-
# @return [FFI::Query]
|
80
|
-
# @raise [FFI::Error] if the FFI call returns an error.
|
81
|
-
def new_query_from_repl
|
82
|
-
query = Rust.new_query_from_repl(self)
|
72
|
+
query = Rust.new_query_from_term(self, JSON.dump(term), 0)
|
83
73
|
raise FFI::Error.get if query.null?
|
84
74
|
|
85
75
|
query
|
data/lib/oso/polar/ffi/query.rb
CHANGED
@@ -12,6 +12,7 @@ module Oso
|
|
12
12
|
attach_function :debug_command, :polar_debug_command, [FFI::Query, :string], :int32
|
13
13
|
attach_function :call_result, :polar_call_result, [FFI::Query, :uint64, :string], :int32
|
14
14
|
attach_function :question_result, :polar_question_result, [FFI::Query, :uint64, :int32], :int32
|
15
|
+
attach_function :application_error, :polar_application_error, [FFI::Query, :string], :int32
|
15
16
|
attach_function :next_event, :polar_next_query_event, [FFI::Query], FFI::QueryEvent
|
16
17
|
attach_function :free, :query_free, [FFI::Query], :int32
|
17
18
|
end
|
@@ -41,6 +42,14 @@ module Oso
|
|
41
42
|
raise FFI::Error.get if res.zero?
|
42
43
|
end
|
43
44
|
|
45
|
+
# @param result [Boolean]
|
46
|
+
# @param call_id [Integer]
|
47
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
48
|
+
def application_error(message)
|
49
|
+
res = Rust.application_error(self, message)
|
50
|
+
raise FFI::Error.get if res.zero?
|
51
|
+
end
|
52
|
+
|
44
53
|
# @return [::Oso::Polar::QueryEvent]
|
45
54
|
# @raise [FFI::Error] if the FFI call returns an error.
|
46
55
|
def next_event
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Oso
|
4
|
+
module Polar
|
5
|
+
# Translate between Polar and the host language (Ruby).
|
6
|
+
class Host # rubocop:disable Metrics/ClassLength
|
7
|
+
protected
|
8
|
+
|
9
|
+
# @return [FFI::Polar]
|
10
|
+
attr_reader :ffi_polar
|
11
|
+
# @return [Hash<String, Class>]
|
12
|
+
attr_reader :classes
|
13
|
+
# @return [Hash<String, Object>]
|
14
|
+
attr_reader :constructors
|
15
|
+
# @return [Hash<Integer, Object>]
|
16
|
+
attr_reader :instances
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
def initialize(ffi_polar)
|
21
|
+
@ffi_polar = ffi_polar
|
22
|
+
@classes = {}
|
23
|
+
@constructors = {}
|
24
|
+
@instances = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_copy(other)
|
28
|
+
@ffi_polar = other.ffi_polar
|
29
|
+
@classes = other.classes.dup
|
30
|
+
@constructors = other.constructors.dup
|
31
|
+
@instances = other.instances.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
# Fetch a Ruby class from the {#classes} cache.
|
35
|
+
#
|
36
|
+
# @param name [String]
|
37
|
+
# @return [Class]
|
38
|
+
# @raise [UnregisteredClassError] if the class has not been registered.
|
39
|
+
def get_class(name)
|
40
|
+
raise UnregisteredClassError, name unless classes.key? name
|
41
|
+
|
42
|
+
classes[name]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Store a Ruby class in the {#classes} cache.
|
46
|
+
#
|
47
|
+
# @param cls [Class] the class to cache
|
48
|
+
# @param name [String] the name to cache the class as. Defaults to the name of the class.
|
49
|
+
# @return [String] the name the class is cached as.
|
50
|
+
# @raise [UnregisteredClassError] if the class has not been registered.
|
51
|
+
def cache_class(cls, name:, constructor:) # rubocop:disable Metrics/MethodLength
|
52
|
+
name = cls.name if name.nil?
|
53
|
+
raise DuplicateClassAliasError, name: name, old: get_class(name), new: cls if classes.key? name
|
54
|
+
|
55
|
+
classes[name] = cls
|
56
|
+
if constructor.nil?
|
57
|
+
constructors[name] = :new
|
58
|
+
elsif constructor.respond_to? :call
|
59
|
+
constructors[name] = constructor
|
60
|
+
else
|
61
|
+
raise InvalidConstructorError
|
62
|
+
end
|
63
|
+
name
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fetch a constructor from the {#constructors} cache.
|
67
|
+
#
|
68
|
+
# @param name [String]
|
69
|
+
# @return [Symbol] if constructor is the default of `:new`.
|
70
|
+
# @return [Proc] if a custom constructor was registered.
|
71
|
+
# @raise [UnregisteredConstructorError] if the constructor has not been registered.
|
72
|
+
def get_constructor(name)
|
73
|
+
raise MissingConstructorError, name unless constructors.key? name
|
74
|
+
|
75
|
+
constructors[name]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if an instance has been cached.
|
79
|
+
#
|
80
|
+
# @param id [Integer]
|
81
|
+
# @return [Boolean]
|
82
|
+
def instance?(id)
|
83
|
+
case id
|
84
|
+
when Integer
|
85
|
+
instances.key? id
|
86
|
+
else
|
87
|
+
instances.value? id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Fetch a Ruby instance from the {#instances} cache.
|
92
|
+
#
|
93
|
+
# @param id [Integer]
|
94
|
+
# @return [Object]
|
95
|
+
# @raise [UnregisteredInstanceError] if the ID has not been registered.
|
96
|
+
def get_instance(id)
|
97
|
+
raise UnregisteredInstanceError, id unless instance? id
|
98
|
+
|
99
|
+
instances[id]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Cache a Ruby instance, fetching a {#new_id} if one isn't provided.
|
103
|
+
#
|
104
|
+
# @param instance [Object]
|
105
|
+
# @param id [Integer]
|
106
|
+
# @return [Integer]
|
107
|
+
def cache_instance(instance, id: nil)
|
108
|
+
id = ffi_polar.new_id if id.nil?
|
109
|
+
instances[id] = instance
|
110
|
+
id
|
111
|
+
end
|
112
|
+
|
113
|
+
# Construct and cache a Ruby instance.
|
114
|
+
#
|
115
|
+
# @param cls_name [String]
|
116
|
+
# @param initargs [Hash<String, Hash>]
|
117
|
+
# @param id [Integer]
|
118
|
+
# @raise [PolarRuntimeError] if instance construction fails.
|
119
|
+
def make_instance(cls_name, initargs:, id:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
120
|
+
constructor = get_constructor(cls_name)
|
121
|
+
instance = if constructor == :new
|
122
|
+
if initargs.empty?
|
123
|
+
get_class(cls_name).__send__(:new)
|
124
|
+
elsif initargs.is_a? Array
|
125
|
+
get_class(cls_name).__send__(:new, *initargs)
|
126
|
+
elsif initargs.is_a? Hash
|
127
|
+
get_class(cls_name).__send__(:new, **initargs)
|
128
|
+
else
|
129
|
+
raise PolarRuntimeError, "Bad initargs: #{initargs}"
|
130
|
+
end
|
131
|
+
elsif initargs.empty?
|
132
|
+
constructor.call
|
133
|
+
elsif initargs.is_a? Array
|
134
|
+
constructor.call(*initargs)
|
135
|
+
elsif initargs.is_a? Hash
|
136
|
+
constructor.call(**initargs)
|
137
|
+
else
|
138
|
+
raise PolarRuntimeError, "Bad initargs: #{initargs}"
|
139
|
+
end
|
140
|
+
cache_instance(instance, id: id)
|
141
|
+
rescue StandardError => e
|
142
|
+
raise PolarRuntimeError, "Error constructing instance of #{cls_name}: #{e}"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Check if the left class is more specific than the right class
|
146
|
+
# with respect to the given instance.
|
147
|
+
#
|
148
|
+
# @param instance_id [Integer]
|
149
|
+
# @param left_tag [String]
|
150
|
+
# @param right_tag [String]
|
151
|
+
# @return [Boolean]
|
152
|
+
def subspecializer?(instance_id, left_tag:, right_tag:)
|
153
|
+
mro = get_instance(instance_id).class.ancestors
|
154
|
+
mro.index(get_class(left_tag)) < mro.index(get_class(right_tag))
|
155
|
+
rescue StandardError
|
156
|
+
false
|
157
|
+
end
|
158
|
+
|
159
|
+
# Check if instance is an instance of class.
|
160
|
+
#
|
161
|
+
# @param instance [Hash<String, Object>]
|
162
|
+
# @param class_tag [String]
|
163
|
+
# @return [Boolean]
|
164
|
+
def isa?(instance, class_tag:)
|
165
|
+
instance = to_ruby(instance)
|
166
|
+
cls = get_class(class_tag)
|
167
|
+
instance.is_a? cls
|
168
|
+
rescue PolarRuntimeError
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
# Check if two instances unify
|
173
|
+
#
|
174
|
+
# @param left_instance_id [Integer]
|
175
|
+
# @param right_instance_id [Integer]
|
176
|
+
# @return [Boolean]
|
177
|
+
def unify?(left_instance_id, right_instance_id)
|
178
|
+
left_instance = get_instance(left_instance_id)
|
179
|
+
right_instance = get_instance(right_instance_id)
|
180
|
+
left_instance == right_instance
|
181
|
+
rescue PolarRuntimeError
|
182
|
+
false
|
183
|
+
end
|
184
|
+
|
185
|
+
# Turn a Ruby value into a Polar term that's ready to be sent across the
|
186
|
+
# FFI boundary.
|
187
|
+
#
|
188
|
+
# @param value [Object]
|
189
|
+
# @return [Hash<String, Object>]
|
190
|
+
def to_polar_term(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
191
|
+
value = case true # rubocop:disable Lint/LiteralAsCondition
|
192
|
+
when value.instance_of?(TrueClass) || value.instance_of?(FalseClass)
|
193
|
+
{ 'Boolean' => value }
|
194
|
+
when value.instance_of?(Integer)
|
195
|
+
{ 'Number' => { 'Integer' => value } }
|
196
|
+
when value.instance_of?(Float)
|
197
|
+
{ 'Number' => { 'Float' => value } }
|
198
|
+
when value.instance_of?(String)
|
199
|
+
{ 'String' => value }
|
200
|
+
when value.instance_of?(Array)
|
201
|
+
{ 'List' => value.map { |el| to_polar_term(el) } }
|
202
|
+
when value.instance_of?(Hash)
|
203
|
+
{ 'Dictionary' => { 'fields' => value.transform_values { |v| to_polar_term(v) } } }
|
204
|
+
when value.instance_of?(Predicate)
|
205
|
+
{ 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar_term(el) } } }
|
206
|
+
when value.instance_of?(Variable)
|
207
|
+
# This is supported so that we can query for unbound variables
|
208
|
+
{ 'Variable' => value }
|
209
|
+
else
|
210
|
+
{ 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => value.to_s } }
|
211
|
+
end
|
212
|
+
{ 'value' => value }
|
213
|
+
end
|
214
|
+
|
215
|
+
# Turn a Polar term passed across the FFI boundary into a Ruby value.
|
216
|
+
#
|
217
|
+
# @param data [Hash<String, Object>]
|
218
|
+
# @option data [Integer] :id
|
219
|
+
# @option data [Integer] :offset Character offset of the term in its source string.
|
220
|
+
# @option data [Hash<String, Object>] :value
|
221
|
+
# @return [Object]
|
222
|
+
# @raise [UnexpectedPolarTypeError] if type cannot be converted to Ruby.
|
223
|
+
def to_ruby(data) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
224
|
+
tag, value = data['value'].first
|
225
|
+
case tag
|
226
|
+
when 'String', 'Boolean'
|
227
|
+
value
|
228
|
+
when 'Number'
|
229
|
+
value.values.first
|
230
|
+
when 'List'
|
231
|
+
value.map { |el| to_ruby(el) }
|
232
|
+
when 'Dictionary'
|
233
|
+
value['fields'].transform_values { |v| to_ruby(v) }
|
234
|
+
when 'ExternalInstance'
|
235
|
+
get_instance(value['instance_id'])
|
236
|
+
when 'Call'
|
237
|
+
Predicate.new(value['name'], args: value['args'].map { |a| to_ruby(a) })
|
238
|
+
when 'Variable'
|
239
|
+
Variable.new(value['name'])
|
240
|
+
else
|
241
|
+
raise UnexpectedPolarTypeError, tag
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|