loxxy 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.
@@ -0,0 +1,60 @@
1
+ # Loxxy
2
+
3
+ A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html),
4
+ a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/).
5
+
6
+ ## Purpose of this project:
7
+ - To deliver an open source example of a programming language fully implemented in Ruby
8
+ (from the scanner, parser, code generation).
9
+ - The implementation should be mature enough to run (LoxLox)[https://github.com/benhoyt/loxlox],
10
+ a Lox interpreter written in Lox.
11
+
12
+ ## Roadmap
13
+ - [DONE] Scanner (tokenizer)
14
+ - [STARTED] Recognizer
15
+ - [TODO] Parser
16
+ - [TODO] Interpreter or transpiler
17
+
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'loxxy'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle install
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install loxxy
34
+
35
+ ## Usage
36
+
37
+ TODO: Write usage instructions here
38
+
39
+ ## Other Lox implementations in Ruby
40
+ An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations
41
+
42
+ For Ruby, ther is the [lox](https://github.com/rdodson41/ruby-lox) gem.
43
+ There are other Ruby-based projects as well:
44
+ - [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
45
+ - [rulox](https://github.com/LevitatingBusinessMan/rulox)
46
+
47
+ ## Development
48
+
49
+ 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.
50
+
51
+ 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).
52
+
53
+ ## Contributing
54
+
55
+ Bug reports and pull requests are welcome on GitHub at https://github.com/famished_tiger/loxxy.
56
+
57
+
58
+ ## License
59
+
60
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './loxxy/version'
4
+
5
+ module Loxxy
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'false'
4
+ require_relative 'lx_string'
5
+ require_relative 'nil'
6
+ require_relative 'number'
7
+ require_relative 'true'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'builtin_datatype'
4
+
5
+ module Loxxy
6
+ module Datatype
7
+ # Abstract class that generalizes a Lox boolean value.
8
+ # An instance acts merely as a wrapper around a Ruby representation
9
+ # of the value.
10
+ class Boolean < BuiltinDatatype
11
+ end # class
12
+ end # module
13
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module Datatype
5
+ # Abstract class that generalizes a value from a Lox built-in data types.
6
+ # An instance acts merely as a wrapper around a Ruby representation
7
+ # of the value.
8
+ class BuiltinDatatype
9
+ # @return [Object] The Ruby representation
10
+ attr_reader :value
11
+
12
+ # Constructor. Initialize a Lox value from one of its built-in data type.
13
+ def initialize(aValue)
14
+ @value = validated_value(aValue)
15
+ end
16
+
17
+
18
+ protected
19
+
20
+ def validated_value(aValue)
21
+ aValue
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton' # Use the Singleton design pattern
4
+ require_relative 'boolean'
5
+
6
+ module Loxxy
7
+ module Datatype
8
+ # Class for representing a Lox false value.
9
+ class False < Boolean
10
+ include Singleton # Make a singleton class
11
+
12
+ # Build the sole instance
13
+ def initialize
14
+ super(false)
15
+ end
16
+ end # class
17
+
18
+ False.instance.freeze # Make the sole instance immutable
19
+ end # module
20
+ end # module
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'builtin_datatype'
4
+
5
+ module Loxxy
6
+ module Datatype
7
+ # Class for representing a Lox string of characters value.
8
+ class LXString < BuiltinDatatype
9
+
10
+ # Build the sole instance
11
+ def initialize(aValue)
12
+ super(aValue)
13
+ end
14
+
15
+ protected
16
+
17
+ def validated_value(aValue)
18
+ unless aValue.is_a?(String)
19
+ raise StandardError, "Invalid number value #{aValue}"
20
+ end
21
+
22
+ # Remove double quotes delimiter
23
+ aValue.gsub(/(^")|("$)/, '')
24
+ end
25
+ end # class
26
+ end # module
27
+ end # module
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton' # Use the Singleton design pattern
4
+ require_relative 'builtin_datatype'
5
+
6
+ module Loxxy
7
+ module Datatype
8
+ # Class for representing a Lox nil "value".
9
+ class Nil < BuiltinDatatype
10
+ include Singleton # Make a singleton class
11
+
12
+ # Build the sole instance
13
+ def initialize
14
+ super(nil)
15
+ end
16
+ end # class
17
+
18
+ Nil.instance.freeze # Make the sole instance immutable
19
+ end # module
20
+ end # module
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'builtin_datatype'
4
+
5
+ module Loxxy
6
+ module Datatype
7
+ # Class for representing a Lox numeric value.
8
+ class Number < BuiltinDatatype
9
+
10
+ # Build the sole instance
11
+ def initialize(aValue)
12
+ super(aValue)
13
+ end
14
+
15
+ protected
16
+
17
+ def validated_value(aValue)
18
+ case aValue
19
+ when Integer
20
+ result = aValue
21
+ when Numeric
22
+ result = aValue
23
+ when /^-?\d+$/
24
+ result = aValue.to_i
25
+ when /^-?\d+\.\d+$/
26
+ result = aValue.to_f
27
+ else
28
+ raise StandardError, "Invalid number value #{aValue}"
29
+ end
30
+
31
+ result
32
+ end
33
+ end # class
34
+ end # module
35
+ end # module
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton' # Use the Singleton design pattern
4
+ require_relative 'boolean'
5
+
6
+ module Loxxy
7
+ module Datatype
8
+ # Class for representing a Lox true value.
9
+ class True < Boolean
10
+ include Singleton # Make a singleton class
11
+
12
+ # Build the sole instance
13
+ def initialize
14
+ super(true)
15
+ end
16
+ end # class
17
+
18
+ True.instance.freeze # Make the sole instance immutable
19
+ end # module
20
+ end # module
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ require 'rley' # Load the gem
5
+
6
+ module Loxxy
7
+ module FrontEnd
8
+ ########################################
9
+ # Grammar for Lox language
10
+ # Authoritave grammar at:
11
+ # https://craftinginterpreters.com/appendix-i.html
12
+ builder = Rley::Syntax::GrammarBuilder.new do
13
+ # Punctuators, separators...
14
+ add_terminals('LEFT_PAREN', 'RIGHT_PAREN', 'LEFT_BRACE', 'RIGHT_BRACE')
15
+ add_terminals('COMMA', 'DOT', 'MINUS', 'PLUS')
16
+ add_terminals('SEMICOLON', 'SLASH', 'STAR')
17
+ add_terminals('BANG', 'BANG_EQUAL', 'EQUAL', 'EQUAL_EQUAL')
18
+ add_terminals('GREATER', 'GREATER_EQUAL', 'LESS', 'LESS_EQUAL')
19
+
20
+ # Keywords and literals
21
+ add_terminals('AND', 'CLASS', 'ELSE', 'FALSE')
22
+ add_terminals('FUN', 'FOR', 'IDENTIFIER', 'IF')
23
+ add_terminals('NIL', 'NUMBER', 'OR', 'PRINT')
24
+ add_terminals('RETURN', 'STRING', 'SUPER', 'THIS')
25
+ add_terminals('TRUE', 'VAR', 'WHILE')
26
+ add_terminals('EOF')
27
+
28
+ # Top-level rule that matches an entire Lox program
29
+ rule('program' => 'declaration_star EOF')
30
+
31
+ # Declarations: bind an identifier to something
32
+ rule('declaration_star' => 'declaration_star declaration')
33
+ rule('declaration_star' => [])
34
+ rule('declaration' => 'classDecl')
35
+ rule('declaration' => 'funDecl')
36
+ rule('declaration' => 'varDecl')
37
+ rule('declaration' => 'statement')
38
+
39
+ rule('classDecl' => 'CLASS classNaming class_body')
40
+ rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
41
+ rule('classNaming' => 'IDENTIFIER')
42
+ rule('class_body' => 'LEFT_BRACE function_star RIGHT_BRACE')
43
+ rule('function_star' => 'function_star function')
44
+ rule('function_star' => [])
45
+
46
+ rule('funDecl' => 'FUN function')
47
+
48
+ rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
49
+ rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
50
+
51
+ # Statements: produce side effects, but don't introduce bindings
52
+ rule('statement' => 'exprStmt')
53
+ rule('statement' => 'forStmt')
54
+ rule('statement' => 'ifStmt')
55
+ rule('statement' => 'printStmt')
56
+ rule('statement' => 'returnStmt')
57
+ rule('statement' => 'whileStmt')
58
+ rule('statement' => 'block')
59
+
60
+ rule('exprStmt' => 'expression SEMICOLON')
61
+
62
+ rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
63
+ rule('forControl' => 'forInitialization forTest forUpdate')
64
+ rule('forInitialization' => 'varDecl')
65
+ rule('forInitialization' => 'exprStmt')
66
+ rule('forInitialization' => 'SEMICOLON')
67
+ rule('forTest' => 'expression_opt SEMICOLON')
68
+ rule('forUpdate' => 'expression_opt')
69
+
70
+ rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
71
+ rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
72
+ rule('elsePart_opt' => 'ELSE statement')
73
+ rule('elsePart_opt' => [])
74
+
75
+ rule('printStmt' => 'PRINT expression SEMICOLON')
76
+ rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
77
+ rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
78
+ rule('block' => 'LEFT_BRACE declaration_star RIGHT_BRACE')
79
+
80
+ # Expressions: produce values
81
+ rule('expression_opt' => 'expression')
82
+ rule('expression_opt' => [])
83
+ rule('expression' => 'assignment')
84
+ rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
85
+ rule('assignment' => 'logic_or')
86
+ rule('owner_opt' => 'call DOT')
87
+ rule('owner_opt' => [])
88
+ rule('logic_or' => 'logic_and disjunct_star')
89
+ rule('disjunct_star' => 'disjunct_star OR logic_and')
90
+ rule('disjunct_star' => [])
91
+ rule('logic_and' => 'equality conjunct_star')
92
+ rule('conjunct_star' => 'conjunct_star AND equality')
93
+ rule('conjunct_star' => [])
94
+ rule('equality' => 'comparison equalityTest_star')
95
+ rule('equalityTest_star' => 'equalityTest_star equalityTest comparison')
96
+ rule('equalityTest_star' => [])
97
+ rule('equalityTest' => 'BANG_EQUAL')
98
+ rule('equalityTest' => 'EQUAL_EQUAL')
99
+ rule('comparison' => 'term comparisonTest_star')
100
+ rule('comparisonTest_star' => 'comparisonTest_star comparisonTest term')
101
+ rule('comparisonTest_star' => [])
102
+ rule('comparisonTest' => 'GREATER')
103
+ rule('comparisonTest' => 'GREATER_EQUAL')
104
+ rule('comparisonTest' => 'LESS')
105
+ rule('comparisonTest' => 'LESS_EQUAL')
106
+ rule('term' => 'factor additive_star')
107
+ rule('additive_star' => 'additive_star additionOp factor')
108
+ rule('additive_star' => [])
109
+ rule('additionOp' => 'MINUS')
110
+ rule('additionOp' => 'PLUS')
111
+ rule('factor' => 'unary multiplicative_star')
112
+ rule('multiplicative_star' => 'multiplicative_star multOp unary')
113
+ rule('multiplicative_star' => [])
114
+ rule('multOp' => 'SLASH')
115
+ rule('multOp' => 'STAR')
116
+ rule('unary' => 'unaryOp unary')
117
+ rule('unary' => 'call')
118
+ rule('unaryOp' => 'BANG')
119
+ rule('unaryOp' => 'MINUS')
120
+ rule('call' => 'primary refinement_star')
121
+ rule('refinement_star' => 'refinement_star refinement')
122
+ rule('refinement_star' => [])
123
+ rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
124
+ rule('refinement' => 'DOT IDENTIFIER')
125
+ rule('primary' => 'TRUE')
126
+ rule('primary' => 'FALSE')
127
+ rule('primary' => 'NIL')
128
+ rule('primary' => 'THIS')
129
+ rule('primary' => 'NUMBER')
130
+ rule('primary' => 'STRING')
131
+ rule('primary' => 'IDENTIFIER')
132
+ rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
133
+ rule('primary' => 'SUPER DOT IDENTIFIER')
134
+
135
+ # Utility rules
136
+ rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block')
137
+ rule('params_opt' => 'parameters')
138
+ rule('params_opt' => [])
139
+ rule('parameters' => 'parameters COMMA IDENTIFIER')
140
+ rule('parameters' => 'IDENTIFIER')
141
+ rule('arguments_opt' => 'arguments')
142
+ rule('arguments_opt' => [])
143
+ rule('arguments' => 'arguments COMMA expression')
144
+ rule('arguments' => 'expression')
145
+
146
+ end
147
+
148
+ # And now build the grammar and make it accessible via a constant
149
+ # @return [Rley::Syntax::Grammar]
150
+ Grammar = builder.grammar
151
+ end # module
152
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rley'
4
+
5
+ module Loxxy
6
+ module FrontEnd
7
+ # The superclass for all tokens that have a data value.
8
+ class Literal < Rley::Lexical::Token
9
+ # @return [Datatype] The value expressed in one of the Lox datatype.
10
+ attr_reader :value
11
+
12
+ # Constructor.
13
+ # @param aValue [Datatype::BuiltinDatatype] the Lox data value
14
+ # @param theLexeme [String] the lexeme (= piece of text from input)
15
+ # @param aTerminal [Rley::Syntax::Terminal, String]
16
+ # The terminal symbol corresponding to the lexeme.
17
+ # @param aPosition [Rley::Lexical::Position] The position of lexeme
18
+ # in input text.
19
+ def initialize(aValue, aLexeme, aTerminal, aPosition)
20
+ super(aLexeme, aTerminal, aPosition)
21
+ @value = aValue
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'scanner'
4
+ require_relative 'grammar'
5
+
6
+ module Loxxy
7
+ module FrontEnd
8
+ class Parser
9
+ attr_reader(:engine)
10
+
11
+ def initialize
12
+ # Create a Rley facade object
13
+ @engine = Rley::Engine.new do |cfg|
14
+ cfg.diagnose = true
15
+ # cfg.repr_builder = SkmBuilder
16
+ end
17
+
18
+ # Step 1. Load Lox grammar
19
+ @engine.use_grammar(Loxxy::FrontEnd::Grammar)
20
+ end
21
+
22
+ # Parse the given Lox program into a parse tree.
23
+ # @param source [String] Lox program to parse
24
+ # @return [Rley::ParseTree] A parse tree equivalent to the Lox input.
25
+ def parse(source)
26
+ lexer = Scanner.new(source)
27
+ result = engine.parse(lexer.tokens)
28
+
29
+ unless result.success?
30
+ # Stop if the parse failed...
31
+ line1 = "Parsing failed\n"
32
+ line2 = "Reason: #{result.failure_reason.message}"
33
+ raise StandardError, line1 + line2
34
+ end
35
+
36
+ return engine.convert(result) # engine.to_ptree(result)
37
+ end
38
+ end # class
39
+ end # module
40
+ end # module