rbscmlex 0.1.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 +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +57 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +36 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/examples/simaple_parser.rb +41 -0
- data/exe/rbscmlex +85 -0
- data/lib/rbscmlex.rb +8 -0
- data/lib/rbscmlex/error.rb +18 -0
- data/lib/rbscmlex/lexer.rb +155 -0
- data/lib/rbscmlex/token.rb +96 -0
- data/lib/rbscmlex/version.rb +6 -0
- data/rbscmlex.gemspec +35 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2ac1672aabaa3f5da45e737b0a662488a9a3fdddfc543b2c5b5835576bbbcb9d
|
4
|
+
data.tar.gz: aadf9ea25326c7b0b45335b99f6d31d7a7540f0b812d8b39434c40977a48f053
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2dd3a53f7bc19f8b56aed51383cdeff7dff1e351822a3971558933690918d66147a599bba5f28c68782a6511bda7d811780fdaef11fe0226716b16204ca3edec
|
7
|
+
data.tar.gz: 27a1df5414a12c1db2d074e39ab81a11c0c89aeee913b9644b611ac45d099164a67b50113b713e9a63f88eb0617633f0074a29a08b64c39b5cedabf77c00bfb1
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Build
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.1
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.15
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
Gemfile.lock
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
/.config
|
5
|
+
/coverage/
|
6
|
+
/InstalledFiles
|
7
|
+
/pkg/
|
8
|
+
/spec/reports/
|
9
|
+
/spec/examples.txt
|
10
|
+
/test/tmp/
|
11
|
+
/test/version_tmp/
|
12
|
+
/tmp/
|
13
|
+
|
14
|
+
# Used by dotenv library to load environment variables.
|
15
|
+
# .env
|
16
|
+
|
17
|
+
# Ignore Byebug command history file.
|
18
|
+
.byebug_history
|
19
|
+
|
20
|
+
## Specific to RubyMotion:
|
21
|
+
.dat*
|
22
|
+
.repl_history
|
23
|
+
build/
|
24
|
+
*.bridgesupport
|
25
|
+
build-iPhoneOS/
|
26
|
+
build-iPhoneSimulator/
|
27
|
+
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
29
|
+
#
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
33
|
+
#
|
34
|
+
# vendor/Pods/
|
35
|
+
|
36
|
+
## Documentation cache and generated files:
|
37
|
+
/.yardoc/
|
38
|
+
/_yardoc/
|
39
|
+
/doc/
|
40
|
+
/rdoc/
|
41
|
+
|
42
|
+
## Environment normalization:
|
43
|
+
/.bundle/
|
44
|
+
/vendor/bundle
|
45
|
+
/lib/bundler/man/
|
46
|
+
|
47
|
+
# for a library or gem, you might want to ignore these files since the code is
|
48
|
+
# intended to run in multiple environments; otherwise, check them in:
|
49
|
+
# Gemfile.lock
|
50
|
+
# .ruby-version
|
51
|
+
# .ruby-gemset
|
52
|
+
|
53
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
54
|
+
.rvmrc
|
55
|
+
|
56
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
57
|
+
# .rubocop-https?--*
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/)
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
6
|
+
|
7
|
+
## [Unreleased]
|
8
|
+
- (nothing to record here)
|
9
|
+
|
10
|
+
## [0.1.0] - 2021-05-06
|
11
|
+
- Initial release
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 mnbi
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# rbscmlex
|
2
|
+
|
3
|
+
[](https://github.com/mnbi/rbscmlex/actions?query=workflow%3A"Build")
|
4
|
+
|
5
|
+
A simple lexical analyzer for Scheme.
|
6
|
+
|
7
|
+
It is intended to be used as a part of a Scheme implementation written
|
8
|
+
in Ruby.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Execute as:
|
13
|
+
|
14
|
+
> gem install rbscmlex
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
See `examples/sample_parser.rb` to know about how to use as a lexical
|
19
|
+
analyzer for Scheme.
|
20
|
+
|
21
|
+
Or, run `rbscmlex` to see the output of the lexer.
|
22
|
+
|
23
|
+
## Development
|
24
|
+
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
26
|
+
|
27
|
+
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).
|
28
|
+
|
29
|
+
## Contributing
|
30
|
+
|
31
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/mnbi/rus3](https://github.com/mnbi/rus3).
|
32
|
+
|
33
|
+
|
34
|
+
## License
|
35
|
+
|
36
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "rbscmlex"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rbscmlex"
|
4
|
+
|
5
|
+
def parse_program(lex)
|
6
|
+
program = []
|
7
|
+
loop {
|
8
|
+
program << parse_expression(lex)
|
9
|
+
}
|
10
|
+
program
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_expression(lex)
|
14
|
+
if lex.peek_token.type == :lparen
|
15
|
+
parse_list(lex)
|
16
|
+
else
|
17
|
+
parse_simple(lex)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_list(lex)
|
22
|
+
lex.next_token # skip :lparen
|
23
|
+
list = []
|
24
|
+
loop {
|
25
|
+
break if lex.peek_token.type == :rparen
|
26
|
+
list << parse_expression(lex)
|
27
|
+
}
|
28
|
+
lex.next_token # skip :rparen
|
29
|
+
list
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_simple(lex)
|
33
|
+
lex.next_token.literal
|
34
|
+
end
|
35
|
+
|
36
|
+
source = ARGF.entries.join(" ")
|
37
|
+
lex = Rbscmlex::Lexer.new(source)
|
38
|
+
|
39
|
+
result = parse_program(lex)
|
40
|
+
|
41
|
+
pp result
|
data/exe/rbscmlex
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "readline"
|
4
|
+
require "rbscmlex"
|
5
|
+
|
6
|
+
def version
|
7
|
+
"rbscmlex version #{Rbscmlex::VERSION} #{Rbscmlex::RELEASE}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def usage
|
11
|
+
<<HELP
|
12
|
+
usage:
|
13
|
+
rbscmlex [option] [FILE ...]
|
14
|
+
option:
|
15
|
+
-o, --output-file OUTPUT_FILE : specify the output file
|
16
|
+
-t, --format-type TYPE : specify the output format
|
17
|
+
-d, --debug : specify to run verbose mode
|
18
|
+
-v, --version : print version
|
19
|
+
-h, --help : show this message
|
20
|
+
|
21
|
+
OUTPUT_FORMAT:
|
22
|
+
Use one of the following name to specify the output format.
|
23
|
+
token, hash, json
|
24
|
+
HELP
|
25
|
+
end
|
26
|
+
|
27
|
+
def opt_parse(args, opts = {})
|
28
|
+
files = []
|
29
|
+
while args.size > 0
|
30
|
+
arg = args.shift
|
31
|
+
case arg
|
32
|
+
when "-o", "--output-file"
|
33
|
+
opts[:output_file] = args.shift
|
34
|
+
when "-t", "--format-type"
|
35
|
+
format_type = args.shift
|
36
|
+
raise ArgumentError, "not specified as format type" if format_type.nil?
|
37
|
+
opts[:format_type] = format_type.intern
|
38
|
+
when "-d", "--debug"
|
39
|
+
opts[:verbose] = true
|
40
|
+
when "-v", "--version"
|
41
|
+
puts version
|
42
|
+
exit 0
|
43
|
+
when "-h", "--help"
|
44
|
+
puts usage
|
45
|
+
exit 0
|
46
|
+
else # must be a filename
|
47
|
+
files << arg if arg
|
48
|
+
end
|
49
|
+
end
|
50
|
+
args.concat(files)
|
51
|
+
opts
|
52
|
+
end
|
53
|
+
|
54
|
+
opts = opt_parse(ARGV)
|
55
|
+
if opts[:verbose]
|
56
|
+
puts version
|
57
|
+
format = opts[:format_type] || "default"
|
58
|
+
puts "Output format: #{format}"
|
59
|
+
ofname = opts[:output_file] || "STDOUT"
|
60
|
+
puts "Output file: #{ofname}"
|
61
|
+
end
|
62
|
+
|
63
|
+
of = STDOUT
|
64
|
+
of = File.open(opts[:output_file], "w") if opts[:output_file]
|
65
|
+
form = opts[:format_type] || :token
|
66
|
+
|
67
|
+
if ARGV.size > 0
|
68
|
+
puts "reading... #{ARGV.join(", ")}" if opts[:verbose]
|
69
|
+
|
70
|
+
source = ARGF.entries.join(" ")
|
71
|
+
lex = Rbscmlex::Lexer.new(source, form: form)
|
72
|
+
|
73
|
+
Kernel.loop {
|
74
|
+
of.puts lex.next_token
|
75
|
+
}
|
76
|
+
else
|
77
|
+
puts "reading...STDIN" if opts[:verbose]
|
78
|
+
|
79
|
+
Kernel.loop {
|
80
|
+
src_line = Readline::readline("Rbscmlex> ")
|
81
|
+
break if src_line.nil?
|
82
|
+
lex = Rbscmlex::Lexer.new(src_line, type: type)
|
83
|
+
Kernel.loop { of.puts lex.next_token }
|
84
|
+
}
|
85
|
+
end
|
data/lib/rbscmlex.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rbscmlex
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class UnknownTokenTypeError < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidConversionTypeError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidHashError < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidJsonError < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Rbscmlex
|
6
|
+
|
7
|
+
# A lexical analyzer for Scheme program. It returns an Array
|
8
|
+
# instance of tokens. The representation of each token is depends
|
9
|
+
# on the specification of type, which is specified to the `new`
|
10
|
+
# method.
|
11
|
+
#
|
12
|
+
# Currently, 3 types of the representation are available.
|
13
|
+
#
|
14
|
+
# 1. a struct Token,
|
15
|
+
# 2. a Hash object,
|
16
|
+
# 3. a JSON string.
|
17
|
+
|
18
|
+
class Lexer
|
19
|
+
|
20
|
+
class << self
|
21
|
+
|
22
|
+
def version # :nodoc:
|
23
|
+
"(scheme-lexer :version #{VERSION})"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# :stopdoc:
|
29
|
+
|
30
|
+
BOOLEAN = /\A#(f(alse)?|t(rue)?)\Z/
|
31
|
+
STRING = /\A\"[^\"]*\"\Z/
|
32
|
+
|
33
|
+
# idents
|
34
|
+
EXTENDED_CHARS = "!\\$%&\\*\\+\\-\\./:<=>\\?@\\^_~"
|
35
|
+
IDENT_PAT = "[a-zA-Z_][a-zA-Z0-9#{EXTENDED_CHARS}]*"
|
36
|
+
IDENTIFIER = Regexp.new("\\A#{IDENT_PAT}\\Z")
|
37
|
+
|
38
|
+
# operators
|
39
|
+
ARITHMETIC_OPS = /\A[+\-*\/%]\Z/
|
40
|
+
COMPARISON_OPS = /\A([<>]=?|=)\Z/
|
41
|
+
|
42
|
+
# numbers
|
43
|
+
REAL_PAT = "(([1-9][0-9]*)|0)(\.[0-9]+)?"
|
44
|
+
RAT_PAT = "#{REAL_PAT}\\/#{REAL_PAT}"
|
45
|
+
C_REAL_PAT = "(#{REAL_PAT}|#{RAT_PAT})"
|
46
|
+
C_IMAG_PAT = "#{C_REAL_PAT}"
|
47
|
+
COMP_PAT = "#{C_REAL_PAT}(\\+|\\-)#{C_IMAG_PAT}i"
|
48
|
+
|
49
|
+
REAL_NUM = Regexp.new("\\A[+-]?#{REAL_PAT}\\Z")
|
50
|
+
RATIONAL = Regexp.new("\\A[+-]?#{RAT_PAT}\\Z")
|
51
|
+
COMPLEX = Regexp.new("\\A[+-]?#{COMP_PAT}\\Z")
|
52
|
+
PURE_IMAG = Regexp.new("\\A[+-](#{C_IMAG_PAT})?i\\Z")
|
53
|
+
|
54
|
+
# char
|
55
|
+
SINGLE_CHAR_PAT = "."
|
56
|
+
SPACE_PAT = "space"
|
57
|
+
NEWLINE_PAT = "newline"
|
58
|
+
|
59
|
+
CHAR_PREFIX = "\#\\\\"
|
60
|
+
CHAR_PAT = "(#{SINGLE_CHAR_PAT}|#{SPACE_PAT}|#{NEWLINE_PAT})"
|
61
|
+
CHAR = Regexp.new("\\A#{CHAR_PREFIX}#{CHAR_PAT}\\Z")
|
62
|
+
|
63
|
+
# :startdoc:
|
64
|
+
|
65
|
+
include Enumerable
|
66
|
+
|
67
|
+
def initialize(src, form: TOKEN_DEFAULT_FORM)
|
68
|
+
@form = form
|
69
|
+
@tokens = tokenize(src)
|
70
|
+
@size = @tokens.size
|
71
|
+
|
72
|
+
@current_pos = @next_pos = 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def each(&blk)
|
76
|
+
if block_given?
|
77
|
+
@tokens.each(&blk)
|
78
|
+
self
|
79
|
+
else
|
80
|
+
@tokens.each
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_a
|
85
|
+
@tokens
|
86
|
+
end
|
87
|
+
|
88
|
+
def size
|
89
|
+
@size
|
90
|
+
end
|
91
|
+
|
92
|
+
def current_token
|
93
|
+
@tokens[@current_pos]
|
94
|
+
end
|
95
|
+
|
96
|
+
def next_token
|
97
|
+
check_pos
|
98
|
+
@current_pos = @next_pos
|
99
|
+
@next_pos += 1
|
100
|
+
@tokens[@current_pos]
|
101
|
+
end
|
102
|
+
|
103
|
+
def peek_token(num = 0)
|
104
|
+
check_pos
|
105
|
+
@tokens[@next_pos + num]
|
106
|
+
end
|
107
|
+
|
108
|
+
def rewind
|
109
|
+
@current_pos = @next_pos = 0
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def check_pos
|
116
|
+
raise StopIteration if @next_pos >= @size
|
117
|
+
end
|
118
|
+
|
119
|
+
S2R_MAP = { "(" => "( ", ")" => " ) ", "'" => " ' " } # :nodoc:
|
120
|
+
|
121
|
+
def tokenize(src)
|
122
|
+
cooked = src.gsub(/[()']/, S2R_MAP)
|
123
|
+
|
124
|
+
cooked.split(" ").map { |literal|
|
125
|
+
case literal
|
126
|
+
when "("
|
127
|
+
Rbscmlex.new_token(:lparen, literal, @form)
|
128
|
+
when ")"
|
129
|
+
Rbscmlex.new_token(:rparen, literal, @form)
|
130
|
+
when "."
|
131
|
+
Rbscmlex.new_token(:dot, literal, @form)
|
132
|
+
when "'"
|
133
|
+
Rbscmlex.new_token(:quotation, literal, @form)
|
134
|
+
when "#("
|
135
|
+
Rbscmlex.new_token(:vec_lparen, literal, @form)
|
136
|
+
when BOOLEAN
|
137
|
+
Rbscmlex.new_token(:boolean, literal, @form)
|
138
|
+
when IDENTIFIER
|
139
|
+
Rbscmlex.new_token(:identifier, literal, @form)
|
140
|
+
when CHAR
|
141
|
+
Rbscmlex.new_token(:character, literal, @form)
|
142
|
+
when STRING
|
143
|
+
Rbscmlex.new_token(:string, literal, @form)
|
144
|
+
when ARITHMETIC_OPS, COMPARISON_OPS
|
145
|
+
Rbscmlex.new_token(:op_proc, literal, @form)
|
146
|
+
when REAL_NUM, RATIONAL, COMPLEX, PURE_IMAG
|
147
|
+
Rbscmlex.new_token(:number, literal, @form)
|
148
|
+
else
|
149
|
+
Rbscmlex.new_token(:illegal, literal, @form)
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Rbscmlex
|
6
|
+
|
7
|
+
TOKEN_FORMS = [ :token, :hash, :json, ] # :nodoc:
|
8
|
+
TOKEN_DEFAULT_FORM = :token # :nodoc:
|
9
|
+
|
10
|
+
TOKEN_TYPES = [ # :nodoc:
|
11
|
+
# delimiters
|
12
|
+
:lparen, # `(`
|
13
|
+
:rparen, # `)`
|
14
|
+
:vec_lparen, # `#(`
|
15
|
+
:bytevec_lparen, # `#u8(`
|
16
|
+
:quotation, # `'`
|
17
|
+
:backquote, # "`" (aka quasiquote)
|
18
|
+
:comma, # `,`
|
19
|
+
:comma_at, # `,@`
|
20
|
+
:dot, # `.`
|
21
|
+
:semicolon, # `;`
|
22
|
+
:comment_lparen, # `#|`
|
23
|
+
:comment_rparen, # `|#`
|
24
|
+
# value types
|
25
|
+
:identifier, # `foo`
|
26
|
+
:boolean, # `#f` or `#t` (`#false` or `#true`)
|
27
|
+
:number, # `123`, `456.789`, `1/2`, `3+4i`
|
28
|
+
:character, # `#\a`
|
29
|
+
:string, # `"hoge"`
|
30
|
+
# operators
|
31
|
+
:op_proc, # `+`, `-`, ...
|
32
|
+
# control
|
33
|
+
:illegal,
|
34
|
+
]
|
35
|
+
|
36
|
+
# a structure to store properties of a token of Scheme program.
|
37
|
+
|
38
|
+
Token = Struct.new(:type, :literal) {
|
39
|
+
alias :to_s :literal
|
40
|
+
}
|
41
|
+
|
42
|
+
class << self
|
43
|
+
|
44
|
+
# Instantiates a new token object form type and literal. The 3rd
|
45
|
+
# argument specifies the form of the object. It must be one of
|
46
|
+
# :token, :hash, and :json.
|
47
|
+
|
48
|
+
def new_token(type, literal = nil, form = :token)
|
49
|
+
case form
|
50
|
+
when :token
|
51
|
+
Token.new(type, literal)
|
52
|
+
when :hash
|
53
|
+
{type: type, literal: literal}
|
54
|
+
when :json
|
55
|
+
JSON.generate(to_hash)
|
56
|
+
else
|
57
|
+
raise InvalidConversionTypeError, "cannot generate #{type} as token"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns true when the argument is valid token type.
|
62
|
+
|
63
|
+
def token_type?(type)
|
64
|
+
TOKEN_TYPES.include?(type)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Converts a Hash object, which has type and literal as its key,
|
68
|
+
# to a new token object. The value associated to type of the Hash
|
69
|
+
# must be valid token type. Otherwise, raises UnknownTokenTypeError.
|
70
|
+
|
71
|
+
def hash2token(hash)
|
72
|
+
if h.key?("type") and h.key?("literal")
|
73
|
+
type = h["type"].intern
|
74
|
+
raise UnknownTokenTypeError, ("got=%s" % type) unless token_type?(type)
|
75
|
+
literal = h["literal"]
|
76
|
+
Token.new(type.intern, literal)
|
77
|
+
else
|
78
|
+
raise InvalidHashError, ("got=%s" % hash)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Converts a JSON notation, which hash type and literal, to a new
|
83
|
+
# token object. The value associated to type of the Hash must be
|
84
|
+
# valid token type. Otherwise, raises UnknownTokenTypeError.
|
85
|
+
|
86
|
+
def json2token(json)
|
87
|
+
h = JSON.parse(json)
|
88
|
+
begin
|
89
|
+
hash2token(h)
|
90
|
+
rescue InvalidHashError => _
|
91
|
+
raise InvalidJsonError, ("got=%s" % json)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/rbscmlex.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rbscmlex/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rbscmlex"
|
7
|
+
spec.version = Rbscmlex::VERSION
|
8
|
+
spec.authors = ["mnbi"]
|
9
|
+
spec.email = ["mnbi@users.noreply.github.com"]
|
10
|
+
|
11
|
+
spec.summary = "A simple lexical analyzer for Scheme"
|
12
|
+
spec.description = "A simple lexical analyzer for Scheme"
|
13
|
+
spec.homepage = "https://github.com/mnbi/rbscmlex"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/mnbi/rbscmlex"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/mnbi/rbscmlex/blob/main/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
# Uncomment to register a new dependency of your gem
|
31
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
32
|
+
|
33
|
+
# For more information and examples about making a new gem, checkout our
|
34
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rbscmlex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mnbi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-06 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A simple lexical analyzer for Scheme
|
14
|
+
email:
|
15
|
+
- mnbi@users.noreply.github.com
|
16
|
+
executables:
|
17
|
+
- rbscmlex
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".github/workflows/main.yml"
|
22
|
+
- ".gitignore"
|
23
|
+
- CHANGELOG.md
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- bin/console
|
29
|
+
- bin/setup
|
30
|
+
- examples/simaple_parser.rb
|
31
|
+
- exe/rbscmlex
|
32
|
+
- lib/rbscmlex.rb
|
33
|
+
- lib/rbscmlex/error.rb
|
34
|
+
- lib/rbscmlex/lexer.rb
|
35
|
+
- lib/rbscmlex/token.rb
|
36
|
+
- lib/rbscmlex/version.rb
|
37
|
+
- rbscmlex.gemspec
|
38
|
+
homepage: https://github.com/mnbi/rbscmlex
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata:
|
42
|
+
homepage_uri: https://github.com/mnbi/rbscmlex
|
43
|
+
source_code_uri: https://github.com/mnbi/rbscmlex
|
44
|
+
changelog_uri: https://github.com/mnbi/rbscmlex/blob/main/CHANGELOG.md
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.7.0
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.2.15
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: A simple lexical analyzer for Scheme
|
64
|
+
test_files: []
|