rquark 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []