calvin 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in calvin.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Utkarsh Kukreti
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Calvin
2
+
3
+ Calvin is a very terse programming language, inspired by APL/J/K.
4
+
5
+ ## Installation
6
+
7
+ ### Using Git (Recommended)
8
+
9
+ git clone https://github.com/utkarshkukreti/calvin.git
10
+ cd calvin
11
+ bundle install
12
+ rake install
13
+
14
+ ### From RubyGems
15
+
16
+ gem install calvin
17
+
18
+ ## Usage
19
+
20
+ Run
21
+
22
+ calvin
23
+
24
+ to start the interactive REPL.
25
+
26
+ ## Documentation
27
+
28
+ No docs yet, but the language is **heavily** tested.
29
+
30
+ Best way to learn more is to read the specs in `spec/`.
31
+
32
+ ## License
33
+
34
+ The MIT License
35
+
36
+ Copyright (c) 2012 Utkarsh Kukreti
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new("spec")
5
+
6
+ task :pry do
7
+ system "pry -I lib -r calvin -e 'include Calvin'"
8
+ end
9
+ task :default => :pry
data/bin/calvin ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'calvin'
3
+
4
+ Calvin::CLI.new ARGV
data/calvin.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'calvin/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "calvin"
8
+ gem.version = Calvin::VERSION
9
+ gem.authors = ["Utkarsh Kukreti"]
10
+ gem.email = ["utkarshkukreti@gmail.com"]
11
+ gem.description = %q{Very terse programming language, inspired by APL/J/K.}
12
+ gem.summary = %q{Very terse programming language, inspired by APL/J/K.}
13
+ gem.homepage = "https://github.com/utkarshkukreti/calvin"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "docopt"
21
+ gem.add_dependency "parslet"
22
+
23
+ gem.add_development_dependency "rspec"
24
+ end
data/lib/calvin/ast.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Calvin
2
+ class AST
3
+ attr_reader :ast
4
+
5
+ def initialize(input)
6
+ @ast = Transform.new.apply Parser.new.parse(input)
7
+ end
8
+ end
9
+ end
data/lib/calvin/cli.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Calvin
2
+ class CLI
3
+ def initialize(argv)
4
+ if argv.any?
5
+ puts "Argument passed: #{argv.inspect}"
6
+ end
7
+ @verbose = argv.include?("--verbose")
8
+ repl
9
+ end
10
+
11
+ def repl
12
+ evaluator = Evaluator.new
13
+ require 'readline'
14
+
15
+ puts "Calvin Programming Language REPL."
16
+ puts "Type exit or press Ctrl-D to exit."
17
+ lines = ""
18
+ loop do
19
+ prompt = "> "
20
+ line = Readline::readline(prompt)
21
+
22
+ exit if line.nil? || line == "exit"
23
+
24
+ Readline::HISTORY.push line
25
+
26
+ begin
27
+ ast = AST.new(line).ast
28
+
29
+ if @verbose
30
+ puts " AST: #{ast.inspect}"
31
+ print "Evaluated: "
32
+ end
33
+
34
+ p evaluator.apply(ast)[0]
35
+ rescue Exception => e
36
+ puts e
37
+ p e.message
38
+ puts e.backtrace
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module Calvin
2
+ class Core
3
+ ImpossibleException = Exception.new
4
+
5
+ BinaryOperators = { add: "+", multiply: "*", subtract: "-", divide: "/", power: "^", modulo: "%" }.invert
6
+ ComparisonOperators = { eq: "=", neq: "<>", lte: "<=", lt: "<", gte: ">=", gt: ">" }.invert
7
+ Folders = { foldr: "\\:", foldl: "\\" }.invert
8
+ Mappers = { map: "@" }.invert
9
+ end
10
+ end
@@ -0,0 +1,130 @@
1
+ module Calvin
2
+ class Evaluator < Parslet::Transform
3
+ attr_accessor :env
4
+
5
+ def initialize
6
+ super
7
+ @env = {}
8
+
9
+ rule assignment: { identifier: simple(:name), expression:
10
+ subtree(:expression)} do |context|
11
+ @env[context[:name].to_s] = context[:expression]
12
+ end
13
+
14
+ rule deassignment: { identifier: simple(:name) } do |context|
15
+ @env[context[:name].to_s]
16
+ end
17
+
18
+ rule range: subtree(:range) do
19
+ if range[:first]
20
+ range[:first]..range[:last]
21
+ else
22
+ 0..(range[:last] - 1)
23
+ end
24
+ end
25
+
26
+ rule folded: { binary_operator: simple(:op), folder: simple(:folder),
27
+ expression: subtree(:expression) } do
28
+ if folder == "\\"
29
+ elsif folder == "\\:"
30
+ expression.reverse!
31
+ else
32
+ raise Core::ImpossibleException
33
+ end
34
+
35
+ case op.to_sym
36
+ when :+, :-, :%, :*, :/
37
+ expression.reduce op.to_sym
38
+ when :^
39
+ expression.reduce :**
40
+ else
41
+ raise Core::ImpossibleException
42
+ end
43
+ end
44
+
45
+ rule mapped: { monad: { left: { binary_operator: simple(:op) },
46
+ integer: simple(:integer) }, mapper: simple(:mapper), expression: subtree(:expression) } do
47
+ if mapper == "@"
48
+ else
49
+ raise Core::ImpossibleException
50
+ end
51
+
52
+ op = op().to_sym # hack; make it a var
53
+ if op == :^
54
+ op = :**
55
+ end
56
+
57
+ if expression.is_a?(Array) || expression.is_a?(Range)
58
+ expression.to_a.map {|el| el.send(op, integer.to_i) }
59
+ else
60
+ expression.send(op, integer.to_i)
61
+ end
62
+ end
63
+
64
+ rule mapped: { monad: { right: { binary_operator: simple(:op) },
65
+ integer: simple(:integer) }, mapper: simple(:mapper), expression: subtree(:expression) } do
66
+ if mapper == "@"
67
+ else
68
+ raise Core::ImpossibleException
69
+ end
70
+
71
+ op = op().to_sym # hack; make it a var
72
+ if op == :^
73
+ op = :**
74
+ end
75
+
76
+ if expression.is_a?(Array) || expression.is_a?(Range)
77
+ expression.to_a.map {|el| integer.to_i.send(op, el) }
78
+ else
79
+ integer.to_i.send(op, expression)
80
+ end
81
+ end
82
+
83
+ rule expression: subtree(:expression) do
84
+ expression
85
+ end
86
+
87
+ rule mapped: { monad: { left: { comparison_operator: simple(:op) },
88
+ integer: simple(:integer) }, mapper: simple(:mapper), expression: subtree(:expression) } do
89
+ if mapper == "@"
90
+ else
91
+ raise Core::ImpossibleException
92
+ end
93
+
94
+ op = op().to_sym # hack; make it a var
95
+ if op == :"<>"
96
+ op = :!=
97
+ elsif op == :"="
98
+ op = :"=="
99
+ end
100
+
101
+ if expression.is_a?(Array) || expression.is_a?(Range)
102
+ expression.to_a.select {|el| el.send(op, integer.to_i) }
103
+ else
104
+ expression.send(op, integer.to_i)
105
+ end
106
+ end
107
+
108
+ rule mapped: { monad: { right: { comparison_operator: simple(:op) },
109
+ integer: simple(:integer) }, mapper: simple(:mapper), expression: subtree(:expression) } do
110
+ if mapper == "@"
111
+ else
112
+ raise Core::ImpossibleException
113
+ end
114
+
115
+ op = op().to_sym # hack; make it a var
116
+ if op == :"<>"
117
+ op = :!=
118
+ elsif op == :"="
119
+ op = :"=="
120
+ end
121
+
122
+ if expression.is_a?(Array) || expression.is_a?(Range)
123
+ expression.to_a.select {|el| integer.to_i.send(op, el) }
124
+ else
125
+ expression.send(op, integer.to_i)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,42 @@
1
+ module Calvin
2
+ class Parser < Parslet::Parser
3
+ rule(:space) { str(" ") }
4
+ rule(:spaces?) { space.repeat }
5
+ rule(:spaces) { space.repeat(1) }
6
+ rule(:digit) { match["0-9"] }
7
+
8
+ rule(:integer) { digit.repeat(1).as(:integer) }
9
+ rule(:constant) { integer }
10
+ rule(:identifier) { match["a-z"].repeat(1).as(:identifier) }
11
+ rule(:deassignment) { identifier.as(:deassignment) }
12
+ rule(:variable) { constant }
13
+
14
+ rule(:array) { (variable >> (space >> variable).repeat).as(:array) }
15
+
16
+ rule(:range) { (integer.as(:first).maybe >> str("..") >> integer.as(:last)).as(:range) }
17
+ { mapper: Core::Mappers,
18
+ folder: Core::Folders,
19
+ binary_operator: Core::BinaryOperators,
20
+ comparison_operator: Core::ComparisonOperators }.each do |type, hash|
21
+ rule type do
22
+ hash.keys.map { |key| str(key).as(type) }.reduce(:|)
23
+ end
24
+ end
25
+
26
+ rule :monad do
27
+ (variable >> (binary_operator | comparison_operator).as(:right) |
28
+ (binary_operator | comparison_operator).as(:left) >> variable).as(:monad)
29
+ end
30
+
31
+ rule(:folded) { (binary_operator >> folder >> expression).as(:folded) }
32
+ rule(:mapped) { (monad >> mapper >> expression).as(:mapped) }
33
+
34
+ rule(:assignment) { (identifier >> str(":=") >> expression).as(:assignment) }
35
+
36
+ rule(:expression) { (deassignment | mapped | folded | range | array).as(:expression) }
37
+ rule(:statement) { assignment | expression }
38
+ rule(:statements) { statement.repeat }
39
+
40
+ root(:statements)
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ module Calvin
2
+ class Transform < Parslet::Transform
3
+ rule(integer: simple(:x)) { x.to_i }
4
+ rule(array: sequence(:x)) { x }
5
+ rule(array: simple(:x)) { [x] }
6
+ rule(function: simple(:x)) { x.to_s }
7
+ rule(applier: simple(:x)) { x.to_s }
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Calvin
2
+ VERSION = "0.0.1"
3
+ end
data/lib/calvin.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "parslet"
2
+ require "docopt"
3
+
4
+ require "calvin/core"
5
+ require "calvin/parser"
6
+ require "calvin/transform"
7
+ require "calvin/ast"
8
+ require "calvin/evaluator"
9
+
10
+ require "calvin/cli"
11
+
12
+ require "calvin/version"
data/spec/ast_spec.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ # describe Calvin::AST do
4
+ # describe "Array" do
5
+ # it "should parse simple arrays" do
6
+ # aste("1").should eq [1]
7
+ # aste("1 2 3").should eq [1, 2, 3]
8
+ # end
9
+ # end
10
+ #
11
+ # describe "Folder" do
12
+ # it "should parse folding with binary operators on arrays" do
13
+ # aste("+\\1 2 3").should eq folded: { binary_operator: "+", folder: "\\", array: [1, 2, 3] }
14
+ # aste("-\\1 2 3").should eq folded: { binary_operator: "-", folder: "\\", array: [1, 2, 3] }
15
+ # aste("*\\1 2 3").should eq folded: { binary_operator: "*", folder: "\\", array: [1, 2, 3] }
16
+ # aste("/\\1 2 3").should eq folded: { binary_operator: "/", folder: "\\", array: [1, 2, 3] }
17
+ # aste("^\\1 2 3").should eq folded: { binary_operator: "^", folder: "\\", array: [1, 2, 3] }
18
+ # aste("%\\1 2 3").should eq folded: { binary_operator: "%", folder: "\\", array: [1, 2, 3] }
19
+ # end
20
+ # end
21
+ # end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe Calvin do
4
+ it "should have a VERSION" do
5
+ Calvin::VERSION.should be_a String
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ require "spec_helper"
2
+
3
+ describe Calvin::Evaluator do
4
+ it "should parse foldl" do
5
+ eval1("+\\1 2 3").should eq 6
6
+ eval1("-\\1 2 3").should eq -4
7
+ eval1("*\\2 3 5").should eq 30
8
+ eval1("/\\8 2 2").should eq 2
9
+ eval1("%\\9 7 9").should eq 2
10
+ eval1("^\\2 2 3").should eq 64
11
+ end
12
+
13
+ it "should parse foldr" do
14
+ eval1("+\\:1 2 3").should eq 6
15
+ eval1("-\\:1 2 3").should eq 0
16
+ eval1("*\\:2 3 5").should eq 30
17
+ eval1("/\\:2 2 8").should eq 2
18
+ eval1("%\\:9 7 9").should eq 2
19
+ eval1("^\\:2 2 3").should eq 81
20
+ end
21
+
22
+ it "should parse map" do
23
+ eval1("+2@1 2 3").should eq [3, 4, 5]
24
+ eval1("2+@1 2 3").should eq [3, 4, 5]
25
+
26
+ eval1("-2@1 2 3").should eq [-1, 0, 1]
27
+ eval1("2-@1 2 3").should eq [1, 0, -1]
28
+
29
+ eval1("*2@1 2 3").should eq [2, 4, 6]
30
+ eval1("2*@1 2 3").should eq [2, 4, 6]
31
+
32
+ eval1("/2@9 6 3").should eq [4, 3, 1]
33
+ eval1("4/@1 2 3").should eq [4, 2, 1]
34
+
35
+ eval1("%2@1 2 3").should eq [1, 0, 1]
36
+ eval1("2%@1 2 3").should eq [0, 0, 2]
37
+
38
+ eval1("^2@1 2 3").should eq [1, 4, 9]
39
+ eval1("2^@1 2 3").should eq [2, 4, 8]
40
+ end
41
+
42
+ it "should parse filter" do
43
+ eval1(">3@1 2 4").should eq [4]
44
+ eval1("<3@1 6 1").should eq [1, 1]
45
+ eval1(">=3@1 3 4").should eq [3, 4]
46
+ eval1("<=3@1 3 4").should eq [1, 3]
47
+ eval1("=3@1 2 3 3 4").should eq [3, 3]
48
+
49
+ eval1("3>@1 2 4").should eq [1, 2]
50
+ eval1("3<@1 6 1").should eq [6]
51
+ eval1("3>=@1 3 4").should eq [1, 3]
52
+ eval1("3<=@1 3 4").should eq [3, 4]
53
+ eval1("3=@1 2 3 3 4").should eq [3, 3]
54
+ end
55
+
56
+ it "should parse nested expressions" do
57
+ eval1("+\\2^@1 2 3").should eq 14
58
+ eval1("-\\:-2@1 2 3").should eq 2
59
+ eval1("/2@^2@2+@1 2 3").should eq [4, 8, 12]
60
+ end
61
+
62
+ it "should parse ranges" do
63
+ eval1("..10").should eq 0..9
64
+ eval1("1..100").should eq 1..100
65
+ eval1("+\\1..100").should eq (100 * 101) / 2
66
+ eval1("+\\..100").should eq (99 * 100) / 2
67
+ end
68
+
69
+ it "should map ranges" do
70
+ eval1("^2@1..3").should eq [1, 4, 9]
71
+ eval1("2^@1..3").should eq [2, 4, 8]
72
+ end
73
+
74
+ it "should apply monads to single element too" do
75
+ eval1("*2@+\\1 2 3").should eq 12
76
+ end
77
+
78
+ it "should save assigned variables in env" do
79
+ e = Calvin::Evaluator.new
80
+
81
+ e.apply ast("a:=1")
82
+ e.env["a"].should eq [1]
83
+ e.apply(ast("a")[0]).should eq [1]
84
+
85
+ e.apply ast("a:=1 2 4")
86
+ e.apply(ast("a")[0]).should eq [1, 2, 4]
87
+
88
+ e.apply ast("b:=+\\4..6")
89
+ e.apply(ast("b")[0]).should eq 15
90
+ end
91
+ end
@@ -0,0 +1,20 @@
1
+ require "bundler/setup"
2
+ require "calvin"
3
+
4
+ RSpec.configure do |config|
5
+ def ast(input)
6
+ Calvin::AST.new(input).ast
7
+ end
8
+
9
+ def aste(input)
10
+ ast(input)[0][:expression]
11
+ end
12
+
13
+ def eval(input)
14
+ Calvin::Evaluator.new.apply ast(input)
15
+ end
16
+
17
+ def eval1(input)
18
+ eval(input).first
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: calvin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Utkarsh Kukreti
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: docopt
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: parslet
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Very terse programming language, inspired by APL/J/K.
63
+ email:
64
+ - utkarshkukreti@gmail.com
65
+ executables:
66
+ - calvin
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - .rspec
72
+ - Gemfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - bin/calvin
77
+ - calvin.gemspec
78
+ - lib/calvin.rb
79
+ - lib/calvin/ast.rb
80
+ - lib/calvin/cli.rb
81
+ - lib/calvin/core.rb
82
+ - lib/calvin/evaluator.rb
83
+ - lib/calvin/parser.rb
84
+ - lib/calvin/transform.rb
85
+ - lib/calvin/version.rb
86
+ - spec/ast_spec.rb
87
+ - spec/calvin_spec.rb
88
+ - spec/evaluator_spec.rb
89
+ - spec/spec_helper.rb
90
+ homepage: https://github.com/utkarshkukreti/calvin
91
+ licenses: []
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.23
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Very terse programming language, inspired by APL/J/K.
114
+ test_files:
115
+ - spec/ast_spec.rb
116
+ - spec/calvin_spec.rb
117
+ - spec/evaluator_spec.rb
118
+ - spec/spec_helper.rb
119
+ has_rdoc: