rquark 0.1.1

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
+ SHA1:
3
+ metadata.gz: b708c25549f1eec79d6d5b856110b96e0e2565fc
4
+ data.tar.gz: c57594b3c62fa984da21a8e52cf87d75356efed5
5
+ SHA512:
6
+ metadata.gz: 89954af0306b8dbea34f9ea97634556d4304644d6c2db63f91bac81f94462b3894611609413b209124359e88f52c28587da7fe741da5679e517ced5725bf1456
7
+ data.tar.gz: c956ac76e8b1c68b35edb6411ad5dc58d45ae6276da334ffbf8343ba16ac47429c8cee8f52e5eea324bb474bab299e247fa5f76e261d13a3cd79959dc45ba272
data/bin/rquark ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/qrun'
4
+
5
+
6
+ # setup base vm
7
+ base_vm = QVM.new([], [], {})
8
+ unless ARGV.include? '--only-core'
9
+ base_vm = qrun(File.read File.expand_path('../../lib/prelude.qrk', __FILE__))
10
+ end
11
+
12
+ # get script filenames
13
+ script_names = ARGV.select { |a| a[0] != '-' }
14
+
15
+ # run either REPL or scripts
16
+ if script_names.empty?
17
+ qrepl base_vm
18
+ else
19
+ script_names.each do |s|
20
+ qrun(File.read(s), base_vm.stack.dup, base_vm.bindings.dup)
21
+ end
22
+ end
data/lib/prelude.qrk ADDED
@@ -0,0 +1,118 @@
1
+
2
+ [ x | ] :drop def
3
+
4
+ [ drop ] :comment def
5
+
6
+
7
+ "## Handy Macros ##" comment
8
+
9
+ [ q n | [ q match ] n def ] :mdef def
10
+
11
+ [ call ] :; def
12
+
13
+
14
+ "## Stack Manipulation Operators ##" comment
15
+
16
+ [ x | x x ] :dup def
17
+
18
+ [ x y | y x ] :swap def
19
+
20
+ [ x y | y ] :nip def
21
+
22
+ [ x y | x y x ] :dipdup def
23
+
24
+ [ x y z | z x y ] :rot def
25
+
26
+ [ x y z | y z x ] :-rot def
27
+
28
+ [[ x | clear ]] :clear mdef
29
+
30
+
31
+ "## Combinators ##" comment
32
+
33
+ [ x y f | x f call y ] :dip def
34
+
35
+ [ x y z f | x y f call z ] :double-dip def
36
+
37
+ [ w x y z f | w x y f call z ] :triple-dip def
38
+
39
+ [ x fa fb | x fa call x fb call ] :bi def
40
+
41
+ [ x fa fb fc | x fa call a fb call x fc call ] :tri def
42
+
43
+ [ q fa fb | q call fa call q call fb call ] :q-bi def
44
+
45
+ [ x y fx fy | x fx call y fy call ] :bi-call def
46
+
47
+ [ x y z fx fy fz | x fx call y fy call z fz call ] :tri-call def
48
+
49
+
50
+ "## Numeric Operators ##" comment
51
+
52
+ [ -1 * + ] :- def
53
+
54
+ [ 1 + ] :++ def
55
+
56
+ [ -1 + ] :-- def
57
+
58
+ [ 1 swap [ [ dup ] dip * ] times-do swap drop ] :^ def
59
+
60
+ [ swap < ] :> def
61
+
62
+ [ x y | [ x y ] [<] [=] q-bi or ] :<= def
63
+
64
+ [ x y | [ x y ] [>] [=] q-bi or ] :>= def
65
+
66
+
67
+ "## String Operators ##" comment
68
+
69
+ [ "" [ weld ] fold ] :multiWeld def
70
+
71
+
72
+ "## Boolean Operators ##" comment
73
+
74
+ [ x x | :true ] := def
75
+
76
+ [ :true :true | :true ] :and def
77
+
78
+ [[ :true x | :true ] [ x :true | :true ]] :or mdef
79
+
80
+ [[ :true | :nil ] [ x | :true ]] :not mdef
81
+
82
+ [ = not ] :!= def
83
+
84
+ [ x y | [ x y ] [!=] [or] q-bi and ] :xor mdef
85
+
86
+
87
+ "## Control Flow ##" comment
88
+
89
+ [[ :true x | x call ] [ z x | ]] :if mdef
90
+
91
+ [[ :true x y | x call ] [ z x y | y call ]] :if-else mdef
92
+
93
+ [[ 0 f | ] [ n f | f call n 1 - f times-do ]] :times-do mdef
94
+
95
+ [ p f | p call not [ f call p f while ] if ] :while def
96
+
97
+
98
+ "## Quote Functions ##" comment
99
+
100
+ [ @- drop ] :pattern def
101
+
102
+ [ @- nip ] :body def
103
+
104
+ [ @- swap >> [ swap @+ ] dip ] :@> def
105
+
106
+ [ q x | q @- swap x << swap @+ ] :<@ def
107
+
108
+ [[ [] x f | x ] [ xs x f | xs >> x f call f fold ]] :fold mdef
109
+
110
+ [[ [] f | [] ] [ xs f | xs >> [ f map ] dip f call << ]] :map mdef
111
+
112
+ [ [] [ swap << ] fold ] :reverse def
113
+
114
+ [ reverse [ swap << ] fold reverse ] :concat def
115
+
116
+ [ [] rot [ dup [ << ] dip ++ ] times-do drop ] :range def
117
+
118
+ [ 1 swap range ] :iota def
data/lib/qeval.rb ADDED
@@ -0,0 +1,178 @@
1
+ require 'qtypes'
2
+
3
+ ## Boilerplate ##
4
+
5
+ QuarkError = Class.new(RuntimeError)
6
+
7
+ $core_func = {}
8
+ $core_func_type = {}
9
+
10
+ def def_cf(name, type_sig, &code)
11
+ $core_func_type[name] = type_sig
12
+ $core_func[name] = code
13
+ end
14
+
15
+ def apply_core_func(name, vm, eval_func)
16
+ eq, expected, got = type_check(vm.stack, $core_func_type[name])
17
+ raise(QuarkError, "Type error with function #{name}\n expected a stack of #{expected}\n but got #{got}") if not eq
18
+ $core_func[name].call(vm, eval_func)
19
+ end
20
+
21
+ def apply_runtime_func(name, vm)
22
+ quote = vm.bindings[name]
23
+ vm.program.unshift(*[quote, QAtom.new('call')])
24
+ end
25
+
26
+ def pattern_match(stack_args, args)
27
+ return false if stack_args.length < args.length
28
+ return {} if args.length == 0
29
+ bindings = {}
30
+ args.zip(stack_args).each do |a, s|
31
+ if a.is_a? QAtom
32
+ return false if (bindings.has_key? a.val) && (bindings[a.val] != s)
33
+ bindings[a.val] = s
34
+ else
35
+ return false if a != s
36
+ end
37
+ end
38
+ return bindings
39
+ end
40
+
41
+ def deep_clone x
42
+ Marshal.load(Marshal.dump(x))
43
+ end
44
+
45
+
46
+ ## Quark Core Functions ##
47
+
48
+ def_cf('+', [:Num, :Num]) do |vm|
49
+ vm.stack.push QNum.new(vm.stack.pop.val + vm.stack.pop.val)
50
+ end
51
+
52
+ def_cf('*', [:Num, :Num]) do |vm|
53
+ vm.stack.push QNum.new(vm.stack.pop.val * vm.stack.pop.val)
54
+ end
55
+
56
+ def_cf('/', [:Num, :Num]) do |vm|
57
+ vm.stack.push QNum.new(vm.stack.pop.val / vm.stack.pop.val)
58
+ end
59
+
60
+ def_cf('<', [:Num, :Num]) do |vm|
61
+ smaller_than = vm.stack.pop.val > vm.stack.pop.val
62
+ vm.stack.push QSym.new(smaller_than ? 'true' : 'nil')
63
+ end
64
+
65
+ def_cf('print', [:Str]) do |vm|
66
+ print vm.stack.pop.val
67
+ end
68
+
69
+ def_cf('.', []) do |vm|
70
+ puts vm.stack.join(' ')
71
+ end
72
+
73
+ def_cf('>>', [:Quote]) do |vm|
74
+ vm.stack.push vm.stack.last.pop
75
+ end
76
+
77
+ def_cf('<<', [:Quote, :Any]) do |vm|
78
+ vm.stack[-2].push vm.stack.pop
79
+ end
80
+
81
+ def_cf('@+', [:Quote, :Quote]) do |vm|
82
+ q1, q2 = vm.stack.pop(2)
83
+ vm.stack.push QQuote.new q1.body, q2.body
84
+ end
85
+
86
+ def_cf('@-', [:Quote]) do |vm|
87
+ q = vm.stack.pop
88
+ vm.stack.push QQuote.new [], q.pattern
89
+ vm.stack.push QQuote.new [], q.body
90
+ end
91
+
92
+ def_cf('show', [:Any]) do |vm|
93
+ vm.stack.push QStr.new(vm.stack.pop.to_s)
94
+ end
95
+
96
+ def_cf('call', [:Quote]) do |vm|
97
+ quote = vm.stack.pop
98
+ args = vm.stack.pop(quote.pattern.length)
99
+ if bindings=pattern_match(args, quote.pattern)
100
+ vm.program.unshift(*quote.bind(bindings).body)
101
+ else vm.stack.push QSym.new('nil') end
102
+ end
103
+
104
+ def_cf('match', [:Quote]) do |vm|
105
+ quotes = vm.stack.pop.body
106
+ quotes.each do |q|
107
+ if bindings=pattern_match(vm.stack.last(q.pattern.length), q.pattern)
108
+ vm.stack.pop(q.pattern.length)
109
+ vm.program.unshift(*q.bind(bindings).body)
110
+ break
111
+ end
112
+ end
113
+ end
114
+
115
+ def_cf('chars', [:Str]) do |vm|
116
+ chars = vm.stack.pop.val.chars.map { |c| QStr.new c }
117
+ vm.stack.push QQuote.new([], chars)
118
+ end
119
+
120
+ def_cf('weld', [:Str, :Str]) do |vm|
121
+ string_a = vm.stack.pop.val
122
+ string_b = vm.stack.pop.val
123
+ vm.stack.push QStr.new(string_b + string_a)
124
+ end
125
+
126
+ def_cf('def', [:Quote, :Sym]) do |vm|
127
+ name = vm.stack.pop.val
128
+ quote = vm.stack.pop
129
+ vm.bindings[name] = quote
130
+ end
131
+
132
+ def_cf('eval', [:Str]) do |vm, eval_func|
133
+ begin
134
+ eval_str = vm.stack.pop.val
135
+ vm2 = eval_func.call(eval_str, vm.stack.dup, vm.bindings.dup)
136
+ vm.stack, vm.bindings = vm2.stack, vm2.bindings
137
+ vm.stack.push QSym.new('ok')
138
+ rescue
139
+ vm.stack.push QSym.new('not-ok')
140
+ end
141
+ end
142
+
143
+ def_cf('type', [:Any]) do |vm|
144
+ type = type_to_qitem vm.stack.pop.qtype
145
+ vm.stack.push type
146
+ end
147
+
148
+ def_cf('load', [:Str]) do |vm|
149
+ begin
150
+ vm.stack.push QStr.new(File.read(vm.stack.pop.val))
151
+ vm.stack.push QSym.new 'ok'
152
+ rescue
153
+ vm.stack.push QSym.new 'not-ok'
154
+ end
155
+ end
156
+
157
+ def_cf('cmd', [:Str]) do |vm|
158
+ begin
159
+ vm.stack.push QStr.new(IO.popen(vm.stack.pop.val).read)
160
+ vm.stack.push QSym.new 'ok'
161
+ rescue
162
+ vm.stack.push QSym.new 'not-ok'
163
+ end
164
+ end
165
+
166
+ def_cf('write', [:Str, :Str]) do |vm|
167
+ begin
168
+ filename, content = vm.stack.pop.val, vm.stack.pop.val
169
+ File.open(filename, 'w+') { |f| f << content }
170
+ vm.stack.push QSym.new 'ok'
171
+ rescue
172
+ vm.stack.push QSym.new 'not-ok'
173
+ end
174
+ end
175
+
176
+ def_cf('exit', []) do |vm|
177
+ exit
178
+ end
data/lib/qparse.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'parslet'
2
+ require 'qtypes'
3
+
4
+ ## Parsing ##
5
+
6
+ class QuarkParse < Parslet::Parser
7
+ rule(:integer) { match('[0-9]').repeat(1) }
8
+ rule(:num) { (str("-").maybe >> integer >> (str('.') >> integer).maybe).as(:num) >> sep? } # Number
9
+ rule(:atom) { match('[^0-9\[\]|:"\' \n\t]').repeat(1).as(:atom) >> sep? } # Function or Variable
10
+ rule(:sym) { str(':') >> match('[^0-9\[\]|:"\' \n\t]').repeat(1).as(:sym) >> sep? } # Symbol
11
+ rule(:stringA) { str("'") >> match("[^']").repeat(0).as(:string) >> str("'") }
12
+ rule(:stringB) { str('"') >> match('[^"]').repeat(0).as(:string) >> str('"') }
13
+ rule(:string) { (stringA | stringB) >> sep? } # String
14
+ rule(:quote) { # Quote
15
+ str("[") >> sep? >>
16
+ (qexpr >> sep? >> str("|") >> sep?).maybe.as(:pattern) >>
17
+ qexpr.as(:body) >> sep? >> str("]") >> sep?
18
+ }
19
+ rule(:qexpr) { sep? >> ((num | atom | sym | string | quote)).repeat(0) }
20
+ rule(:sep) { (match('\s') | match('\n') | match('\t')).repeat(1) }
21
+ rule(:sep?) { sep.maybe }
22
+ root :qexpr
23
+ end
24
+
25
+ class QuarkTransform < Parslet::Transform
26
+ rule(:num => simple(:x)) { QNum.new(x.to_f) }
27
+ rule(:atom => simple(:x)) { QAtom.new(x.to_s) }
28
+ rule(:sym => simple(:x)) { QSym.new(x.to_s) }
29
+ rule(:string => simple(:x)) { QStr.new(x.to_s) }
30
+ rule(:string => sequence(:x)) { QStr.new('') }
31
+ rule(:pattern => sequence(:a), :body => sequence(:b)) { QQuote.new(a, b) }
32
+ rule(:pattern => simple(:a), :body => sequence(:b)) { QQuote.new([], b) }
33
+ rule(:pattern => sequence(:a), :body => simple(:b)) { QQuote.new(a, []) }
34
+ rule(:pattern => simple(:a), :body => simple(:b)) { QQuote.new([], []) }
35
+ end
36
+
37
+ def qparse str
38
+ parsed = QuarkParse.new.parse(str)
39
+ QuarkTransform.new.apply parsed
40
+ rescue Parslet::ParseFailed => e
41
+ e.cause.ascii_tree
42
+ end
data/lib/qrun.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'qtypes'
2
+ require 'qparse'
3
+ require 'qeval'
4
+
5
+ ## Evaluation ##
6
+
7
+ QVM = Struct.new(:stack, :program, :bindings)
8
+
9
+ def qrun(str, stack=[], bindings={})
10
+ qreduce QVM.new(stack, qparse(str), bindings)
11
+ end
12
+
13
+ def qreduce vm
14
+ until vm.program.empty?
15
+ item = vm.program.shift
16
+ if item.is_a? QAtom
17
+ if $core_func.has_key? item.val
18
+ apply_core_func(item.val, vm, ->(*x){qrun(*x)})
19
+ elsif vm.bindings.has_key? item.val
20
+ quote = vm.bindings[item.val]
21
+ vm.program.unshift(*[quote.dup, QAtom.new('call')])
22
+ else
23
+ raise QuarkError, "No such function: #{item}"
24
+ end
25
+ else
26
+ vm.stack.push item
27
+ end
28
+ end
29
+ return vm
30
+ end
31
+
32
+ def qrepl(vm)
33
+ loop do
34
+ print ':> '
35
+ begin
36
+ input = $stdin.gets.chomp
37
+ case input.strip
38
+ when '*q'
39
+ exit!
40
+ when '*f'
41
+ vm.bindings.sort.each { |k, v| puts "#{k}\n #{v.to_s}\n\n"}
42
+ when /\*f\s+(.+)/
43
+ if vm.bindings.has_key? $1
44
+ puts vm.bindings[$1]
45
+ else puts "No such function: #{$1}" end
46
+ else
47
+ vm = qrun(input, vm.stack.dup, vm.bindings.dup)
48
+ end
49
+ puts vm.stack.map { |x| x.is_a?(QQuote) ? x.to_s(20) : x.to_s }.join(' ')
50
+ rescue Exception => e
51
+ puts e
52
+ end
53
+ end
54
+ end
data/lib/qtypes.rb ADDED
@@ -0,0 +1,139 @@
1
+
2
+ ## Quark Values ##
3
+
4
+ # parent class for all the quark primitive values
5
+ class QVal
6
+
7
+ attr_accessor :val
8
+
9
+ def initialize(v)
10
+ @val = v
11
+ end
12
+
13
+ def to_s
14
+ @val.to_s
15
+ end
16
+
17
+ def ==(a)
18
+ return @val == a.val if a.is_a? self.class
19
+ false
20
+ end
21
+
22
+ end
23
+
24
+ # quark numbers
25
+ class QNum < QVal
26
+
27
+ def to_s
28
+ @val.to_i == @val ? @val.to_i.to_s : @val.to_s
29
+ end
30
+
31
+ def qtype; :Num end
32
+ end
33
+
34
+ # quark functions/variables
35
+ class QAtom < QVal
36
+ def qtype; :Atom end
37
+ end
38
+
39
+ # quark symbols
40
+ class QSym < QVal
41
+
42
+ def to_s
43
+ ":#{@val.to_s}"
44
+ end
45
+
46
+ def qtype; :Sym end
47
+ end
48
+
49
+ # quark strings
50
+ class QStr < QVal
51
+
52
+ def to_s
53
+ "\"#{@val.to_s}\""
54
+ end
55
+
56
+ def qtype; :Str end
57
+ end
58
+
59
+ # class for quark quotes
60
+ class QQuote
61
+
62
+ attr_accessor :pattern, :body
63
+
64
+ def initialize(p, b)
65
+ @pattern = p
66
+ @body = b
67
+ end
68
+
69
+ def to_s(n=nil)
70
+ return "[]" if @body.empty? and @pattern.empty?
71
+ serialize = lambda do |arr|
72
+ xs = n ? arr.first(n) : arr
73
+ xs.map { |x| x.is_a?(QQuote) ? x.to_s(n) : x.to_s }.join(' ') + (n && arr.length > n ? ' ...' : '')
74
+ end
75
+ pattern_str = " #{serialize.call(@pattern)} |" if !@pattern.empty?
76
+ body_str = " #{serialize.call(@body)} " if !@body.empty?
77
+ "[#{pattern_str}#{body_str}]"
78
+ end
79
+
80
+ def dup
81
+ Marshal.load(Marshal.dump(self))
82
+ end
83
+
84
+ def ==(x)
85
+ if x.is_a? QQuote
86
+ (@pattern == x.pattern) && (@body == x.body)
87
+ else false end
88
+ end
89
+
90
+ def qtype; :Quote end
91
+
92
+ # pushes to quote body
93
+ def push x
94
+ @body.push x
95
+ end
96
+
97
+ # pops from quark body
98
+ def pop
99
+ @body.pop
100
+ end
101
+
102
+ # takes a hash of var strings to quark items and recursively subs its body
103
+ def bind bindings
104
+ @body.map! do |x|
105
+ if x.is_a? QQuote then x.bind bindings
106
+ elsif x.is_a? QAtom then bindings[x.val] || x
107
+ else x end
108
+ end
109
+ return self
110
+ end
111
+
112
+ end
113
+
114
+
115
+ ## Type Checking ##
116
+
117
+ # compares two qtype to see if they are equivelent
118
+ # type comparison is not symmetric
119
+ # `a` is the signature type, so (:Any, :Empty) will match, but (:Empty, :Any) won't
120
+ def type_match(a, b)
121
+ return true if a == :Any
122
+ a == b
123
+ end
124
+
125
+ # matches a type signature against a data stack
126
+ def type_check(stack, type_sig)
127
+ return false if stack.length < type_sig.length
128
+ return true if type_sig.length == 0
129
+ stack_type = stack.last(type_sig.length).map(&:qtype)
130
+ types_eq = type_sig.zip(stack_type)
131
+ .map { |sig, type| type_match(sig, type) }
132
+ .inject(true) { |a, b| a && b}
133
+ return types_eq, type_sig, stack_type
134
+ end
135
+
136
+ # converts a qtype to a qitem representation (used in the `type` function)
137
+ def type_to_qitem t
138
+ return QSym.new(t.to_s)
139
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rquark
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - "⊥"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ description: This is a ruby implementation of the Quark language
28
+ email:
29
+ executables:
30
+ - rquark
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/rquark
35
+ - lib/prelude.qrk
36
+ - lib/qeval.rb
37
+ - lib/qparse.rb
38
+ - lib/qrun.rb
39
+ - lib/qtypes.rb
40
+ homepage: http://kdt.io/~/quark
41
+ licenses:
42
+ - CC0
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.4.5
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Quark, A Functional, Purely Homoiconic, Concatenative Language
64
+ test_files: []