Skip to content

Commit 7723acb

Browse files
committed
Merge branch 'ruby-ports'
2 parents c505796 + 3f40a33 commit 7723acb

4 files changed

Lines changed: 293 additions & 7 deletions

File tree

ports-rb/json-rfc.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
require "json"
2+
require_relative "ports"
3+
4+
suite "suites/json-rfc.ports" do
5+
placeholder "parse" do |_env, json_string|
6+
JSON.parse(json_string)
7+
rescue JSON::ParserError => e
8+
e
9+
end
10+
11+
placeholder "list-json-test-files" do |_env|
12+
Dir.glob("suites/json-rfc-fixtures/*.json").map { |path| File.basename(path) }
13+
end
14+
15+
placeholder "parse-success?" do |_env, parse_result|
16+
!parse_result.is_a?(JSON::ParserError)
17+
end
18+
19+
placeholder "file-contents" do |_env, filename|
20+
File.read(File.join("suites", "json-rfc-fixtures", filename))
21+
end
22+
end

ports-rb/ports.rb

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
require "json"
2+
require "test/unit"
3+
require_relative "scheme"
4+
5+
class PortsFunction
6+
attr_accessor :function, :env
7+
8+
def initialize(func, env)
9+
@function = func
10+
@env = env
11+
end
12+
13+
def valid?
14+
!@function.nil?
15+
end
16+
17+
def call(*args)
18+
@function.call(@env, *args)
19+
end
20+
21+
def [](key)
22+
if key == 0
23+
ports_role
24+
elsif key == 1
25+
self
26+
end
27+
end
28+
end
29+
30+
class PortsSetup < PortsFunction
31+
def ports_role
32+
:setup
33+
end
34+
end
35+
36+
class PortsTearDown < PortsFunction
37+
def ports_role
38+
:tearDown
39+
end
40+
end
41+
42+
class Placeholder < PortsFunction
43+
attr_accessor :name, :parameters, :doc_string
44+
45+
def initialize(name, parameters, doc_string)
46+
super(nil, nil)
47+
@name = name
48+
@parameters = parameters
49+
@doc_string = doc_string
50+
end
51+
52+
def [](key)
53+
:placeholder if key == 0
54+
end
55+
56+
def template_string
57+
params = ["env"]
58+
params += parameters.map { |param| param.to_s.tr("-", "_") }
59+
60+
<<~TEMPLATE
61+
placeholder "#{name}" do |#{params.join(", ")}|
62+
# TODO: Implement
63+
end
64+
TEMPLATE
65+
end
66+
end
67+
68+
def ports_assert(value, msg = "")
69+
raise Test::Unit::AssertionFailedError, msg || "Expected #{value} to be truthy" unless value
70+
end
71+
72+
def ports_assert_eq(expected, actual)
73+
raise Test::Unit::AssertionFailedError, "Expected #{actual} to be #{expected}" unless expected == actual
74+
end
75+
76+
class PortsSuite
77+
attr_reader :scheme_env, :suite_name, :suite_version, :sources,
78+
:placeholders, :root_capability
79+
80+
def initialize(file_name)
81+
@scheme_env = Scheme::Environment.new([], [], Scheme::GLOBAL_ENV)
82+
initialize_ports_primitives
83+
initialize_ports
84+
@suite_source = File.read(file_name)
85+
86+
@suite_name = nil
87+
@suite_version = nil
88+
@sources = nil
89+
@placeholders = nil
90+
@root_capability = nil
91+
92+
@placeholder_functions = {}
93+
@set_up_functions = []
94+
@tear_down_functions = []
95+
end
96+
97+
def initialize_suite
98+
@suite_name, @suite_version, @sources,
99+
@placeholders, @root_capability = eval_scheme(@suite_source)
100+
end
101+
102+
def eval_scheme(code, env = nil)
103+
env ||= @scheme_env
104+
Scheme.evaluate_string(code, env)
105+
end
106+
107+
def eval_scheme_with_args(code, **kwargs)
108+
env = Scheme::Environment.new([], [], @scheme_env)
109+
kwargs.each do |key, value|
110+
env[key.to_sym] = value
111+
end
112+
eval_scheme(code, env)
113+
end
114+
115+
def create_placeholder(name, parameters, doc_string = "")
116+
new_placeholder = Placeholder.new(name, parameters, doc_string)
117+
@scheme_env[name] = new_placeholder
118+
new_placeholder.env = @scheme_env
119+
120+
if @placeholder_functions[name]
121+
new_placeholder.function = @placeholder_functions[name]
122+
end
123+
124+
new_placeholder
125+
end
126+
127+
def initialize_ports_primitives
128+
primitives = {
129+
"create-placeholder" => lambda { |*args| create_placeholder(*args) },
130+
"is-placeholder?" => lambda { |x| x.is_a?(Placeholder) },
131+
"assert" => method(:ports_assert),
132+
"assert-equal" => method(:ports_assert_eq),
133+
"is-assertion-error?" => lambda { |e| e.is_a?(Test::Unit::AssertionFailedError) }
134+
}
135+
136+
primitives.each do |key, value|
137+
@scheme_env[key.to_sym] = value
138+
end
139+
end
140+
141+
def initialize_ports
142+
ports_content = File.read("ports/ports.scm")
143+
eval_scheme(ports_content)
144+
end
145+
146+
def placeholder(name, &func)
147+
@placeholder_functions[name.to_sym] = func
148+
func
149+
end
150+
151+
def set_up(&func)
152+
@set_up_functions << func
153+
func
154+
end
155+
156+
def tear_down(&func)
157+
@tear_down_functions << func
158+
func
159+
end
160+
161+
def ensure_placeholders_are_valid
162+
invalid_placeholders = @placeholders.reject(&:valid?)
163+
return if invalid_placeholders.empty?
164+
165+
invalid_placeholder_list = invalid_placeholders.map { |p| "- #{p.name}" }.join("\n")
166+
placeholders_suggestion = invalid_placeholders.map(&:template_string).join("\n")
167+
raise [
168+
"Your test suite is missing definitions for the following placeholders:\n#{invalid_placeholder_list}\n",
169+
"Implement the missing placeholders:\n#{placeholders_suggestion}"
170+
].join("\n")
171+
end
172+
173+
def install_set_up_tear_down_functions
174+
@set_up_functions.each do |func|
175+
@root_capability[2].unshift(PortsSetup.new(func, @scheme_env))
176+
end
177+
@tear_down_functions.each do |func|
178+
@root_capability[3].unshift(PortsTearDown.new(func, @scheme_env))
179+
end
180+
end
181+
182+
def run(options = {})
183+
only = options[:only]
184+
only_capabilities = options[:only_capabilities]
185+
exclude = options[:exclude]
186+
exclude_capabilities = options[:exclude_capabilities]
187+
expected_failures = options[:expected_failures] || []
188+
189+
initialize_suite
190+
install_set_up_tear_down_functions
191+
ensure_placeholders_are_valid
192+
193+
# We set the root-capability in the env, as we need it repeatedly
194+
@scheme_env[:"root-capability"] = @root_capability
195+
196+
eval_scheme_with_args(
197+
"(run-suite suite_name suite_version root-capability only_tests only_capabilities exclude exclude_capabilities expected_failures)",
198+
suite_name: @suite_name,
199+
suite_version: @suite_version,
200+
only_tests: only,
201+
only_capabilities: only_capabilities,
202+
exclude: exclude,
203+
exclude_capabilities: exclude_capabilities,
204+
expected_failures: expected_failures
205+
)
206+
end
207+
end
208+
209+
def suite(file_name, &block)
210+
obj = PortsSuite.new(file_name)
211+
obj.instance_eval(&block)
212+
obj.run
213+
end

ports-rb/scheme.rb

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,44 @@ def call(*args)
5959
:< => proc { |a, b| a < b },
6060
:<= => proc { |a, b| a <= b },
6161
:"=" => proc { |a, b| a == b },
62-
:list => proc { |*args| args },
63-
:cons => proc { |a, b| [a] + b },
64-
:car => :first.to_proc,
65-
:cdr => proc { |a| a[1..] },
62+
:abs => proc { |a| a.abs },
6663
:append => proc { |*args| args.flatten(1) },
64+
:apply => proc { |procedure, list| procedure.call(*list) },
65+
:boolean? => proc { |a| [true, false].include?(a) },
66+
:car => proc { |a| a[0] },
67+
:cdr => proc { |a| a[1..] },
68+
:cons => proc { |a, b| [a] + b },
6769
:display => proc { |a| print a },
70+
:eq? => proc { |a, b| a == b },
71+
:equal? => proc { |a, b| a == b },
72+
:error => proc { |msg| StandardError.new(msg) },
73+
:exit => proc { |a| Kernel.exit(a) },
6874
:inexact => proc { |a| a.to_f },
6975
:length => :length.to_proc,
76+
:list? => proc { |a| a.is_a?(Array) },
77+
:list => proc { |*args| args },
78+
:"list-ref" => proc { |list, idx| list[idx] },
79+
:"list-set!" => proc { |list, idx, value| list[idx] = value },
7080
:not => proc { |a| !a },
7181
:null? => proc { |a| a.nil? || a.empty? },
7282
:pair? => proc { |tokens| tokens.is_a?(Array) && !tokens.empty? },
73-
:sqrt => proc { |a| Math.sqrt(a) }
83+
:raise => proc { |e| raise e },
84+
:sqrt => proc { |a| Math.sqrt(a) },
85+
:"string-append" => proc { |*strings| strings.join("") },
86+
:"string-downcase" => proc { |s| s.downcase },
87+
:"string-index" => proc { |s, sub| s.index(sub) || false },
88+
:"string-replace" => proc { |old, new, s| s.gsub(old, new) },
89+
:"string-split" => proc { |s, sep| s.split(sep) },
90+
:"string-trim" => proc { |s| s.strip },
91+
:"string-upcase" => proc { |s| s.upcase },
92+
:symbol? => proc { |a| a.is_a?(Symbol) },
93+
:"with-exception-handler" => proc { |handler, body|
94+
begin
95+
body.call
96+
rescue => e
97+
handler.call(e)
98+
end
99+
}
74100
}.entries.transpose
75101
GLOBAL_ENV = Environment.new(GLOBAL_DICT[0], GLOBAL_DICT[1])
76102

@@ -328,8 +354,8 @@ def evaluate(tokens, environment = GLOBAL_ENV)
328354
end
329355
end
330356

331-
def evaluate_string(source)
332-
Scheme.evaluate(Scheme.expand(Parser.parse_string(source), toplevel: true))
357+
def evaluate_string(source, env = GLOBAL_ENV)
358+
Scheme.evaluate(Scheme.expand(Parser.parse_string(source), toplevel: true), env)
333359
end
334360

335361
module_function :evaluate_string, :evaluate, :expand, :pair?, :require_syntax,

ports-rb/url_parsing.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "uri"
2+
require_relative "ports"
3+
4+
suite "suites/url-parsing-RFC.ports" do
5+
placeholder "url-parse" do |_env, url_string|
6+
URI.parse(url_string)
7+
rescue URI::InvalidURIError => e
8+
e
9+
end
10+
11+
placeholder "parse-error?" do |_env, parse_result|
12+
parse_result.is_a?(URI::InvalidURIError)
13+
end
14+
15+
placeholder "url-scheme" do |_env, uri|
16+
uri.scheme
17+
end
18+
19+
placeholder "url-authority" do |_env, uri|
20+
authority = uri.host
21+
authority = "#{uri.userinfo}@#{authority}" if uri.userinfo
22+
authority = "#{authority}:#{uri.port}" if uri.port
23+
authority
24+
end
25+
end

0 commit comments

Comments
 (0)