unlambda 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/README.md +18 -0
- data/Rakefile +14 -0
- data/bin/unli +19 -0
- data/lib/unlambda/evaluator.rb +127 -0
- data/lib/unlambda/parse.rb +32 -0
- data/lib/unlambda.rb +4 -0
- data/test/unlambda_test.rb +51 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6f05d51f8c72595da1e30bb205e7c9318fc14736c4800e208360db55d7f4416a
|
4
|
+
data.tar.gz: 6635ade0872112355d474a81fb59de0db814de81af0624bc2e99775e40815857
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1703d2d5e34078fabc638355e0faeab28eec9742c739f9bee654e545b81d3f96ab97decac583c98d103d86ef718980f2dbae12b9391699d84f67e401b561c066
|
7
|
+
data.tar.gz: 56ad22bbba5191cd9307caa2e10a040e9c69dfb8c2ee0159a1daa7486ada7c3cccea53333c65f9869268e9dc4229fa1e91bd2be87d231a7c424b37942272c5f3
|
data/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# unlambda-ruby
|
2
|
+
|
3
|
+
An Unlambda interpreter.
|
4
|
+
|
5
|
+
## Run
|
6
|
+
|
7
|
+
```
|
8
|
+
rake install
|
9
|
+
curl -OL https://users.math.cas.cz/~jerabek/ptakoviny/unlambda/sierp.unl
|
10
|
+
./bin/unli sierp.unl
|
11
|
+
```
|
12
|
+
|
13
|
+
## References
|
14
|
+
|
15
|
+
- http://www.madore.org/~david/programs/unlambda/
|
16
|
+
- http://users.math.cas.cz/~jerabek/ptakoviny/index.html#unl
|
17
|
+
- http://people.cs.uchicago.edu/~odonnell/Teacher/Lectures/Formal_Organization_of_Knowledge/Examples/combinator_calculus/
|
18
|
+
- http://practical-scheme.net/docs/cont-j.html
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
task default: ['test']
|
8
|
+
|
9
|
+
Rake::TestTask.new('test') do |t|
|
10
|
+
t.description = 'Run tests'
|
11
|
+
t.libs << ['lib']
|
12
|
+
t.pattern = 'test/*_test.rb'
|
13
|
+
t.warning = false
|
14
|
+
end
|
data/bin/unli
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2020. All rights reserved.
|
4
|
+
#
|
5
|
+
# frozen_string_literal: true
|
6
|
+
|
7
|
+
require 'unlambda'
|
8
|
+
|
9
|
+
def run(filename, cin = proc { $stdin.getc }, cout = proc { |x| print x })
|
10
|
+
stack = File.open(filename, 'r') { |io| Unlambda.parse io }
|
11
|
+
Unlambda::Evaluator.new([stack, 'E'], cin, cout).eval
|
12
|
+
end
|
13
|
+
|
14
|
+
if ARGV.length != 1
|
15
|
+
warn 'usage: unli <PATH/TO/FILE>'
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
run ARGV[0]
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unlambda
|
4
|
+
class Evaluator
|
5
|
+
def initialize(stk, cin, cout, debug: false)
|
6
|
+
@stack = stk
|
7
|
+
@rest = []
|
8
|
+
@in = cin
|
9
|
+
@out = cout
|
10
|
+
@cur_ch = nil # current char
|
11
|
+
|
12
|
+
@debug = debug
|
13
|
+
debug_log 'initialize'
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval
|
17
|
+
until @stack.empty?
|
18
|
+
while (intr_op = @stack.pop)
|
19
|
+
case intr_op
|
20
|
+
when 'E'
|
21
|
+
@rest = @stack.pop
|
22
|
+
@rest[0] == '`' && @stack += [@rest[2], 'F', @rest[1], 'E']
|
23
|
+
break
|
24
|
+
when 'F'
|
25
|
+
if @rest[0] == 'd'
|
26
|
+
# delay: の場合、式を評価せずプロミスへ
|
27
|
+
@rest = ['D', @stack.pop]
|
28
|
+
else
|
29
|
+
@stack += [@rest, 'A', @stack.pop, 'E']
|
30
|
+
end
|
31
|
+
break
|
32
|
+
else # when `A'
|
33
|
+
apply
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def apply
|
42
|
+
while (a = @stack.pop)
|
43
|
+
debug_log 'apply'
|
44
|
+
|
45
|
+
case a[0]
|
46
|
+
when 's'
|
47
|
+
# subsitution: 関数(\X,Y,Z -> XZ, YZ)に引数を適用し、
|
48
|
+
# 関数(\Y,Z -> XZ,YZ)を返す
|
49
|
+
@rest = ['S', @rest]
|
50
|
+
break
|
51
|
+
when 'S'
|
52
|
+
# subsitution first partial: 関数(\Y,Z -> XZ,YZ)に引数を適用し、
|
53
|
+
# 関数(\Z -> XZ,YZ)を返す
|
54
|
+
@rest = ['T', a[1], @rest]
|
55
|
+
break
|
56
|
+
when 'T'
|
57
|
+
# subsitution application: 関数(\Z -> XZ,YZ) に引数を与えた式を返す
|
58
|
+
@stack += [['`', ['`', a[1], @rest], ['`', a[2], @rest]], 'E']
|
59
|
+
break
|
60
|
+
when 'k'
|
61
|
+
# constant generator: 関数(\X,Y -> X)に引数を適用し、関数(\Y -> X)を返す
|
62
|
+
@rest = ['K', @rest]
|
63
|
+
break
|
64
|
+
when 'K'
|
65
|
+
# constant function: 関数(\Y -> X)に引数を与えた値を返す
|
66
|
+
@rest = a[1]
|
67
|
+
break
|
68
|
+
when 'i'
|
69
|
+
# identity: 恒等関数
|
70
|
+
break
|
71
|
+
when 'v'
|
72
|
+
# void: 自分自身(v)をそのまま返す
|
73
|
+
@rest = a
|
74
|
+
break
|
75
|
+
when 'e'
|
76
|
+
# exit: 直ちに終了、ただし仕様には引数をとり評価した結果をプログラムの
|
77
|
+
# exit-status にする? と記載がある
|
78
|
+
@stack = []
|
79
|
+
break
|
80
|
+
when 'D'
|
81
|
+
# promise: `d' によって作成されたプロミス(評価されていない引数)に
|
82
|
+
# D の引数を与えた式を返す
|
83
|
+
@stack += [['`', a[1], @rest], 'E']
|
84
|
+
break
|
85
|
+
when 'c'
|
86
|
+
# call/cc: 現在の計算(継続)を引数 X の引数として与えた式を返す
|
87
|
+
@stack += [['`', @rest, ['C', [@stack]]], 'E']
|
88
|
+
break
|
89
|
+
when 'C'
|
90
|
+
# cont: 継続を取り出す
|
91
|
+
@stack = a[1].pop
|
92
|
+
break
|
93
|
+
when '.'
|
94
|
+
# print: 引数を出力し、その値をそのまま返す
|
95
|
+
@out.call(a[1])
|
96
|
+
break
|
97
|
+
when '@'
|
98
|
+
# read: 入力から一文字受取り current character(@cur_ch) にセット
|
99
|
+
# 成功した場合は `Xi' 失敗の場合 `Xv' を返す(このとき X は @ の引数)
|
100
|
+
@stack += [['`', @rest, [(@cur_ch = @in.call) ? 'i' : 'v']], 'E']
|
101
|
+
break
|
102
|
+
when '?'
|
103
|
+
# compare character read: current character(@cur_ch) がセットされ、
|
104
|
+
# その値が ? の次に続く文字(X)と等しい場合 `Yi' を返す、
|
105
|
+
# 等しくない場合 `Yv' を返す (このとき Y は ?X の引数)
|
106
|
+
@stack += [['`', @rest, [a[1] == @cur_ch ? 'i' : 'v']], 'E']
|
107
|
+
break
|
108
|
+
when '|'
|
109
|
+
# reprint character read: current character(@cur_ch) がセットされている
|
110
|
+
# 場合、`X.@cur_ch' を返す、そうでない場合 `Xv' を返す
|
111
|
+
# (このとき X は | の引数)
|
112
|
+
@stack += [['`', @rest, @cur_ch ? ['.', @cur_ch] : ['v']], 'E']
|
113
|
+
break
|
114
|
+
else
|
115
|
+
raise "`#{a[0]}' -- WTF?"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def debug_log(name)
|
121
|
+
return unless @debug
|
122
|
+
|
123
|
+
$stderr.print "DEBUG: #{name}: "
|
124
|
+
warn @stack
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unlambda
|
4
|
+
def self.parse(io)
|
5
|
+
while (cc = io.getc)
|
6
|
+
c = cc.downcase
|
7
|
+
|
8
|
+
case c
|
9
|
+
when '.', '?'
|
10
|
+
# `.', `?' に続くEOFでない文字は受け入れる
|
11
|
+
if (nc = io.getc)
|
12
|
+
return [c, nc]
|
13
|
+
else
|
14
|
+
raise "unexpected EOF after `#{c}'"
|
15
|
+
end
|
16
|
+
when '`'
|
17
|
+
return [c, parse(io), parse(io)]
|
18
|
+
when /^[skivcde@|]/
|
19
|
+
return [c]
|
20
|
+
when 'r'
|
21
|
+
return ['.', "\n"]
|
22
|
+
when /\s/
|
23
|
+
next
|
24
|
+
when '#'
|
25
|
+
io.readline
|
26
|
+
next
|
27
|
+
else
|
28
|
+
raise "unknown character `#{c}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/unlambda.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'stringio'
|
5
|
+
require 'unlambda'
|
6
|
+
|
7
|
+
class TestUnlambda < Test::Unit::TestCase
|
8
|
+
def scenario(src, input = [])
|
9
|
+
stack = Unlambda.parse StringIO.new(src)
|
10
|
+
output = String.new('')
|
11
|
+
Unlambda::Evaluator.new(
|
12
|
+
[stack, 'E'],
|
13
|
+
proc { input.pop },
|
14
|
+
proc { |x| output << x },
|
15
|
+
debug: false
|
16
|
+
).eval
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_write_space_comment
|
21
|
+
src = <<~"UNL"
|
22
|
+
# Hello world
|
23
|
+
`r``` ````` ```.H
|
24
|
+
.e .l
|
25
|
+
.l
|
26
|
+
.o. .w .o .r
|
27
|
+
.l .di
|
28
|
+
UNL
|
29
|
+
assert_equal "Hello world\n", scenario(src)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_read_currentchar
|
33
|
+
assert_equal 'X', scenario('`@.X', ['_'])
|
34
|
+
assert_equal 'X', scenario('````@i.Xi```?Yi.Zi', ['X'])
|
35
|
+
assert_equal 'XZ', scenario('````@i.Xi```?Yi.Zi', ['Y'])
|
36
|
+
assert_equal 'XYZ', scenario('````@i.Xi``|.Yi', ['Z'])
|
37
|
+
assert_equal 'Y', scenario('````@i.Xi``|.Yi', [])
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_s_k_i
|
41
|
+
assert_equal 'XYZZ', scenario('````s.X.Y.Zi')
|
42
|
+
assert_equal 'X', scenario('```k.X.Yi')
|
43
|
+
assert_equal 'X', scenario('`.Xi')
|
44
|
+
assert_equal 'XY', scenario('``````s``s`ks``s`kk``s`ks``s`k`sik`kk.X.Yii')
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_c_d
|
48
|
+
assert_equal 'XX', scenario('``cd`.Xi')
|
49
|
+
assert_equal 'XYZ', scenario('``ci`d`c`@|', ['XYZ'])
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unlambda
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yu Ishii
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
email:
|
13
|
+
- asbish@hobbyhorsemodels.com
|
14
|
+
executables:
|
15
|
+
- unli
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- Rakefile
|
21
|
+
- bin/unli
|
22
|
+
- lib/unlambda.rb
|
23
|
+
- lib/unlambda/evaluator.rb
|
24
|
+
- lib/unlambda/parse.rb
|
25
|
+
- test/unlambda_test.rb
|
26
|
+
homepage: https://git.sr.ht/~asbish/unlambda-ruby
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.2'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.6.9
|
45
|
+
specification_version: 4
|
46
|
+
summary: An Unlambda interpreter
|
47
|
+
test_files: []
|