oso-oso 0.2.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|