natural_dsl 0.0.2
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/CHANGELOG.md +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/lib/natural_dsl/command.rb +50 -0
- data/lib/natural_dsl/command_runner.rb +38 -0
- data/lib/natural_dsl/lang.rb +38 -0
- data/lib/natural_dsl/primitives.rb +21 -0
- data/lib/natural_dsl/stack.rb +21 -0
- data/lib/natural_dsl/version.rb +3 -0
- data/lib/natural_dsl/vm.rb +65 -0
- data/lib/natural_dsl.rb +7 -0
- data/lib/refinements/string_demodulize.rb +7 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 97856ddab791034315f5feac6ec0bb5648299d2374d2130b49f6601d315a710e
|
4
|
+
data.tar.gz: 1f1fa4301a53ab7cedbad37cd68ebde01fd77df24fedbb033e30e632a8d5e6ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03ead22cfb4c3d7020778bfad056a02c00fc3954a8f99450be770c4bfa79b7344459988bd62037eecac5250921be89afebe2ffb6a1095fb8459e204e846a951b
|
7
|
+
data.tar.gz: 3cb2097e3e5bbf7df1e51cc508978ea5f3a313b161bb23f6aa4a543792f3651f466c0e42a91aa0debc2c22406bd20b9a9ec5710fae7105ad1c0526b115eb5ed7
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 DmitryTsepelev
|
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,50 @@
|
|
1
|
+
# NaturalDSL
|
2
|
+
|
3
|
+
An experimental (and highly likely useless for real–world) *DSL to build a natural–ish DSL language* and write your programs using it. Right to the example:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
lang = NaturalDSL::Lang.define do
|
7
|
+
command :route do
|
8
|
+
keyword :from
|
9
|
+
token
|
10
|
+
keyword :to
|
11
|
+
token
|
12
|
+
value :takes
|
13
|
+
|
14
|
+
execute do |vm, city1, city2, distance|
|
15
|
+
distances = vm.read_variable(:distances) || {}
|
16
|
+
distances[[city1, city2]] = distance
|
17
|
+
vm.assign_variable(:distances, distances)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
command :how do
|
22
|
+
keyword :long
|
23
|
+
keyword :will
|
24
|
+
keyword :it
|
25
|
+
keyword :take
|
26
|
+
keyword :to
|
27
|
+
keyword :get
|
28
|
+
keyword :from
|
29
|
+
token
|
30
|
+
keyword :to
|
31
|
+
token
|
32
|
+
|
33
|
+
execute do |vm, city1, city2|
|
34
|
+
distances = vm.read_variable(:distances) || {}
|
35
|
+
distance = distances[[city1, city2]].value
|
36
|
+
"Travel from #{city1.name} to #{city2.name} takes #{distance} hours"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
result = NaturalDSL::VM.run(lang) do
|
42
|
+
route from london to glasgow takes 22
|
43
|
+
route from paris to prague takes 12
|
44
|
+
how long will it take to get from london to glasgow
|
45
|
+
end
|
46
|
+
|
47
|
+
puts result # => Travel from london to glasgow takes 22 hours
|
48
|
+
```
|
49
|
+
|
50
|
+
Read more about this experiment in by [blog](https://dmitrytsepelev.dev/natural-language-programming-with-ruby).
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "natural_dsl/command_runner"
|
2
|
+
|
3
|
+
module NaturalDSL
|
4
|
+
class Command
|
5
|
+
def self.build(command_name, &block)
|
6
|
+
new(command_name).build(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :name, :execution_block
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(&block)
|
16
|
+
tap { |command| command.instance_eval(&block) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(vm)
|
20
|
+
CommandRunner.new(self, vm).run
|
21
|
+
end
|
22
|
+
|
23
|
+
def expectations
|
24
|
+
@expectations ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def value_method_names
|
28
|
+
@value_method_names ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def token
|
34
|
+
expectations << Primitives::Token
|
35
|
+
end
|
36
|
+
|
37
|
+
def value(method_name = :value)
|
38
|
+
value_method_names << method_name
|
39
|
+
expectations << Primitives::Value
|
40
|
+
end
|
41
|
+
|
42
|
+
def keyword(type)
|
43
|
+
expectations << Primitives::Keyword.new(type)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(&block)
|
47
|
+
@execution_block = block
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "refinements/string_demodulize"
|
2
|
+
|
3
|
+
module NaturalDSL
|
4
|
+
class CommandRunner
|
5
|
+
using StringDemodulize
|
6
|
+
|
7
|
+
class StackNotEmpty < RuntimeError; end
|
8
|
+
|
9
|
+
def initialize(command, vm)
|
10
|
+
@command = command
|
11
|
+
@vm = vm
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
args = @command.expectations.each_with_object([], &method(:check_expectation))
|
16
|
+
|
17
|
+
raise_stack_not_empty_error if @vm.stack.any?
|
18
|
+
|
19
|
+
@command.execution_block.call(@vm, *args)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_expectation(expectation, args)
|
25
|
+
if expectation.is_a?(Primitives::Keyword)
|
26
|
+
@vm.stack.pop_if_keyword(expectation.type)
|
27
|
+
else
|
28
|
+
args << @vm.stack.pop_if(expectation)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def raise_stack_not_empty_error
|
33
|
+
class_names = @vm.stack.map { |primitive| primitive.class.name.demodulize }
|
34
|
+
|
35
|
+
raise StackNotEmpty, "unexpected #{class_names.join(" ")} after #{@command.name}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "natural_dsl/command"
|
2
|
+
|
3
|
+
module NaturalDSL
|
4
|
+
class Lang
|
5
|
+
def self.define(&block)
|
6
|
+
new.tap { |lang| lang.instance_eval(&block) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def command(command_name, &block)
|
10
|
+
command = Command.build(command_name, &block)
|
11
|
+
register_keywords(command)
|
12
|
+
commands[command_name] = command
|
13
|
+
end
|
14
|
+
|
15
|
+
def keywords
|
16
|
+
@keywords ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def commands
|
20
|
+
@commands ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def register_keywords(command)
|
26
|
+
command.expectations.filter(&method(:keyword?)).each(&method(:register_keyword))
|
27
|
+
end
|
28
|
+
|
29
|
+
def keyword?(expectation)
|
30
|
+
expectation.is_a?(Primitives::Keyword)
|
31
|
+
end
|
32
|
+
|
33
|
+
def register_keyword(keyword)
|
34
|
+
return if keywords.include?(keyword.type)
|
35
|
+
keywords << keyword.type
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module NaturalDSL
|
2
|
+
module Primitives
|
3
|
+
Value = Struct.new(:value) do
|
4
|
+
def inspect
|
5
|
+
"Value(#{value})"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Token = Struct.new(:name) do
|
10
|
+
def inspect
|
11
|
+
"Token(#{name})"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Keyword = Struct.new(:type) do
|
16
|
+
def inspect
|
17
|
+
"Keyword(#{type})"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module NaturalDSL
|
2
|
+
class Stack < Array
|
3
|
+
using StringDemodulize
|
4
|
+
|
5
|
+
def pop_if(expected_class)
|
6
|
+
return pop if last.is_a?(expected_class)
|
7
|
+
|
8
|
+
error_reason = empty? ? "stack was empty" : "got #{last.class.name.demodulize}"
|
9
|
+
raise "Expected #{expected_class.name.demodulize} but #{error_reason}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def pop_if_keyword(keyword_type)
|
13
|
+
pop_if(Primitives::Keyword).tap do |keyword|
|
14
|
+
next if keyword.type == keyword_type
|
15
|
+
|
16
|
+
push(keyword)
|
17
|
+
raise "Expected #{keyword_type} but got #{keyword.type}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "natural_dsl/stack"
|
2
|
+
|
3
|
+
module NaturalDSL
|
4
|
+
class VM
|
5
|
+
class << self
|
6
|
+
def build(lang)
|
7
|
+
lang.commands.each do |command_name, command|
|
8
|
+
define_command(lang, command_name, command)
|
9
|
+
end
|
10
|
+
|
11
|
+
new(lang)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(lang, &block)
|
15
|
+
build(lang).run(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def define_command(lang, command_name, command)
|
21
|
+
define_method(command_name) { |*| command.run(self) }
|
22
|
+
|
23
|
+
command.value_method_names.each do |value_method_name|
|
24
|
+
define_method(value_method_name) do |value|
|
25
|
+
@stack << NaturalDSL::Primitives::Value.new(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :variables, :stack
|
32
|
+
|
33
|
+
def initialize(lang)
|
34
|
+
@lang = lang
|
35
|
+
@variables = {}
|
36
|
+
@stack = NaturalDSL::Stack.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def run(&block)
|
40
|
+
instance_eval(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def assign_variable(token, value)
|
44
|
+
@variables[token.name] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def read_variable(token)
|
48
|
+
@variables[token.name]
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(unknown, *args, &block)
|
52
|
+
klass = if @lang.keywords.include?(unknown)
|
53
|
+
NaturalDSL::Primitives::Keyword
|
54
|
+
else
|
55
|
+
NaturalDSL::Primitives::Token
|
56
|
+
end
|
57
|
+
|
58
|
+
@stack << klass.new(unknown)
|
59
|
+
end
|
60
|
+
|
61
|
+
def respond_to_missing?(*)
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/natural_dsl.rb
ADDED
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: natural_dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- DmitryTsepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- dmitry.a.tsepelev@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/natural_dsl.rb
|
24
|
+
- lib/natural_dsl/command.rb
|
25
|
+
- lib/natural_dsl/command_runner.rb
|
26
|
+
- lib/natural_dsl/lang.rb
|
27
|
+
- lib/natural_dsl/primitives.rb
|
28
|
+
- lib/natural_dsl/stack.rb
|
29
|
+
- lib/natural_dsl/version.rb
|
30
|
+
- lib/natural_dsl/vm.rb
|
31
|
+
- lib/refinements/string_demodulize.rb
|
32
|
+
homepage: https://github.com/DmitryTsepelev/natural_dsl
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata:
|
36
|
+
bug_tracker_uri: https://github.com/DmitryTsepelev/natural_dsl/issues
|
37
|
+
changelog_uri: https://github.com/DmitryTsepelev/natural_dsl/blob/master/CHANGELOG.md
|
38
|
+
documentation_uri: https://github.com/DmitryTsepelev/natural_dsl/blob/master/README.md
|
39
|
+
homepage_uri: https://github.com/DmitryTsepelev/natural_dsl
|
40
|
+
source_code_uri: https://github.com/DmitryTsepelev/natural_dsl
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 3.1.0
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubygems_version: 3.3.3
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: An experiment of building natural language DSLs in Ruby
|
60
|
+
test_files: []
|