lyambda_gem 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +1 -0
- data/lib/lyambda_gem/abstraction.rb +55 -0
- data/lib/lyambda_gem/application.rb +39 -0
- data/lib/lyambda_gem/parser.rb +67 -0
- data/lib/lyambda_gem/reducer.rb +15 -0
- data/lib/lyambda_gem/term.rb +20 -0
- data/lib/lyambda_gem/variable.rb +40 -0
- data/lib/lyambda_gem/version.rb +5 -0
- data/lib/lyambda_gem.rb +29 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a2537aff07e563e3fa865fffe25a890735610c05275330a6019e3cd7bc7d19b9
|
|
4
|
+
data.tar.gz: bb560d3093386fa5db6b436e917614ebfd343a69b9cffd57f90e8ba67c10952c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a831803d75ec86b06c887b0a1fa991fc5069ef4a6f8c2171ee5146460edc37698aa37a6630497f6bfbb99e475ecccc909884c99b47cfe80bea62143612464fd4
|
|
7
|
+
data.tar.gz: 3b3d4675c504324cc068df3dc8263f5b704174b1b2fbc8d2b36b8c1a7a82c6e9b5a27ec9e8ac465c8b350bddec9743fef69c3f1daab41666602112e7a7995e4f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shatov1van, chokopiku61
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# LymbdaGem
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module LyambdaGem
|
|
2
|
+
#Implementation of an abstraction (lambda function) in the lambda calculus
|
|
3
|
+
class Abstraction < Term
|
|
4
|
+
attr_reader :parameter, :body
|
|
5
|
+
|
|
6
|
+
def initialize(parameter, body)
|
|
7
|
+
@parameter = parameter
|
|
8
|
+
@body = body
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#Список свободных переменных
|
|
12
|
+
def free_variables
|
|
13
|
+
@body.free_variables - Set.new([@parameter])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#Получение новой переменной относительно терма (для правила 7)
|
|
17
|
+
def fresh_variable(term)
|
|
18
|
+
cnt = 1
|
|
19
|
+
fv = term.free_variables
|
|
20
|
+
while fv.any?{|variable| variable.name == "z#{cnt}"}
|
|
21
|
+
cnt += 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return Variable.new("z#{cnt}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#Подстановка
|
|
28
|
+
def substitute(term, variable)
|
|
29
|
+
#puts "p:#{@parameter} | body:#{@body} | t:#{term} | v:#{variable}"
|
|
30
|
+
return self if variable == @parameter
|
|
31
|
+
|
|
32
|
+
return self if !@body.free_variables.include?(variable)
|
|
33
|
+
|
|
34
|
+
return Abstraction.new(@parameter, @body.substitute(term, variable)) if !term.free_variables.include?(@parameter)
|
|
35
|
+
|
|
36
|
+
new_variable = fresh_variable((Application.new(term, @body)))
|
|
37
|
+
return Abstraction.new(new_variable, @body.substitute(new_variable, @parameter).substitute(term, variable))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reduceable?
|
|
41
|
+
@body.reduceable?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reduce(strategy: :normal_order)
|
|
45
|
+
#puts " abs:#{self.to_s} | r-able: #{reduceable?}"
|
|
46
|
+
return self unless reduceable?
|
|
47
|
+
|
|
48
|
+
return Abstraction.new(@parameter, @body.reduce)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_s
|
|
52
|
+
"(λ#{parameter}.#{body})"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module LyambdaGem
|
|
2
|
+
#Implementation of an application (function application) in the lambda calculus
|
|
3
|
+
class Application < Term
|
|
4
|
+
attr_reader :left, :right
|
|
5
|
+
|
|
6
|
+
def initialize(left, right)
|
|
7
|
+
@left = left
|
|
8
|
+
@right = right
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#Список свободных переменных
|
|
12
|
+
def free_variables
|
|
13
|
+
return @left.free_variables + @right.free_variables
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def substitute(term, variable)
|
|
17
|
+
return Application.new(@left.substitute(term, variable), @right.substitute(term, variable))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reduceable?
|
|
21
|
+
return @left.is_a?(Abstraction) || @left.reduceable? || @right.reduceable?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reduce(strategy: :normal_order)
|
|
25
|
+
#puts " app:#{self.to_s} | r-able: #{reduceable?}"
|
|
26
|
+
return self unless reduceable?
|
|
27
|
+
#puts @left.body.substitute(@right, @left.parameter)
|
|
28
|
+
return @left.body.substitute(@right, @left.parameter) if @left.is_a?(Abstraction)
|
|
29
|
+
|
|
30
|
+
return Application.new(@left.reduce, @right) if @left.reduceable?
|
|
31
|
+
|
|
32
|
+
return Application.new(@left, @right.reduce) if @right.reduceable?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"(#{@left.to_s} #{@right.to_s})"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# lib/lyambda_gem/parser.rb
|
|
2
|
+
module LyambdaGem
|
|
3
|
+
class ParseError < StandardError; end
|
|
4
|
+
|
|
5
|
+
class Parser
|
|
6
|
+
def initialize(input)
|
|
7
|
+
@tokens = input.gsub('λ', '\\') # унифицируем
|
|
8
|
+
.scan(/[()\\\.]|[^\s()\\\.]+/)
|
|
9
|
+
@pos = 0
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse
|
|
13
|
+
term = parse_expression
|
|
14
|
+
raise ParseError, "Unexpected tokens after end: #{@tokens[@pos..].join(' ')}" unless @pos == @tokens.size
|
|
15
|
+
term
|
|
16
|
+
rescue ParseError => e
|
|
17
|
+
raise e
|
|
18
|
+
rescue => e
|
|
19
|
+
raise ParseError, "Invalid syntax: #{e.message}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def current
|
|
25
|
+
@tokens[@pos]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def advance
|
|
29
|
+
@pos += 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_expression
|
|
33
|
+
left = parse_atom
|
|
34
|
+
while @pos < @tokens.size && current != ')' && current != '.'
|
|
35
|
+
# Применение: следующая атомарная – аргумент
|
|
36
|
+
right = parse_atom
|
|
37
|
+
left = Application.new(left, right)
|
|
38
|
+
end
|
|
39
|
+
left
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse_atom
|
|
43
|
+
if current == '('
|
|
44
|
+
advance
|
|
45
|
+
expr = parse_expression
|
|
46
|
+
raise ParseError, "Missing closing ')'" unless current == ')'
|
|
47
|
+
advance
|
|
48
|
+
expr
|
|
49
|
+
elsif current == '\\'
|
|
50
|
+
advance
|
|
51
|
+
param_name = current
|
|
52
|
+
raise ParseError, "Expected variable after '\\'" unless param_name && param_name !~ /[()\\\.]/
|
|
53
|
+
advance
|
|
54
|
+
raise ParseError, "Expected '.' after variable in abstraction" unless current == '.'
|
|
55
|
+
advance
|
|
56
|
+
body = parse_expression
|
|
57
|
+
Abstraction.new(Variable.new(param_name), body)
|
|
58
|
+
else
|
|
59
|
+
# переменная
|
|
60
|
+
name = current
|
|
61
|
+
raise ParseError, "Unexpected token '#{name}'" if name.nil? || name.match?(/[()\\\.]/)
|
|
62
|
+
advance
|
|
63
|
+
Variable.new(name)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module LyambdaGem
|
|
2
|
+
module Reducer
|
|
3
|
+
def self.to_normal(term, strategy: :normal_order, verbose: false)
|
|
4
|
+
step = 0
|
|
5
|
+
puts "##{step} #{term}" if verbose
|
|
6
|
+
while term.reduceable?
|
|
7
|
+
term = term.reduce(strategy: strategy)
|
|
8
|
+
|
|
9
|
+
step += 1
|
|
10
|
+
puts "##{step} #{term}" if verbose
|
|
11
|
+
end
|
|
12
|
+
term
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module LyambdaGem
|
|
2
|
+
#Base abstract class for all terms in the lambda calculus (variables, abstractions, applications)
|
|
3
|
+
class Term
|
|
4
|
+
def free_variables
|
|
5
|
+
raise NotImplementedError, "Subclasses must implement the free_variables method"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def substitute(term, variable)
|
|
9
|
+
raise NotImplementedError, "Subclasses must implement the substitute method"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def reduceable?
|
|
13
|
+
raise NotImplementedError, "Subclasses must implement the reduceable? method"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reduce(strategy: :normal_order)
|
|
17
|
+
raise NotImplementedError, "Subclasses must implement the reduce method"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module LyambdaGem
|
|
2
|
+
#Implementation of a variable in the lambda calculus
|
|
3
|
+
class Variable < Term
|
|
4
|
+
attr_reader :name
|
|
5
|
+
|
|
6
|
+
def initialize(name)
|
|
7
|
+
@name = name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
#Список свободных переменных
|
|
11
|
+
def free_variables
|
|
12
|
+
return Set.new([self])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#Подстановка
|
|
16
|
+
def substitute(term, variable)
|
|
17
|
+
self == variable ? term : self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reduceable?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reduce(strategy: :normal_order)
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
name.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def inspect
|
|
33
|
+
@name.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
other.is_a?(Variable) && @name == other.name
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/lyambda_gem.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lyambda_gem/version"
|
|
4
|
+
require_relative "lyambda_gem/version"
|
|
5
|
+
require_relative "lyambda_gem/term"
|
|
6
|
+
require_relative "lyambda_gem/variable"
|
|
7
|
+
require_relative "lyambda_gem/abstraction"
|
|
8
|
+
require_relative "lyambda_gem/application"
|
|
9
|
+
require_relative "lyambda_gem/reducer"
|
|
10
|
+
|
|
11
|
+
module LyambdaGem
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
extend self
|
|
15
|
+
|
|
16
|
+
def var(name)
|
|
17
|
+
Variable.new(name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def abs(param, body)
|
|
21
|
+
Abstraction.new(param, body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def app(left, right)
|
|
25
|
+
Application.new(left, right)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module_function :var, :abs, :app
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lyambda_gem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- shatov1van
|
|
8
|
+
- chokopiku61
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A Ruby gem for parsing, reducing, and manipulating lambda calculus expressions
|
|
14
|
+
email:
|
|
15
|
+
- ivanshatov13@gmail.com
|
|
16
|
+
- tim.23tim23.23@gmail.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/lyambda_gem.rb
|
|
24
|
+
- lib/lyambda_gem/abstraction.rb
|
|
25
|
+
- lib/lyambda_gem/application.rb
|
|
26
|
+
- lib/lyambda_gem/parser.rb
|
|
27
|
+
- lib/lyambda_gem/reducer.rb
|
|
28
|
+
- lib/lyambda_gem/term.rb
|
|
29
|
+
- lib/lyambda_gem/variable.rb
|
|
30
|
+
- lib/lyambda_gem/version.rb
|
|
31
|
+
homepage: https://github.com/shatov1van/LymbdaGem
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
homepage_uri: https://github.com/shatov1van/LymbdaGem
|
|
36
|
+
source_code_uri: https://github.com/shatov1van/LymbdaGem
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: 3.1.0
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
requirements: []
|
|
51
|
+
rubygems_version: 4.0.6
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Lambda calculus interpreter and reducer
|
|
54
|
+
test_files: []
|