kdl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 54cb1225594aa690afe9708fe772f2d9a2847fd411081e5a4635509086b8229b
4
+ data.tar.gz: 248db2de267c36e4f1889b98ca3d17446c52de27459e94a7d3f7a7a7ff9a55f6
5
+ SHA512:
6
+ metadata.gz: 51ce94fcf0542173069ccda592b18463981d8b91a2984b8bba34b2c54e2e1b9e0de521b46ca5a29ae440eb884ebe7b209104a48261b51fbfd43098173b5ae3ae
7
+ data.tar.gz: 6844f3747214785f7b206b51fdaa0d36f133f56c6ab2e1b09365e3d545e20c27eec5b9a91c33c581a30ccafc5221e1a1b540c63f7ce42e9f3f2bd65e76b5991f
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ main ]
13
+ pull_request:
14
+ branches: [ main ]
15
+
16
+ jobs:
17
+ test:
18
+ strategy:
19
+ matrix:
20
+ ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0.0-preview1]
21
+
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ - name: Install dependencies
31
+ run: gem install bundler && bundle install
32
+ - name: Build parser
33
+ run: bundle exec racc lib/kdl/kdl.yy
34
+ - name: Run tests
35
+ run: bundle exec rake test
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
11
+
12
+ lib/kdl/kdl.tab.rb
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.0
6
+ before_install: gem install bundler -v 2.1.2
@@ -0,0 +1,105 @@
1
+ # Code of Conduct
2
+
3
+ ## When Something Happens
4
+
5
+ If you see behavoir that makes you feel unsafe or unwelcome or otherwise uncomfortable, follow these steps:
6
+
7
+ 1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s) or commits.
8
+ 2. That person should immediately stop the behavior and correct the issue.
9
+ 3. If this doesn’t happen, or if you're uncomfortable speaking up, [contact the maintainers](#contacting-maintainers).
10
+ 4. As soon as available, a maintainer will look into the issue, and take [further action (see below)](#further-enforcement), starting with a warning, then temporary block, then long-term repo or organization ban.
11
+
12
+ **The maintainer team will prioritize the well-being and comfort of those affected over the comfort of the offending party.** See [some examples below](#enforcement-examples).
13
+
14
+ ## Our Pledge
15
+
16
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers of this project pledge to making participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, technical preferences, nationality, personal appearance, race, religion, or sexual identity and orientation.
17
+
18
+ This commitment means that inappropriate behavior can lead to intervention. This includes our intention to address issues with [missing stairs](https://en.wikipedia.org/wiki/Missing_stair) who may not have explicitly violated any written-down rules but might still be disrupting the community.
19
+
20
+ ## Scope
21
+
22
+ This Code of Conduct applies both within spaces involving this project and in other spaces involving community members. This includes the repository, its Pull Requests and Issue tracker, its Twitter community, private email communications in the context of the project, and any events where members of the project are participating, as well as adjacent communities and venues affecting the project's members.
23
+
24
+ Depending on the violation, the maintainers may decide that violations of this code of conduct that have happened outside of the scope of the community may deem an individual unwelcome, and take appropriate action to maintain the comfort and safety of its members.
25
+
26
+ ## Contacting Maintainers
27
+
28
+ - [Dan Smith <danini@hey.com>](mailto:danini@hey.com)
29
+
30
+ ## Further Enforcement
31
+
32
+ If you've already followed the [initial enforcement steps](#enforcement), these are the steps maintainers will take for further enforcement, as needed:
33
+
34
+ 1. Repeat the request to stop.
35
+ 2. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked.
36
+ 3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours.
37
+ 4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used.
38
+
39
+ On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary.
40
+
41
+ Maintainers reserve full rights to skip any of these steps, at their discretion, if the violation is considered to be a serious and/or immediate threat to the health and well-being of members of the community. These include any threats, serious physical or verbal attacks, and other such behavior that would be completely unacceptable in any social setting that puts our members at risk.
42
+
43
+ Members expelled from events or venues with any sort of paid attendance will not be refunded.
44
+
45
+ ## Who Watches the Watchers?
46
+
47
+ Maintainers and other leaders who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. These may include anything from removal from the maintainer team to a permanent ban from the community.
48
+
49
+ Additionally, as a project hosted on both GitHub, [their Community Guidielines may be applied to maintainers of this project](https://help.github.com/articles/github-community-guidelines/), externally of this project's procedures.
50
+
51
+ ## Enforcement Examples
52
+
53
+ ### The Best Case
54
+
55
+ The vast majority of situations work out like this. This interaction is common, and generally positive.
56
+
57
+ > Alex: "Yeah I used X and it was really crazy!"
58
+
59
+ > Patt (not a maintainer): "Hey, could you not use that word? What about 'ridiculous' instead?"
60
+
61
+ > Alex: "oh sorry, sure." -> edits old comment to say "it was really confusing!"
62
+
63
+ ### The Maintainer Case
64
+
65
+ Sometimes, though, you need to get maintainers involved. Maintainers will do their best to resolve conflicts, but people who were harmed by something **will take priority**.
66
+
67
+ > Patt: "Honestly, sometimes I just really hate using $library and anyone who uses it probably sucks at their job."
68
+
69
+ > Alex: "Whoa there, could you dial it back a bit? There's a CoC thing about attacking folks' tech use like that."
70
+
71
+ > Patt: "I'm not attacking anyone, what's your problem?"
72
+
73
+ > Alex: "@maintainers hey uh. Can someone look at this issue? Patt is getting a bit aggro. I tried to nudge them about it, but nope."
74
+
75
+ > KeeperOfCommitBits: (on issue) "Hey Patt, maintainer here. Could you tone it down? This sort of attack is really not okay in this space."
76
+
77
+ > Patt: "Leave me alone I haven't said anything bad wtf is wrong with you."
78
+
79
+ > KeeperOfCommitBits: (deletes user's comment), "@patt I mean it. Please refer to the CoC over at (URL to this CoC) if you have questions, but you can consider this an actual warning. I'd appreciate it if you reworded your messages in this thread, since they made folks there uncomfortable. Let's try and be kind, yeah?"
80
+
81
+ > Patt: "@keeperofbits Okay sorry. I'm just frustrated and I'm kinda burnt out and I guess I got carried away. I'll DM Alex a note apologizing and edit my messages. Sorry for the trouble."
82
+
83
+ > KeeperOfCommitBits: "@patt Thanks for that. I hear you on the stress. Burnout sucks :/. Have a good one!"
84
+
85
+ ### The Nope Case
86
+
87
+ > PepeTheFrog🐸: "Hi, I am a literal actual nazi and I think white supremacists are quite fashionable."
88
+
89
+ > Patt: "NOOOOPE. OH NOPE NOPE."
90
+
91
+ > Alex: "JFC NO. NOPE. @keeperofbits NOPE NOPE LOOK HERE"
92
+
93
+ > KeeperOfCommitBits: "👀 Nope. NOPE NOPE NOPE. 🔥"
94
+
95
+ > PepeTheFrog🐸 has been banned from all organization or user repositories belonging to KeeperOfCommitBits.
96
+
97
+ ## Attribution
98
+
99
+ This Code of Conduct was generated using [WeAllJS Code of Conduct Generator](https://npm.im/weallbehave), which is based on the [WeAllJS Code of
100
+ Conduct](https://wealljs.org/code-of-conduct), which is itself based on
101
+ [Contributor Covenant](http://contributor-covenant.org), version 1.4, available
102
+ at
103
+ [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4),
104
+ and the LGBTQ in Technology Slack [Code of
105
+ Conduct](http://lgbtq.technology/coc.html).
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in kdl.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "minitest", "~> 5.0"
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Daniel Smith
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.
@@ -0,0 +1,44 @@
1
+ # KDL
2
+
3
+ [![Actions Status](https://github.com/jellymann/kdl-rb/workflows/Ruby/badge.svg)](https://github.com/jellymann/kdl-rb\/actions)
4
+
5
+ This is a Ruby implementation of the [KDL Document Language](https://kdl.dev)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'kdl'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install kdl
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'kdl'
27
+
28
+ KDL.parse_document(a_string) #=> KDL::Document
29
+ ```
30
+
31
+ ## Development
32
+
33
+ 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.
34
+
35
+ 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).
36
+
37
+ ## Contributing
38
+
39
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jellymann/kdl-rb.
40
+
41
+
42
+ ## License
43
+
44
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ t.options = '--pride'
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kdl"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ require_relative 'lib/kdl/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "kdl"
5
+ spec.version = KDL::VERSION
6
+ spec.authors = ["Daniel Smith"]
7
+ spec.email = ["danini@hey.com"]
8
+
9
+ spec.summary = %q{KDL Document Language}
10
+ spec.description = %q{Ruby implementation of the KDL Document Language Spec}
11
+ spec.homepage = "https://kdl.dev"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/jellymann/kdl-rb"
17
+ spec.metadata["changelog_uri"] = "https://github.com/jellymann/kdl-rb/releases"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency 'racc', '~> 1.5'
29
+ end
@@ -0,0 +1,12 @@
1
+ require "kdl/version"
2
+ require "kdl/tokenizer"
3
+ require "kdl/document"
4
+ require "kdl/value"
5
+ require "kdl/node"
6
+ require "kdl/kdl.tab"
7
+
8
+ module KDL
9
+ def self.parse_document(input)
10
+ Parser.new.parse(input)
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ module KDL
2
+ class Document
3
+ attr_accessor :nodes
4
+
5
+ def initialize(nodes)
6
+ @nodes = nodes
7
+ end
8
+
9
+ def to_s
10
+ @nodes.map(&:to_s).join("\n")
11
+ end
12
+ alias inspect to_s
13
+
14
+ def ==(other)
15
+ return false unless other.is_a?(Document)
16
+
17
+ nodes == other.nodes
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ class KDL::Parser
2
+ options no_result_var
3
+ token IDENT
4
+ STRING RAWSTRING
5
+ INTEGER FLOAT TRUE FALSE NULL
6
+ WS NEWLINE
7
+ LPAREN RPAREN
8
+ EQUALS
9
+ SEMICOLON
10
+ EOF
11
+ SLASHDASH
12
+ rule
13
+ document : nodes { KDL::Document.new(val[0]) }
14
+ | linespaces { KDL::Document.new([])}
15
+
16
+ nodes : none { [] }
17
+ | linespace_star node { [val[1]] }
18
+ | linespace_star empty_node { [] }
19
+ | nodes node { [*val[0], val[1]] }
20
+ | nodes empty_node { val[0] }
21
+ node : node_decl node_term { val[0] }
22
+ | node_decl node_children node_term { val[0].tap { |x| x.children = val[1] } }
23
+ | node_decl empty_children node_term { val[0] }
24
+ node_decl : identifier { KDL::Node.new(val[0]) }
25
+ | node_decl WS value { val[0].tap { |x| x.arguments << val[2] } }
26
+ | node_decl WS SLASHDASH ws_star value { val[0] }
27
+ | node_decl WS property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
28
+ | node_decl WS SLASHDASH ws_star property { val[0] }
29
+ empty_node: SLASHDASH ws_star node
30
+ node_children: ws_star LPAREN nodes RPAREN { val[2] }
31
+ empty_children: SLASHDASH node_children
32
+ | WS empty_children
33
+ node_term: linespaces | semicolon_term
34
+ semicolon_term: SEMICOLON | SEMICOLON linespaces
35
+
36
+ identifier: IDENT { val[0] }
37
+ | STRING { val[0] }
38
+
39
+ property: identifier EQUALS value { [val[0], val[2]] }
40
+
41
+ value : STRING { KDL::Value::String.new(val[0]) }
42
+ | RAWSTRING { KDL::Value::String.new(val[0]) }
43
+ | INTEGER { KDL::Value::Int.new(val[0]) }
44
+ | FLOAT { KDL::Value::Float.new(val[0]) }
45
+ | boolean { KDL::Value::Boolean.new(val[0]) }
46
+ | NULL { KDL::Value::Null }
47
+
48
+ boolean : TRUE { true }
49
+ | FALSE { false }
50
+
51
+ ws_star: none | WS
52
+ linespace: WS | NEWLINE | EOF
53
+ linespaces: linespace | linespaces linespace
54
+ linespace_star: none | linespaces
55
+
56
+ none: { nil }
57
+
58
+ ---- inner
59
+ def parse(str)
60
+ @tokenizer = ::KDL::Tokenizer.new(str)
61
+ do_parse
62
+ end
63
+
64
+ private
65
+
66
+ def next_token
67
+ @tokenizer.next_token
68
+ end
@@ -0,0 +1,39 @@
1
+ module KDL
2
+ class Node
3
+ attr_accessor :name, :arguments, :properties, :children
4
+
5
+ def initialize(name, arguments = [], properties = {}, children = [])
6
+ @name = name
7
+ @arguments = arguments
8
+ @properties = properties
9
+ @children = children
10
+ end
11
+
12
+ def to_s(level = 0)
13
+ indent = ' ' * level
14
+ s = "#{indent}#{name}"
15
+ unless arguments.empty?
16
+ s += " #{arguments.map(&:to_s).join(' ')}"
17
+ end
18
+ unless properties.empty?
19
+ s += " #{properties.map { |k, v| "#{k}=#{v}" }.join(' ')}"
20
+ end
21
+ unless children.empty?
22
+ s += " {\n"
23
+ s += children.map { |c| c.to_s(level + 1) }.join("\n")
24
+ s += "\n#{indent}}"
25
+ end
26
+ s
27
+ end
28
+ alias inspect to_s
29
+
30
+ def ==(other)
31
+ return false unless other.is_a?(Node)
32
+
33
+ name == other.name &&
34
+ arguments == other.arguments &&
35
+ properties == other.properties &&
36
+ children == other.children
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,322 @@
1
+ module KDL
2
+ class Tokenizer
3
+ class Error < StandardError; end
4
+
5
+ attr_reader :index
6
+
7
+ SYMBOLS = {
8
+ '{' => :LPAREN,
9
+ '}' => :RPAREN,
10
+ '=' => :EQUALS,
11
+ '=' => :EQUALS,
12
+ ';' => :SEMICOLON
13
+ }
14
+
15
+ WHITEPACE = ["\u0009", "\u0020", "\u00A0", "\u1680",
16
+ "\u2000", "\u2001", "\u2002", "\u2003",
17
+ "\u2004", "\u2005", "\u2006", "\u2007",
18
+ "\u2008", "\u2009", "\u200A", "\u202F",
19
+ "\u205F", "\u3000" ]
20
+
21
+ NEWLINES = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]
22
+
23
+ NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}\\<>[]\","
24
+ IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}\x0-\x20]/
25
+ INITIAL_IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}0-9\x0-\x20]/
26
+
27
+ def initialize(str, start = 0)
28
+ @str = str
29
+ @context = nil
30
+ @rawstring_hashes = nil
31
+ @index = start
32
+ @buffer = ""
33
+ @done = false
34
+ @previous_context = nil
35
+ end
36
+
37
+ def next_token
38
+ @context = nil
39
+ @previous_context = nil
40
+ loop do
41
+ c = @str[@index]
42
+ case @context
43
+ when nil
44
+ case c
45
+ when '"'
46
+ self.context = :string
47
+ @buffer = ''
48
+ @index += 1
49
+ when 'r'
50
+ if @str[@index + 1] == '"'
51
+ self.context = :rawstring
52
+ @index += 2
53
+ @rawstring_hashes = 0
54
+ @buffer = ''
55
+ next
56
+ elsif @str[@index + 1] == '#'
57
+ i = @index + 1
58
+ @rawstring_hashes = 0
59
+ while @str[i] == '#'
60
+ @rawstring_hashes += 1
61
+ i += 1
62
+ end
63
+ if @str[i] == '"'
64
+ self.context = :rawstring
65
+ @index = i + 1
66
+ @buffer = ''
67
+ next
68
+ end
69
+ end
70
+ self.context = :ident
71
+ @buffer = c
72
+ @index += 1
73
+ when /[0-9\-+]/
74
+ n = @str[@index + 1]
75
+ if c == '0' && n =~ /[box]/
76
+ @index += 2
77
+ @buffer = ''
78
+ self.context = case n
79
+ when 'b' then :binary
80
+ when 'o' then :octal
81
+ when 'x' then :hexadecimal
82
+ end
83
+ else
84
+ self.context = :decimal
85
+ @index += 1
86
+ @buffer = c
87
+ end
88
+ when '\\'
89
+ t = Tokenizer.new(@str, @index + 1)
90
+ la = t.next_token[0]
91
+ if la == :NEWLINE
92
+ @index = t.index
93
+ elsif la == :WS && (lan = t.next_token[0]) == :NEWLINE
94
+ @index = t.index
95
+ else
96
+ raise Error, "Unexpected '\\'"
97
+ end
98
+ when *SYMBOLS.keys
99
+ @index += 1
100
+ return [SYMBOLS[c], c]
101
+ when "\r"
102
+ n = @str[@index + 1]
103
+ if n == "\n"
104
+ @index += 2
105
+ return [:NEWLINE, "#{c}#{n}"]
106
+ else
107
+ @index += 1
108
+ return [:NEWLINE, c]
109
+ end
110
+ when *NEWLINES
111
+ @index += 1
112
+ return [:NEWLINE, c]
113
+ when "/"
114
+ if @str[@index + 1] == '/'
115
+ self.context = :single_line_comment
116
+ @index += 2
117
+ elsif @str[@index + 1] == '*'
118
+ self.context = :multi_line_comment
119
+ @comment_nesting = 1
120
+ @index += 2
121
+ elsif @str[@index + 1] == '-'
122
+ @index += 2
123
+ return [:SLASHDASH, '/-']
124
+ else
125
+ self.context = :ident
126
+ @buffer = c
127
+ @index += 1
128
+ end
129
+ when *WHITEPACE
130
+ self.context = :whitespace
131
+ @buffer = c
132
+ @index += 1
133
+ when nil
134
+ return [false, false] if @done
135
+ @done = true
136
+ return [:EOF, '']
137
+ when INITIAL_IDENTIFIER_CHARS
138
+ self.context = :ident
139
+ @buffer = c
140
+ @index += 1
141
+ else
142
+ raise Error, "Unexpected character #{c.inspect}"
143
+ end
144
+ when :ident
145
+ case c
146
+ when IDENTIFIER_CHARS
147
+ @index += 1
148
+ @buffer += c
149
+ else
150
+ case @buffer
151
+ when 'true' then return [:TRUE, true]
152
+ when 'false' then return [:FALSE, false]
153
+ when 'null' then return [:NULL, nil]
154
+ else return [:IDENT, @buffer]
155
+ end
156
+ end
157
+ when :string
158
+ case c
159
+ when '\\'
160
+ @buffer += c
161
+ @buffer += @str[@index + 1]
162
+ @index += 2
163
+ when '"'
164
+ @index += 1
165
+ return [:STRING, convert_escapes(@buffer)]
166
+ when nil
167
+ raise Error, "Unterminated string literal"
168
+ else
169
+ @buffer += c
170
+ @index += 1
171
+ end
172
+ when :rawstring
173
+ raise Error, "Unterminated rawstring literal" if c.nil?
174
+
175
+ if c == '"'
176
+ h = 0
177
+ while @str[@index + 1 + h] == '#' && h < @rawstring_hashes
178
+ h += 1
179
+ end
180
+ if h == @rawstring_hashes
181
+ @index += 1 + h
182
+ return [:RAWSTRING, @buffer]
183
+ end
184
+ end
185
+
186
+ @buffer += c
187
+ @index += 1
188
+ when :decimal
189
+ case c
190
+ when /[0-9.\-+_eE]/
191
+ @index += 1
192
+ @buffer += c
193
+ else
194
+ return parse_decimal(@buffer)
195
+ end
196
+ when :hexadecimal
197
+ case c
198
+ when /[0-9a-fA-F_]/
199
+ @index += 1
200
+ @buffer += c
201
+ else
202
+ return parse_hexadecimal(@buffer)
203
+ end
204
+ when :octal
205
+ case c
206
+ when /[0-7_]/
207
+ @index += 1
208
+ @buffer += c
209
+ else
210
+ return parse_octal(@buffer)
211
+ end
212
+ when :binary
213
+ case c
214
+ when /[01_]/
215
+ @index += 1
216
+ @buffer += c
217
+ else
218
+ return parse_binary(@buffer)
219
+ end
220
+ when :single_line_comment
221
+ if NEWLINES.include?(c) || c == "\r"
222
+ self.context = nil
223
+ next
224
+ elsif c.nil?
225
+ @done = true
226
+ return [:EOF, '']
227
+ else
228
+ @index += 1
229
+ end
230
+ when :multi_line_comment
231
+ if c == '/' && @str[@index + 1] == '*'
232
+ @comment_nesting += 1
233
+ @index += 2
234
+ elsif c == '*' && @str[@index + 1] == '/'
235
+ @comment_nesting -= 1
236
+ @index += 2
237
+ if @comment_nesting == 0
238
+ revert_context
239
+ end
240
+ else
241
+ @index += 1
242
+ end
243
+ when :whitespace
244
+ if WHITEPACE.include?(c)
245
+ @index += 1
246
+ @buffer += c
247
+ elsif c == "\\"
248
+ t = Tokenizer.new(@str, @index + 1)
249
+ la = t.next_token[0]
250
+ if la == :NEWLINE
251
+ @index = t.index
252
+ elsif (la == :WS && (lan = t.next_token[0]) == :NEWLINE)
253
+ @index = t.index
254
+ else
255
+ raise Error, "Unexpected '\\'"
256
+ end
257
+ elsif c == "/" && @str[@index + 1] == '*'
258
+ self.context = :multi_line_comment
259
+ @comment_nesting = 1
260
+ @index += 2
261
+ else
262
+ return [:WS, @buffer]
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def context=(val)
269
+ @previous_context = @context
270
+ @context = val
271
+ end
272
+
273
+ def revert_context
274
+ @context = @previous_context
275
+ @previous_context = nil
276
+ end
277
+
278
+ private
279
+
280
+ def parse_decimal(s)
281
+ return [:FLOAT, Float(munch_underscores(s))] if s =~ /[.eE]/
282
+ [:INTEGER, Integer(munch_underscores(s), 10)]
283
+ end
284
+
285
+ def parse_hexadecimal(s)
286
+ [:INTEGER, Integer(munch_underscores(s), 16)]
287
+ end
288
+
289
+ def parse_octal(s)
290
+ [:INTEGER, Integer(munch_underscores(s), 8)]
291
+ end
292
+
293
+ def parse_binary(s)
294
+ [:INTEGER, Integer(munch_underscores(s), 2)]
295
+ end
296
+
297
+ def munch_underscores(s)
298
+ s.chomp('_').squeeze('_')
299
+ end
300
+
301
+ def convert_escapes(string)
302
+ string.gsub(/\\[^u]/) do |m|
303
+ case m
304
+ when '\n' then "\n"
305
+ when '\r' then "\r"
306
+ when '\t' then "\t"
307
+ when '\\\\' then "\\"
308
+ when '\"' then "\""
309
+ when '\b' then "\b"
310
+ when '\f' then "\f"
311
+ else raise Error, "Unexpected escape #{m.inspect}"
312
+ end
313
+ end.gsub(/\\u\{[0-9a-fA-F]{0,6}\}/) do |m|
314
+ i = Integer(m[3..-2], 16)
315
+ if i < 0 || i > 0x10FFFF
316
+ raise Error, "Invalid code point #{u}"
317
+ end
318
+ i.chr(Encoding::UTF_8)
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,70 @@
1
+ module KDL
2
+ class Value
3
+ attr_reader :value
4
+
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def to_s
10
+ value.to_s
11
+ end
12
+ alias inspect to_s
13
+
14
+ class Int < Value
15
+ def ==(other)
16
+ other.is_a?(Int) && value == other.value
17
+ end
18
+ end
19
+
20
+ class Float < Value
21
+ def ==(other)
22
+ other.is_a?(Float) && value == other.value
23
+ end
24
+ end
25
+
26
+ class Boolean < Value
27
+ def ==(other)
28
+ other.is_a?(Boolean) && value == other.value
29
+ end
30
+ end
31
+
32
+ class String < Value
33
+ def to_s
34
+ value.inspect
35
+ end
36
+ alias inspect to_s
37
+
38
+ def ==(other)
39
+ other.is_a?(String) && value == other.value
40
+ end
41
+ end
42
+
43
+ class NullImpl < Value
44
+ def initialize
45
+ super(nil)
46
+ end
47
+
48
+ def to_s
49
+ "null"
50
+ end
51
+ alias inspect to_s
52
+
53
+ def ==(other)
54
+ other.is_a?(NullImpl)
55
+ end
56
+ end
57
+ Null = NullImpl.new
58
+
59
+ def self.from(value)
60
+ case value
61
+ when ::String then String.new(value)
62
+ when Integer then Int.new(value)
63
+ when ::Float then Float.new(value)
64
+ when TrueClass, FalseClass then Boolean.new(value)
65
+ when NilClass then Null
66
+ else raise Error("Unsupported value type: #{value.class}")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module KDL
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kdl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Smith
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: racc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ description: Ruby implementation of the KDL Document Language Spec
28
+ email:
29
+ - danini@hey.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/ruby.yml"
35
+ - ".gitignore"
36
+ - ".travis.yml"
37
+ - CODE_OF_CONDUCT.md
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - bin/console
43
+ - bin/setup
44
+ - kdl.gemspec
45
+ - lib/kdl.rb
46
+ - lib/kdl/document.rb
47
+ - lib/kdl/kdl.yy
48
+ - lib/kdl/node.rb
49
+ - lib/kdl/tokenizer.rb
50
+ - lib/kdl/value.rb
51
+ - lib/kdl/version.rb
52
+ homepage: https://kdl.dev
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://kdl.dev
57
+ source_code_uri: https://github.com/jellymann/kdl-rb
58
+ changelog_uri: https://github.com/jellymann/kdl-rb/releases
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.3.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.1.4
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: KDL Document Language
78
+ test_files: []