loxby 0.0.2 → 0.0.3
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 +4 -4
- data/.github/workflows/gem-push.yml +2 -2
- data/.gitignore +8 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +4 -1
- data/bin/loxby +2 -2
- data/lib/loxby/config.rb +93 -0
- data/lib/loxby/core.rb +24 -14
- data/lib/loxby/helpers/ast.rb +18 -31
- data/lib/loxby/helpers/callable.rb +18 -0
- data/lib/loxby/helpers/environment.rb +18 -7
- data/lib/loxby/helpers/errors.rb +7 -1
- data/lib/loxby/helpers/functions.rb +40 -0
- data/lib/loxby/helpers/native_functions.rb +48 -0
- data/lib/loxby/helpers/token_type.rb +9 -21
- data/lib/loxby/interpreter.rb +76 -3
- data/lib/loxby/parser.rb +196 -11
- data/lib/loxby/runner.rb +13 -5
- data/lib/loxby/scanner.rb +33 -45
- data/lib/loxby/version.rb +2 -2
- data/lib/loxby/visitors/ast_printer.rb +18 -0
- data/lib/loxby/visitors/base.rb +1 -1
- data/lib/loxby/visitors/rpn_converter.rb +2 -0
- data/lib/loxby.rb +0 -0
- data/loxby.gemspec +7 -4
- metadata +13 -7
- data/.rubocop.yml +0 -8
- data/Gemfile.lock +0 -47
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 425095fb701b73736edd3558262a10753ae0b900389f47a5dcf50e77b556ad97
|
|
4
|
+
data.tar.gz: 05c522b5c1d6b099fe73748ddf8d041e6b49cf64335d727c3b5e251beced24c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ffca94d2a7d18ce137f7b1fda7fd09ef76530c361e5553df0bfb4764e8d5876f4f442b219c8393bc6708d76e10a4acfc41146c535e13ec89ff9f9bcea4e525a
|
|
7
|
+
data.tar.gz: 570bbfcc023ca9c697717c8a91810411b89c333e1e2f452970cfe500ed4d845c7b094ac8ca9ed2997e79b583c7b236edbb1a9a2f8b49f2bb20003a657ca51523
|
|
@@ -12,12 +12,12 @@ jobs:
|
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
14
|
- uses: actions/checkout@v4
|
|
15
|
-
- name: Set up Ruby
|
|
15
|
+
- name: Set up Ruby
|
|
16
16
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
17
17
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
18
18
|
uses: ruby/setup-ruby@v1
|
|
19
19
|
with:
|
|
20
|
-
ruby-version: 3.
|
|
20
|
+
ruby-version: 3.3.5
|
|
21
21
|
|
|
22
22
|
- name: Publish to GPR
|
|
23
23
|
run: |
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.3.5
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Paul Hartman
|
|
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
CHANGED
|
@@ -10,4 +10,7 @@ Loxby is written following the first half of Robert Nystrom's wonderful web-form
|
|
|
10
10
|
```ruby
|
|
11
11
|
require 'loxby/runner'
|
|
12
12
|
Lox::Runner.new(ARGV, $stdout, $stderr)
|
|
13
|
-
```
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
This gem is licensed under the [MIT License](https://opensource.org/license/mit). See LICENSE.txt for more.
|
data/bin/loxby
CHANGED
data/lib/loxby/config.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry-configurable'
|
|
4
|
+
require_relative 'helpers/errors'
|
|
5
|
+
|
|
6
|
+
class Lox # rubocop:disable Style/Documentation
|
|
7
|
+
extend Dry::Configurable
|
|
8
|
+
|
|
9
|
+
setting :scanner do
|
|
10
|
+
setting :expressions do
|
|
11
|
+
setting :whitespace, default: /\s/
|
|
12
|
+
setting :number_literal, default: /\d/
|
|
13
|
+
setting :identifier, default: /[a-zA-Z_]/
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
setting :keywords,
|
|
17
|
+
default: %w[and class else false for fun if nil or print return super this true var while break]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
setting :token_types do
|
|
21
|
+
setting :tokens, default: [
|
|
22
|
+
# Single-character tokens.
|
|
23
|
+
:left_paren, :right_paren, :left_brace, :right_brace,
|
|
24
|
+
:comma, :dot, :minus, :plus, :semicolon, :slash, :star,
|
|
25
|
+
:question, :colon,
|
|
26
|
+
|
|
27
|
+
# 1-2 character tokens.
|
|
28
|
+
:bang, :bang_equal,
|
|
29
|
+
:equal, :equal_equal,
|
|
30
|
+
:greater, :greater_equal,
|
|
31
|
+
:less, :less_equal,
|
|
32
|
+
|
|
33
|
+
# Literals.
|
|
34
|
+
:identifier, :string, :number,
|
|
35
|
+
|
|
36
|
+
# Keywords.
|
|
37
|
+
:and, :class, :else, :false, :fun, :for, :if, :nil, :or,
|
|
38
|
+
:print, :return, :super, :this, :true, :var, :while, :break,
|
|
39
|
+
|
|
40
|
+
:eof
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
setting :single_tokens,
|
|
44
|
+
default: '(){},.-+;/*?:',
|
|
45
|
+
constructor: -> { _1.split('').zip(Lox.config.token_types.tokens).to_h }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
setting :native_functions do
|
|
49
|
+
setting :clock do
|
|
50
|
+
setting :arity, default: 0
|
|
51
|
+
setting :block, default: ->(_, _) { Time.now.to_i.to_f }
|
|
52
|
+
end
|
|
53
|
+
setting :exit do
|
|
54
|
+
setting :arity, default: 0
|
|
55
|
+
setting :block, default: ->(_, _) { throw :lox_exit }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
setting :ast do
|
|
60
|
+
setting :expression,
|
|
61
|
+
default: {
|
|
62
|
+
assign: [%i[token name], %i[expr value]],
|
|
63
|
+
binary: [%i[expr left], %i[token operator], %i[expr right]],
|
|
64
|
+
ternary: [%i[expr left], %i[token left_operator], %i[expr center], %i[token right_operator],
|
|
65
|
+
%i[expr right]],
|
|
66
|
+
call: [%i[expr callee], %i[token paren], %i[expr_list arguments]],
|
|
67
|
+
grouping: [%i[expr expression]],
|
|
68
|
+
literal: [%i[object value]],
|
|
69
|
+
logical: [%i[expr left], %i[token operator], %i[expr right]],
|
|
70
|
+
unary: [%i[token operator], %i[expr right]],
|
|
71
|
+
variable: [%i[token name]]
|
|
72
|
+
}
|
|
73
|
+
setting :statement,
|
|
74
|
+
default: {
|
|
75
|
+
block: [%i[stmt_list statements]],
|
|
76
|
+
expression: [%i[expr expression]],
|
|
77
|
+
function: [%i[token name], %i[token_list params], %i[stmt_list body]],
|
|
78
|
+
if: [%i[expr condition], %i[stmt then_branch], %i[stmt else_branch]],
|
|
79
|
+
print: [%i[expr expression]],
|
|
80
|
+
return: [%i[token keyword], %i[expr value]],
|
|
81
|
+
var: [%i[token name], %i[expr initializer]],
|
|
82
|
+
while: [%i[expr condition], %i[stmt body]],
|
|
83
|
+
break: []
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
setting :exit_code do
|
|
88
|
+
setting :interrupt, default: 130
|
|
89
|
+
setting :usage, default: 64
|
|
90
|
+
setting :syntax_error, default: 65
|
|
91
|
+
setting :runtime_error, default: 70
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/loxby/core.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'scanner'
|
|
|
4
4
|
require_relative 'parser'
|
|
5
5
|
require_relative 'interpreter'
|
|
6
6
|
require_relative 'helpers/token_type'
|
|
7
|
+
require_relative 'config'
|
|
7
8
|
|
|
8
9
|
# Lox interpreter.
|
|
9
10
|
# Each interpreter keeps track of its own
|
|
@@ -13,36 +14,45 @@ class Lox
|
|
|
13
14
|
attr_reader :errored, :interpreter
|
|
14
15
|
|
|
15
16
|
def initialize
|
|
17
|
+
# Whether an error occurred while parsing.
|
|
16
18
|
@errored = false
|
|
19
|
+
# Whether an error occurred while interpreting
|
|
17
20
|
@errored_in_runtime = false
|
|
18
|
-
|
|
21
|
+
# `Lox::Interpreter` instance. Static so interactive sessions reuse it
|
|
22
|
+
@interpreter = Interpreter.new(self)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
#
|
|
25
|
+
# Parse and run a file
|
|
22
26
|
def run_file(path)
|
|
23
27
|
if File.exist? path
|
|
24
|
-
|
|
28
|
+
catch(:lox_exit) do
|
|
29
|
+
run File.read(path)
|
|
30
|
+
end
|
|
25
31
|
else
|
|
26
32
|
report(0, '', "No such file: '#{path}'")
|
|
27
33
|
end
|
|
28
|
-
exit
|
|
29
|
-
exit
|
|
34
|
+
exit Lox.config.exit_code.syntax_error if @errored # Don't execute malformed code
|
|
35
|
+
exit Lox.config.exit_code.runtime_error if @errored_in_runtime
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
# Run interactively
|
|
38
|
+
# Run interactively, REPL-style
|
|
33
39
|
def run_prompt
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
catch(:lox_exit) do
|
|
41
|
+
loop do
|
|
42
|
+
print '> '
|
|
43
|
+
line = gets
|
|
44
|
+
break unless line # Trap eof (Ctrl+D unix, Ctrl+Z win)
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
result = run(line)
|
|
47
|
+
puts "=> #{@interpreter.lox_obj_to_str result}" unless @errored
|
|
48
|
+
|
|
49
|
+
# When run interactively, resets after every prompt so as to not kill the repl
|
|
50
|
+
@errored = false
|
|
51
|
+
end
|
|
42
52
|
end
|
|
43
53
|
end
|
|
44
54
|
|
|
45
|
-
#
|
|
55
|
+
# Parse and run a string
|
|
46
56
|
def run(source)
|
|
47
57
|
tokens = Scanner.new(source, self).scan_tokens
|
|
48
58
|
parser = Parser.new(tokens, self)
|
data/lib/loxby/helpers/ast.rb
CHANGED
|
@@ -16,17 +16,22 @@ end
|
|
|
16
16
|
|
|
17
17
|
class Lox
|
|
18
18
|
# Interface:
|
|
19
|
+
# ```ruby
|
|
19
20
|
# Lox::AST.define_ast(
|
|
20
21
|
# "ASTBaseClass",
|
|
21
22
|
# {
|
|
22
|
-
# :ast_type => [
|
|
23
|
+
# :ast_type => [
|
|
24
|
+
# [:field_one_type, :field_one_name],
|
|
25
|
+
# [:field_two_type, :field_two_name]
|
|
26
|
+
# ],
|
|
23
27
|
# :other_ast_type => [[:field_type, :field_name]]
|
|
24
28
|
# }
|
|
25
29
|
# )
|
|
30
|
+
# ```
|
|
26
31
|
#
|
|
27
|
-
# This
|
|
28
|
-
# and
|
|
29
|
-
# which calls `visitor.visit_ast_type(self)
|
|
32
|
+
# This call to `#define_ast` generates `Lox::AST::ASTBaseClass`, as well as `::AstType` and
|
|
33
|
+
# `::OtherAstType` descending from and scoped uner it. Generated classes follow the Visitor
|
|
34
|
+
# pattern: `::AstType` generates with `#accept(visitor)` which calls `visitor.visit_ast_type(self)`.
|
|
30
35
|
module AST
|
|
31
36
|
module_function
|
|
32
37
|
|
|
@@ -43,19 +48,19 @@ class Lox
|
|
|
43
48
|
define_class base_name.to_camel_case, base_class
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
def define_type(base_class, base_class_name, subtype_name, fields) # rubocop:disable Metrics/MethodLength
|
|
51
|
+
def define_type(base_class, base_class_name, subtype_name, fields) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
47
52
|
subtype = Class.new(base_class)
|
|
48
53
|
parameters = fields.map { _1[1].to_s }
|
|
49
54
|
|
|
50
55
|
subtype.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
51
56
|
include Visitable # Visitor pattern
|
|
52
|
-
attr_reader #{parameters.map { ":#{_1}" }.join(', ')}
|
|
57
|
+
#{parameters.empty? ? '' : 'attr_reader '}#{parameters.map { ":#{_1}" }.join(', ')}
|
|
53
58
|
def initialize(#{parameters.map { "#{_1}:" }.join(', ')})
|
|
54
|
-
#{parameters.map { "@#{_1}" }.join(', ')} = #{parameters.join ', '}
|
|
59
|
+
#{parameters.map { "@#{_1}" }.join(', ')}#{parameters.empty? ? '' : ' = '}#{parameters.join ', '}
|
|
55
60
|
end
|
|
56
61
|
|
|
57
|
-
#
|
|
58
|
-
# Expects
|
|
62
|
+
# This function was dynamically generated for visitor pattern.
|
|
63
|
+
# Expects visitors to define `#visit_#{subtype_name}_#{base_class_name}`.
|
|
59
64
|
def accept(visitor)
|
|
60
65
|
visitor.visit_#{subtype_name}_#{base_class_name}(self)
|
|
61
66
|
end
|
|
@@ -70,25 +75,7 @@ class Lox
|
|
|
70
75
|
end
|
|
71
76
|
end
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
binary: [%i[expr left], %i[token operator], %i[expr right]],
|
|
78
|
-
ternary: [%i[expr left], %i[token left_operator], %i[expr center], %i[token right_operator], %i[expr right]],
|
|
79
|
-
grouping: [%i[expr expression]],
|
|
80
|
-
literal: [%i[object value]],
|
|
81
|
-
unary: [%i[token operator], %i[expr right]],
|
|
82
|
-
variable: [%i[token name]]
|
|
83
|
-
}
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
Lox::AST.define_ast(
|
|
87
|
-
:statement,
|
|
88
|
-
{
|
|
89
|
-
block: [%i[stmt_list statements]],
|
|
90
|
-
expression: [%i[expr expression]],
|
|
91
|
-
print: [%i[expr expression]],
|
|
92
|
-
var: [%i[token name], %i[expr initializer]]
|
|
93
|
-
}
|
|
94
|
-
)
|
|
78
|
+
# Default AST specification for loxby.
|
|
79
|
+
Lox.config.ast.values.each do |name, definition|
|
|
80
|
+
Lox::AST.define_ast(name, definition)
|
|
81
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Lox
|
|
4
|
+
# The interface for callable objects
|
|
5
|
+
# in loxby. Currently just functions.
|
|
6
|
+
#
|
|
7
|
+
# To mark a class as callable, simply
|
|
8
|
+
# `include Lox::Callable`.
|
|
9
|
+
module Callable
|
|
10
|
+
def call(_interpreter, _arguments)
|
|
11
|
+
raise NotImplementedError, "#{self.class} has not implemented #call"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def arity
|
|
15
|
+
raise NotImplementedError, "#{self.class} has not implemented #arity"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
require_relative 'errors'
|
|
4
4
|
|
|
5
5
|
class Lox
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# nested (for scope).
|
|
6
|
+
# Stores namespaces for a Lox interpreter.
|
|
7
|
+
# Environments can be nested (for scope).
|
|
9
8
|
class Environment
|
|
10
9
|
def initialize(enclosing = nil)
|
|
11
10
|
@enclosing = enclosing
|
|
@@ -17,16 +16,26 @@ class Lox
|
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def []=(name, value)
|
|
20
|
-
|
|
19
|
+
set name.lexeme, value
|
|
21
20
|
end
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
# Used to set a static association. For example:
|
|
23
|
+
# env.set 'static_function_name', static_function
|
|
24
|
+
def set(name, value)
|
|
25
|
+
@values[name] = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def declared?(name)
|
|
29
|
+
# We can't check for a dummy value
|
|
30
|
+
# since loxby uses `nil` as well
|
|
24
31
|
@values.keys.member? name.lexeme
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
def [](name)
|
|
28
|
-
if
|
|
35
|
+
if @values[name.lexeme]
|
|
29
36
|
@values[name.lexeme]
|
|
37
|
+
elsif declared? name
|
|
38
|
+
raise Lox::RunError.new(name, "Declared variable not initialized: '#{name.lexeme}'")
|
|
30
39
|
elsif @enclosing
|
|
31
40
|
@enclosing[name]
|
|
32
41
|
else
|
|
@@ -35,7 +44,7 @@ class Lox
|
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
def assign(name, value)
|
|
38
|
-
if
|
|
47
|
+
if declared? name
|
|
39
48
|
self[name] = value
|
|
40
49
|
elsif @enclosing
|
|
41
50
|
@enclosing.assign(name, value)
|
|
@@ -43,5 +52,7 @@ class Lox
|
|
|
43
52
|
raise undefined_variable(name)
|
|
44
53
|
end
|
|
45
54
|
end
|
|
55
|
+
|
|
56
|
+
alias define []=
|
|
46
57
|
end
|
|
47
58
|
end
|
data/lib/loxby/helpers/errors.rb
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Lox
|
|
4
|
+
# A generic loxby error class raised
|
|
5
|
+
# when a syntax error is found.
|
|
4
6
|
class ParseError < RuntimeError; end
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
# A generic loxby error class raised
|
|
9
|
+
# when a runtime error is found.
|
|
10
|
+
class RunError < RuntimeError
|
|
7
11
|
attr_reader :token
|
|
8
12
|
|
|
9
13
|
def initialize(token, message)
|
|
@@ -12,5 +16,7 @@ class Lox
|
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
|
|
19
|
+
# A loxby error class raised when
|
|
20
|
+
# a number is divided by zero.
|
|
15
21
|
class DividedByZeroError < RunError; end
|
|
16
22
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'callable'
|
|
4
|
+
require_relative 'environment'
|
|
5
|
+
require_relative '../interpreter'
|
|
6
|
+
|
|
7
|
+
class Lox
|
|
8
|
+
# A 'Function' is a loxby function.
|
|
9
|
+
#
|
|
10
|
+
# You could manually instantiate one,
|
|
11
|
+
# but why would you?
|
|
12
|
+
class Function
|
|
13
|
+
include Callable
|
|
14
|
+
attr_reader :declaration, :enclosure
|
|
15
|
+
|
|
16
|
+
def initialize(declaration, closure)
|
|
17
|
+
@declaration = declaration
|
|
18
|
+
@closure = closure
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(interpreter, args)
|
|
22
|
+
env = Environment.new(@closure)
|
|
23
|
+
@declaration.params.zip(args).each do |param, arg|
|
|
24
|
+
env[param] = arg # Environment grabs the lexeme automatically
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Interpreter will `throw :return, return_value` to unwind
|
|
28
|
+
# callstack, jumping out of the `catch` block. `catch` then
|
|
29
|
+
# implicitly returns that value.
|
|
30
|
+
catch(:return) do
|
|
31
|
+
interpreter.execute_block @declaration.body, env
|
|
32
|
+
# If we get here, there was no return statement.
|
|
33
|
+
return nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def arity = @declaration.params.size
|
|
38
|
+
def to_s = "<fn #{@declaration.name ? @declaration.name.lexeme : '(anonymous)'}>"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interpreter'
|
|
4
|
+
require_relative 'callable'
|
|
5
|
+
require_relative '../visitors/base'
|
|
6
|
+
|
|
7
|
+
class Interpreter < Visitor # rubocop:disable Style/Documentation
|
|
8
|
+
# A `NativeFunction` is a loxby function
|
|
9
|
+
# which references a callable Ruby
|
|
10
|
+
# object (block, proc, method, etc.).
|
|
11
|
+
#
|
|
12
|
+
# For example:
|
|
13
|
+
# ```ruby
|
|
14
|
+
# @environment.set(
|
|
15
|
+
# 'clock',
|
|
16
|
+
# NativeFunction.new(0) do |_interpreter, _args|
|
|
17
|
+
# Time.now.to_i.to_f
|
|
18
|
+
# end
|
|
19
|
+
# )
|
|
20
|
+
# ```
|
|
21
|
+
class NativeFunction
|
|
22
|
+
include Lox::Callable
|
|
23
|
+
def initialize(given_arity = 0, &block)
|
|
24
|
+
@block = block
|
|
25
|
+
@arity = given_arity
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(interpreter, args) = @block.call(interpreter, args)
|
|
29
|
+
|
|
30
|
+
def arity
|
|
31
|
+
if @arity.respond_to? :call
|
|
32
|
+
@arity.call
|
|
33
|
+
else
|
|
34
|
+
@arity
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
'<native fn>'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def define_native_functions
|
|
44
|
+
Lox.config.native_functions.values.each do |name, func|
|
|
45
|
+
@globals.set name.to_s, NativeFunction.new(func.arity, &func.block)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../config'
|
|
4
|
+
|
|
3
5
|
class Lox
|
|
6
|
+
# A single token. Emitted by
|
|
7
|
+
# `Lox::Scanner` and consumed
|
|
8
|
+
# by `Lox::Parser`.
|
|
4
9
|
class Token
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:left_paren, :right_paren, :left_brace, :right_brace,
|
|
8
|
-
:comma, :dot, :minus, :plus, :semicolon, :slash, :star,
|
|
9
|
-
:question, :colon,
|
|
10
|
-
|
|
11
|
-
# 1-2 character tokens.
|
|
12
|
-
:bang, :bang_equal,
|
|
13
|
-
:equal, :equal_equal,
|
|
14
|
-
:greater, :greater_equal,
|
|
15
|
-
:less, :less_equal,
|
|
16
|
-
|
|
17
|
-
# Literals.
|
|
18
|
-
:identifier, :string, :number,
|
|
19
|
-
|
|
20
|
-
# Keywords.
|
|
21
|
-
:and, :class, :else, :false, :fun, :for, :if, :nil, :or,
|
|
22
|
-
:print, :return, :super, :this, :true, :var, :while,
|
|
10
|
+
# List of all token types.
|
|
11
|
+
TOKENS = Lox.config.token_types.tokens
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
SINGLE_TOKENS = TOKENS.zip('(){},.-+;/*'.split('')).to_h
|
|
13
|
+
# Map of single-character token types.
|
|
14
|
+
SINGLE_TOKENS = Lox.config.token_types.single_tokens
|
|
27
15
|
|
|
28
16
|
attr_reader :type, :lexeme, :literal, :line
|
|
29
17
|
|