kdl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +12 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +105 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kdl.gemspec +29 -0
- data/lib/kdl.rb +12 -0
- data/lib/kdl/document.rb +20 -0
- data/lib/kdl/kdl.yy +68 -0
- data/lib/kdl/node.rb +39 -0
- data/lib/kdl/tokenizer.rb +322 -0
- data/lib/kdl/value.rb +70 -0
- data/lib/kdl/version.rb +3 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -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__)
|
data/bin/setup
ADDED
data/kdl.gemspec
ADDED
@@ -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
|
data/lib/kdl.rb
ADDED
data/lib/kdl/document.rb
ADDED
@@ -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
|
data/lib/kdl/kdl.yy
ADDED
@@ -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
|
data/lib/kdl/node.rb
ADDED
@@ -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
|
data/lib/kdl/value.rb
ADDED
@@ -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
|
data/lib/kdl/version.rb
ADDED
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: []
|