grub-lexer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b9f00137dd827d330f00866fcc9c0d61fc4c0655ad5d5acf5e7c01680cfd87ab
4
+ data.tar.gz: 9aaae3ca78d8e73ae801737835668cc8712f3a65c040fc92586fc8d22f21412f
5
+ SHA512:
6
+ metadata.gz: 8c7410e0df3c0dd5567f3bd084f7b46ecdf7059f7d6fec1074c11cc2dc09f5e36d4369af58622cf2a55dbbdd9b55b72ac4b5e955f406e4933432ccf70ac030c1
7
+ data.tar.gz: 9baabc9b16ff42330a03d8d3fb59dee6fb3822d14a75488f5751f2b85437934404540fd2820bff5490f5b8184a961e3a14eb6cf804ce3bc67997be9a207ef782
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+
12
+ AllCops:
13
+ DisabledByDefault: true
14
+ Exclude:
15
+ - '**/*.rex.rb'
16
+ Style/FrozenStringLiteralComment:
17
+ Enabled: true
18
+ SafeAutoCorrect: true
data/.solargraph.yml ADDED
@@ -0,0 +1,22 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - spec/**/*
6
+ - test/**/*
7
+ - vendor/**/*
8
+ - ".bundle/**/*"
9
+ require: []
10
+ domains: []
11
+ reporters:
12
+ - rubocop
13
+ - require_not_found
14
+ formatter:
15
+ rubocop:
16
+ cops: safe
17
+ except: []
18
+ only: []
19
+ extra_args: []
20
+ require_paths: []
21
+ plugins: []
22
+ max_files: 5000
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ ignore:
2
+ - '**/*.rex'
3
+ - '**/*.rex.rb'
@@ -0,0 +1,34 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "rdbg",
9
+ "name": "Debug specs",
10
+ "request": "launch",
11
+ "command": "rspec",
12
+ "script": "spec",
13
+ "args": [],
14
+ "askParameters": false
15
+ },
16
+ {
17
+ "type": "rdbg",
18
+ "name": "Debug console",
19
+ "request": "launch",
20
+ "script": "${workspaceFolder}/bin/console",
21
+ "args": [],
22
+ "askParameters": false,
23
+ "useTerminal": true
24
+ },
25
+ {
26
+ "type": "rdbg",
27
+ "name": "Debug current file",
28
+ "request": "launch",
29
+ "script": "${file}",
30
+ "args": [],
31
+ "askParameters": false
32
+ },
33
+ ]
34
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.1.0] - 2023-10-20
2
+
3
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::Grub
4
+ class Lexer
5
+ AUTOLOADERS = [] unless defined? AUTOLOADERS
6
+ end
7
+ end
8
+
9
+ source "https://rubygems.org"
10
+ gemspec
11
+
12
+ gem "rake", "~> 13.0"
13
+ gem "rspec", "~> 3.0"
14
+ gem "standard", "~> 1.31"
15
+ gem "solargraph", "~> 0.49.0"
16
+ gem "zeitwerk", "~> 2.6"
17
+ gem "rubocop", "~> 1.56", require: false
18
+ gem "oedipus_lex", "~> 2.6"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Paweł Pokrywka
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 ADDED
@@ -0,0 +1,95 @@
1
+ # Grub::Lexer
2
+
3
+ Convert Grub boot loader configuration into tokens that can be fed into the parser.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ bundle add grub-lexer
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ gem install grub-lexer
14
+
15
+ ## Usage
16
+
17
+ ### Basic example
18
+
19
+ require "grub/lexer"
20
+ lexer = Grub::Lexer.new
21
+ lexer.parse "sleep 1"
22
+ lexer.next_token # => [:WORD, "sleep"]
23
+ lexer.next_token # => [:WORD, "1"]
24
+ lexer.next_token # => nil
25
+
26
+ ### Loading configuration from file
27
+
28
+ require "grub/lexer"
29
+ lexer = Grub::Lexer.new
30
+ lexer.parse_file "/boot/grub/grub.cfg"
31
+
32
+ ### Variables expansion
33
+
34
+ require "grub/lexer"
35
+ expander = ->(var) { var == :greeting ? "hello" : "awesome" }
36
+ lexer = Grub::Lexer.new expander
37
+ lexer.parse %{echo "$greeting ${adjective} world!"\n}
38
+ lexer.next_token # => [:WORD, "echo"]
39
+ lexer.next_token # => [:WORD, "hello awesome world!"]
40
+ lexer.next_token # => [:SEPARATOR, "\n"]
41
+
42
+ ### Variables detection and per-call expanders
43
+
44
+ require "grub/lexer"
45
+ lexer = Grub::Lexer.new ->(_) { "same" }
46
+ lexer.parse '$var1 text "$var2 text"'
47
+ token = lexer.next_token # => [:WORD, "same"]
48
+ token[1].expandable? # => true
49
+ token = lexer.next_token # => [:WORD, "text"]
50
+ token[1].expandable? # => false
51
+ token = lexer.next_token # => [:WORD, "same text"]
52
+ token[1].to_s(->(v) { v.to_s.upcase }) # => "VAR2 text"
53
+
54
+ ## Limitations
55
+
56
+ Only Grub 2 is supported. Grub Legacy may work, but hasn't been tested.
57
+
58
+ The gem doesn't support Grub translations.
59
+ I18n strings (`$"translation key"`) are treated like normal strings in double quotes.
60
+
61
+ Only subset of metacharacters are recognized as distinct token types.
62
+ However, implementation of missing metacharacter types is trivial.
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run
67
+ the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
68
+
69
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update
70
+ the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for
71
+ the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
72
+
73
+ ## Contributing
74
+
75
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/phantom-node/grub-lexer>. This project
76
+ is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
77
+ the [code of conduct](https://github.com/[USERNAME]/grub-lexer/blob/master/CODE_OF_CONDUCT.md).
78
+
79
+ ## Author
80
+
81
+ My name is Paweł Pokrywka and I'm the author of grub-lexer.
82
+
83
+ If you want to contact me or get to know me better, check out
84
+ [my blog](https://blog.pawelpokrywka.com).
85
+
86
+ Thank you for your interest in this project :)
87
+
88
+ ## License
89
+
90
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
91
+
92
+ ## Code of Conduct
93
+
94
+ Everyone interacting in the Grub project's codebases, issue trackers, chat rooms and mailing lists is expected to
95
+ follow the [code of conduct](https://github.com/[USERNAME]/grub-lexer/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "standard/rake"
4
+ require "bundler/gem_tasks"
5
+ require "rspec/core/rake_task"
6
+ Rake.application.rake_require "oedipus_lex"
7
+
8
+ Rake::Task.define_task :validate_loader do
9
+ abort "Basic loader is stale, run `bin/loader generate` to fix" unless system("bin/loader validate")
10
+ end
11
+ Rake::Task[:build].enhance [:validate_loader]
12
+
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ task generate_lexers: ["lib/grub/lexer/lex.rex.rb"]
16
+ task spec: :generate_lexers
17
+
18
+ task default: %i[spec]
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/grub/lexer/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "grub-lexer"
7
+ spec.version = Grub::Lexer::VERSION
8
+ spec.authors = ["Paweł Pokrywka"]
9
+ spec.email = ["pepawel@users.noreply.github.com"]
10
+
11
+ spec.summary = "Convert Grub boot loader configuration into tokens that can be fed into the parser."
12
+ spec.homepage = "https://phantomno.de/grub-lexer"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.7.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/phantom-node/grub-lexer"
18
+ spec.metadata["changelog_uri"] = "https://github.com/phantom-node/grub-lexer/blob/master/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # File generated automatically, do not edit
4
+
5
+ require "grub/lexer/lex"
6
+ require "grub/lexer/token_value"
7
+ require "grub/lexer/word"
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lex.rex"
4
+
5
+ module Grub
6
+ class Lexer
7
+ class Lex
8
+ UnmatchedSingleQuote = Class.new StandardError
9
+ UnmatchedDoubleQuote = Class.new StandardError
10
+ NothingFollowsEscape = Class.new StandardError
11
+ InvalidVariableName = Class.new StandardError
12
+
13
+ def call
14
+ return nil unless ss
15
+ token = next_non_state_token
16
+ return token if token
17
+ raise state_remain_exception if state_remain_exception
18
+ [word_type, word!] if word.present?
19
+ end
20
+
21
+ private
22
+
23
+ def next_non_state_token
24
+ token = nil
25
+ loop do
26
+ token = next_token
27
+ next if token && token[0] == :state
28
+ break
29
+ end
30
+ token
31
+ end
32
+
33
+ def handle_meta(type, text)
34
+ return [type, text] if word.blank?
35
+ ss.unscan
36
+ [word_type, word!]
37
+ end
38
+
39
+ def handle_blank(_)
40
+ word.present? ? [word_type, word!] : nil
41
+ end
42
+
43
+ def handle_variable(_)
44
+ var = match[1] || match[2]
45
+ word.variable(var)
46
+ nil
47
+ end
48
+
49
+ def append_first_match(_)
50
+ word.append match[1]
51
+ nil
52
+ end
53
+
54
+ def word
55
+ @word ||= word_builder.call
56
+ end
57
+
58
+ def word!
59
+ word.tap { @word = word_builder.call }
60
+ end
61
+
62
+ def start_of_line?
63
+ !previous_char || previous_char == "\n"
64
+ end
65
+
66
+ def previous_blank?
67
+ [" ", "\t"].include? previous_char
68
+ end
69
+
70
+ def previous_char
71
+ return if ss.charpos < 1
72
+ ss.string[ss.charpos - 1]
73
+ end
74
+
75
+ def enter_state(name, raise_if_remain: nil)
76
+ @state_remain_exception = raise_if_remain
77
+ [:state, name]
78
+ end
79
+
80
+ def leave_state(_)
81
+ @state_remain_exception = nil
82
+ [:state, nil]
83
+ end
84
+
85
+ attr_reader :state_remain_exception
86
+
87
+ # Empty method required by lexer generator
88
+ def do_parse
89
+ end
90
+
91
+ attr_reader :word_type, :word_builder
92
+
93
+ def initialize(word_type: :WORD, word_builder: -> { Word.new })
94
+ @word_type = word_type
95
+ @word_builder = word_builder
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,43 @@
1
+ module Grub
2
+ class Lexer
3
+
4
+ # Indentation has to be zeroed for lexer generator to work
5
+ class Lex
6
+
7
+ macros
8
+ NORMAL_VAR /[[:alpha:]_][[:alnum:]_]*/
9
+ POSITIONAL_VAR /[0-9]+/
10
+ SPECIAL_VAR /#|@|\?|\*/
11
+ VAR /#{NORMAL_VAR}|#{POSITIONAL_VAR}|#{SPECIAL_VAR}/
12
+
13
+ rules
14
+ /\\\n/
15
+ start_of_line? /#.*\n/
16
+ previous_blank? /#.*\n/ { [:SEPARATOR, "\n"] }
17
+ /\\(.)/ append_first_match
18
+ /\\/ { raise NothingFollowsEscape }
19
+ /'([^']*)'/ append_first_match
20
+ /'/ { raise UnmatchedSingleQuote }
21
+ /\$"/ :QUOTE # i18n not supported
22
+ /"/ { enter_state :QUOTE, raise_if_remain: UnmatchedDoubleQuote }
23
+ /\${(#{VAR})}|\$(#{VAR})/ handle_variable
24
+ /\$/ { raise InvalidVariableName }
25
+ /\n|;/ { handle_meta(:SEPARATOR, text) }
26
+ /{/ { handle_meta(:BEGIN, text) }
27
+ /}/ { handle_meta(:END, text) }
28
+ /&|<|>|\|/ { handle_meta(:META, text) }
29
+ /[ \t]+/ handle_blank
30
+ /(.)/ append_first_match
31
+
32
+ :QUOTE /\\(\n)/
33
+ :QUOTE /\\(\$)/ append_first_match
34
+ :QUOTE /\\(")/ append_first_match
35
+ :QUOTE /\\(\\)/ append_first_match
36
+ :QUOTE /\${(#{VAR})}|\$(#{VAR})/ handle_variable
37
+ :QUOTE /"/ leave_state
38
+ :QUOTE /(.|\n)/ append_first_match
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ # encoding: UTF-8
3
+ #--
4
+ # This file is automatically generated. Do not modify it.
5
+ # Generated by: oedipus_lex version 2.6.1.
6
+ # Source: lib/grub/lexer/lex.rex
7
+ #++
8
+
9
+ module Grub
10
+ class Lexer
11
+
12
+ # Indentation has to be zeroed for lexer generator to work
13
+
14
+
15
+ ##
16
+ # The generated lexer Lex
17
+
18
+ class Lex
19
+ require 'strscan'
20
+
21
+ # :stopdoc:
22
+ NORMAL_VAR = /[[:alpha:]_][[:alnum:]_]*/
23
+ POSITIONAL_VAR = /[0-9]+/
24
+ SPECIAL_VAR = /#|@|\?|\*/
25
+ VAR = /#{NORMAL_VAR}|#{POSITIONAL_VAR}|#{SPECIAL_VAR}/
26
+ # :startdoc:
27
+ # :stopdoc:
28
+ class LexerError < StandardError ; end
29
+ class ScanError < LexerError ; end
30
+ # :startdoc:
31
+
32
+ ##
33
+ # The file name / path
34
+
35
+ attr_accessor :filename
36
+
37
+ ##
38
+ # The StringScanner for this lexer.
39
+
40
+ attr_accessor :ss
41
+
42
+ ##
43
+ # The current lexical state.
44
+
45
+ attr_accessor :state
46
+
47
+ alias :match :ss
48
+
49
+ ##
50
+ # The match groups for the current scan.
51
+
52
+ def matches
53
+ m = (1..9).map { |i| ss[i] }
54
+ m.pop until m[-1] or m.empty?
55
+ m
56
+ end
57
+
58
+ ##
59
+ # Yields on the current action.
60
+
61
+ def action
62
+ yield
63
+ end
64
+
65
+
66
+ ##
67
+ # The current scanner class. Must be overridden in subclasses.
68
+
69
+ def scanner_class
70
+ StringScanner
71
+ end unless instance_methods(false).map(&:to_s).include?("scanner_class")
72
+
73
+ ##
74
+ # Parse the given string.
75
+
76
+ def parse str
77
+ self.ss = scanner_class.new str
78
+ self.state ||= nil
79
+
80
+ do_parse
81
+ end
82
+
83
+ ##
84
+ # Read in and parse the file at +path+.
85
+
86
+ def parse_file path
87
+ self.filename = path
88
+ open path do |f|
89
+ parse f.read
90
+ end
91
+ end
92
+
93
+ ##
94
+ # The current location in the parse.
95
+
96
+ def location
97
+ [
98
+ (filename || "<input>"),
99
+ ].compact.join(":")
100
+ end
101
+
102
+ ##
103
+ # Lex the next token.
104
+
105
+ def next_token
106
+
107
+ token = nil
108
+
109
+ until ss.eos? or token do
110
+ token =
111
+ case state
112
+ when nil then
113
+ case
114
+ when ss.skip(/\\\n/) then
115
+ # do nothing
116
+ when start_of_line? && (ss.skip(/#.*\n/)) then
117
+ # do nothing
118
+ when previous_blank? && (ss.skip(/#.*\n/)) then
119
+ action { [:SEPARATOR, "\n"] }
120
+ when text = ss.scan(/\\(.)/) then
121
+ append_first_match text
122
+ when ss.skip(/\\/) then
123
+ action { raise NothingFollowsEscape }
124
+ when text = ss.scan(/'([^']*)'/) then
125
+ append_first_match text
126
+ when ss.skip(/'/) then
127
+ action { raise UnmatchedSingleQuote }
128
+ when ss.skip(/\$"/) then
129
+ [:state, :QUOTE]
130
+ when ss.skip(/"/) then
131
+ action { enter_state :QUOTE, raise_if_remain: UnmatchedDoubleQuote }
132
+ when text = ss.scan(/\${(#{VAR})}|\$(#{VAR})/) then
133
+ handle_variable text
134
+ when ss.skip(/\$/) then
135
+ action { raise InvalidVariableName }
136
+ when text = ss.scan(/\n|;/) then
137
+ action { handle_meta(:SEPARATOR, text) }
138
+ when text = ss.scan(/{/) then
139
+ action { handle_meta(:BEGIN, text) }
140
+ when text = ss.scan(/}/) then
141
+ action { handle_meta(:END, text) }
142
+ when text = ss.scan(/&|<|>|\|/) then
143
+ action { handle_meta(:META, text) }
144
+ when text = ss.scan(/[ \t]+/) then
145
+ handle_blank text
146
+ when text = ss.scan(/(.)/) then
147
+ append_first_match text
148
+ else
149
+ text = ss.string[ss.pos .. -1]
150
+ raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
151
+ end
152
+ when :QUOTE then
153
+ case
154
+ when ss.skip(/\\(\n)/) then
155
+ # do nothing
156
+ when text = ss.scan(/\\(\$)/) then
157
+ append_first_match text
158
+ when text = ss.scan(/\\(")/) then
159
+ append_first_match text
160
+ when text = ss.scan(/\\(\\)/) then
161
+ append_first_match text
162
+ when text = ss.scan(/\${(#{VAR})}|\$(#{VAR})/) then
163
+ handle_variable text
164
+ when text = ss.scan(/"/) then
165
+ leave_state text
166
+ when text = ss.scan(/(.|\n)/) then
167
+ append_first_match text
168
+ else
169
+ text = ss.string[ss.pos .. -1]
170
+ raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
171
+ end
172
+ else
173
+ raise ScanError, "undefined state at #{location}: '#{state}'"
174
+ end # token = case state
175
+
176
+ next unless token # allow functions to trigger redo w/ nil
177
+ end # while
178
+
179
+ raise LexerError, "bad lexical result at #{location}: #{token.inspect}" unless
180
+ token.nil? || (Array === token && token.size >= 2)
181
+
182
+ # auto-switch state
183
+ self.state = token.last if token && token.first == :state
184
+
185
+ token
186
+ end # def next_token
187
+ end # class
188
+
189
+ end
190
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grub
4
+ class Lexer
5
+ class TokenValue < String
6
+ def to_s(expander = nil)
7
+ return super() unless expander
8
+ parts.map do |part|
9
+ part.is_a?(Symbol) ? expander.call(part) : part
10
+ end.join
11
+ end
12
+
13
+ def expandable?
14
+ parts.any? { |e| e.is_a? Symbol }
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :parts, :default_expander
20
+
21
+ def initialize(parts, expander)
22
+ @parts = parts
23
+ @default_expander = expander
24
+ super to_s(expander)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grub
4
+ class Lexer
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grub
4
+ class Lexer
5
+ class Word
6
+ attr_reader :parts
7
+
8
+ def append(text)
9
+ parts << text.to_s
10
+ end
11
+
12
+ def variable(v)
13
+ parts << v.to_sym
14
+ end
15
+
16
+ def present?
17
+ !blank?
18
+ end
19
+
20
+ def blank?
21
+ parts.empty?
22
+ end
23
+
24
+ private
25
+
26
+ def initialize
27
+ @parts = []
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/grub/lexer.rb ADDED
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lexer/version"
4
+
5
+ if defined? Grub::Lexer::AUTOLOADERS
6
+ require "zeitwerk"
7
+ Grub::Lexer::AUTOLOADERS << Zeitwerk::Loader.for_gem_extension(Grub).tap do |loader|
8
+ loader.ignore("#{__dir__}/**/*.rex.rb")
9
+ loader.setup
10
+ end
11
+ else
12
+ require "basic_loader"
13
+ end
14
+
15
+ module Grub
16
+ class Lexer
17
+ def parse(content)
18
+ worker.parse(content)
19
+ end
20
+
21
+ def parse_file(file)
22
+ worker.parse_file(file)
23
+ end
24
+
25
+ def next_token
26
+ token = worker.call
27
+ return unless token
28
+ [token.first, expand(token.last)]
29
+ end
30
+
31
+ private
32
+
33
+ def expand(value)
34
+ arg = word_checker.call(value) ? value.parts : [value]
35
+ value_builder.call arg
36
+ end
37
+
38
+ attr_reader :worker, :word_checker, :value_builder
39
+
40
+ def initialize(
41
+ expander = ->(v) { "${#{v}}" },
42
+ worker: Lex.new,
43
+ word_checker: ->(v) { v.is_a? Word },
44
+ value_builder: ->(arg) { TokenValue.new(arg, expander) }
45
+ )
46
+ @worker = worker
47
+ @word_checker = word_checker
48
+ @value_builder = value_builder
49
+ end
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grub-lexer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paweł Pokrywka
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-10-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - pepawel@users.noreply.github.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - ".rubocop.yml"
22
+ - ".solargraph.yml"
23
+ - ".standard.yml"
24
+ - ".vscode/launch.json"
25
+ - CHANGELOG.md
26
+ - CODE_OF_CONDUCT.md
27
+ - Gemfile
28
+ - LICENSE.txt
29
+ - README.md
30
+ - Rakefile
31
+ - grub-lexer.gemspec
32
+ - lib/basic_loader.rb
33
+ - lib/grub/lexer.rb
34
+ - lib/grub/lexer/lex.rb
35
+ - lib/grub/lexer/lex.rex
36
+ - lib/grub/lexer/lex.rex.rb
37
+ - lib/grub/lexer/token_value.rb
38
+ - lib/grub/lexer/version.rb
39
+ - lib/grub/lexer/word.rb
40
+ homepage: https://phantomno.de/grub-lexer
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://phantomno.de/grub-lexer
45
+ source_code_uri: https://github.com/phantom-node/grub-lexer
46
+ changelog_uri: https://github.com/phantom-node/grub-lexer/blob/master/CHANGELOG.md
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 2.7.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.2.22
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Convert Grub boot loader configuration into tokens that can be fed into the
66
+ parser.
67
+ test_files: []