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 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'unlambda/evaluator'
4
+ require 'unlambda/parse'
@@ -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: []