├── .document ├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── VERSION ├── lib └── comalisp.rb └── spec ├── comalisp_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | #.DS_Store 31 | 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | 36 | # For emacs: 37 | #*~ 38 | #\#* 39 | #.\#* 40 | 41 | # For vim: 42 | #*.swp 43 | 44 | # For redcar: 45 | #.redcar 46 | 47 | # For rubinius: 48 | #*.rbc 49 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem "activesupport", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | gem "rspec", "~> 2.3.0" 10 | gem "bundler", "~> 1.0.0" 11 | gem "jeweler", "~> 1.6.4" 12 | gem "rcov", ">= 0" 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | diff-lcs (1.1.3) 5 | git (1.2.5) 6 | jeweler (1.6.4) 7 | bundler (~> 1.0) 8 | git (>= 1.2.5) 9 | rake 10 | rake (0.9.2) 11 | rcov (0.9.10) 12 | rspec (2.3.0) 13 | rspec-core (~> 2.3.0) 14 | rspec-expectations (~> 2.3.0) 15 | rspec-mocks (~> 2.3.0) 16 | rspec-core (2.3.1) 17 | rspec-expectations (2.3.0) 18 | diff-lcs (~> 1.1.2) 19 | rspec-mocks (2.3.0) 20 | 21 | PLATFORMS 22 | ruby 23 | 24 | DEPENDENCIES 25 | bundler (~> 1.0.0) 26 | jeweler (~> 1.6.4) 27 | rcov 28 | rspec (~> 2.3.0) 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Howard Yeh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Only works under Ruby 1.9 2 | 3 | # Lisp and Ruby. Marriage Made In Hell 4 | 5 | (ComaLisp { 6 | (let [:a,1], [:b, 2] { 7 | (defun [:foo,:a,:b,:c] { 8 | (let [:d, 6] { 9 | (list a, b, c, d)})}) 10 | (p (list a, b, (foo 3, 4, 5)))})}) 11 | 12 | This is _VALID_ Ruby syntax under 1.9. 13 | 14 | Here is an [introduction to ComaLisp](http://metacircus.com/hacking/2011/09/07/lispy-abuse-of-ruby-syntax.html) 15 | 16 | Feedback [@hayeah](http://twitter.com/hayeah) -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 17 | gem.name = "comalisp" 18 | gem.homepage = "https://github.com/hayeah/ComaLisp" 19 | gem.license = "MIT" 20 | gem.summary = "A dialect of Lisp. Fully Compatible with Ruby." 21 | gem.description = "A dialect of Lisp. Fully Compatible with Ruby." 22 | gem.email = "hayeah@gmail.com" 23 | gem.authors = ["Howard Yeh"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rspec/core' 29 | require 'rspec/core/rake_task' 30 | RSpec::Core::RakeTask.new(:spec) do |spec| 31 | spec.pattern = FileList['spec/**/*_spec.rb'] 32 | end 33 | 34 | RSpec::Core::RakeTask.new(:rcov) do |spec| 35 | spec.pattern = 'spec/**/*_spec.rb' 36 | spec.rcov = true 37 | end 38 | 39 | task :default => :spec 40 | 41 | require 'rake/rdoctask' 42 | Rake::RDocTask.new do |rdoc| 43 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 44 | 45 | rdoc.rdoc_dir = 'rdoc' 46 | rdoc.title = "comalisp #{version}" 47 | rdoc.rdoc_files.include('README*') 48 | rdoc.rdoc_files.include('lib/**/*.rb') 49 | end 50 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.1 -------------------------------------------------------------------------------- /lib/comalisp.rb: -------------------------------------------------------------------------------- 1 | class ComaLisp 2 | def initialize(opts={}) 3 | # parent is used for scoping 4 | @parent = opts[:parent] 5 | @env = opts[:env] || {} 6 | end 7 | 8 | # we use array to represent list and cons 9 | # The implication is that all lists must be proper. That is (cons 1, 2) is invalid. 10 | # [] is the empty list. Nothing else is the empty list. It is the fixpoint for cdr. 11 | def cons(head,tail=nil) 12 | [head,*tail] 13 | end 14 | 15 | def consp(o) 16 | o.is_a? Array 17 | end 18 | 19 | def tail(lst) 20 | lst.last 21 | end 22 | 23 | def car(lst) 24 | return null if lst.size < 1 25 | lst[0] 26 | end 27 | 28 | def null 29 | [] 30 | end 31 | 32 | def nullp(list) 33 | list == null 34 | end 35 | 36 | def cdr(lst) 37 | return [] if lst.size == 0 38 | lst[1..-1] 39 | end 40 | 41 | def list(*args) 42 | args 43 | end 44 | 45 | def call(fn,*args) 46 | self.apply(fn,args) 47 | end 48 | 49 | def apply(fn,args) 50 | self.send(fn,*args) 51 | end 52 | 53 | def let(*vars,&body) 54 | env = vars.inject({}) {|acc,(k,v)| 55 | acc[k] = v 56 | acc 57 | } 58 | scope(env,&body) 59 | end 60 | 61 | def scope(env={},&body) 62 | scope = ComaLisp.new(:parent => self, :env => env) 63 | scope.instance_eval(&body) 64 | end 65 | 66 | def defun(argslist,&body) 67 | me = self 68 | name = argslist.shift 69 | eigenclass = class << self; self; end 70 | eigenclass.instance_eval do 71 | define_method(name) do |*vals| 72 | env = {} 73 | argslist.each_with_index do |arg,i| 74 | env[arg] = vals[i] 75 | end 76 | me.scope(env,&body) 77 | end 78 | end 79 | end 80 | 81 | def set(var,val) 82 | @env[var.to_sym] = val 83 | end 84 | 85 | private 86 | 87 | def method_missing(name,*args,&block) 88 | if args.empty? 89 | variable_lookup(name) 90 | else 91 | method_call(name,*args,&block) 92 | end 93 | end 94 | 95 | # we assume the first argument is the receiver 96 | def method_call(method,receiver,*args,&block) 97 | receiver.send(method,*args,&block) 98 | end 99 | 100 | def variable_lookup(name) 101 | if @env.has_key?(name) 102 | @env[name] 103 | elsif @parent 104 | @parent.send(name) 105 | else 106 | raise "no binding for #{name}" 107 | end 108 | end 109 | 110 | alias_method :eval, :instance_eval 111 | end 112 | 113 | def ComaLisp(&block) 114 | ComaLisp.new.eval(&block) 115 | end 116 | -------------------------------------------------------------------------------- /spec/comalisp_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe "Comalisp" do 4 | context "list:" do 5 | it "is a cons" do 6 | (ComaLisp { 7 | (consp (list 1, 2)) 8 | }).should be_true 9 | end 10 | 11 | specify "car is 1" do 12 | (ComaLisp { 13 | (car (list 1, 2)) 14 | }).should == 1 15 | end 16 | 17 | it "cadr element is 2" do 18 | (ComaLisp { 19 | (car (cdr (list 1, 2))) 20 | }).should == 2 21 | end 22 | end 23 | 24 | context "cons:" do 25 | it "builds a cons" do 26 | (ComaLisp { 27 | (consp (cons 1, null)) 28 | }).should == true 29 | end 30 | 31 | it "takes the car" do 32 | (ComaLisp { 33 | (car (cons 1, null)) 34 | }).should == 1 35 | end 36 | 37 | it "the cdr of a one element list is null" do 38 | (ComaLisp { 39 | (nullp (cdr (cons 1, null))) 40 | }).should be_true 41 | end 42 | 43 | specify "the cdr of null is null" do 44 | (ComaLisp { 45 | (nullp (cdr null)) 46 | }).should be_true 47 | end 48 | 49 | specify "car of null is null" do 50 | (ComaLisp { 51 | (nullp (car null)) 52 | }).should be_true 53 | end 54 | end 55 | 56 | context "let:" do 57 | specify "variable binding" do 58 | (ComaLisp { 59 | (let [:a,1], [:b,2] { 60 | [a,b] 61 | })}).should == [1,2] 62 | end 63 | 64 | specify "variable shadow" do 65 | (ComaLisp { 66 | (let [:a,1], [:b,2] { 67 | (let [:a,3] { 68 | [a,b] 69 | })})}).should == [3,2] 70 | end 71 | end 72 | 73 | context "set:" do 74 | it "sets a binding" do 75 | (ComaLisp { 76 | (let [:a,1] { 77 | (set :a, 2) 78 | a 79 | })}).should == 2 80 | end 81 | end 82 | 83 | context "defun:" do 84 | specify "define local function" do 85 | (ComaLisp { 86 | (defun [:foo,:a,:b] { 87 | [a,b]}) 88 | [(foo 1, 2),(foo 3, 4)]}).should == [[1,2],[3,4]] 89 | end 90 | end 91 | 92 | context "function application" do 93 | it "calls a function with args" do 94 | (ComaLisp { 95 | (defun [:foo,:a,:b] { 96 | (list a, b)}) 97 | (call :foo, 1, 2)}).should == [1,2] 98 | end 99 | 100 | it "applies a function with args" do 101 | (ComaLisp { 102 | (defun [:foo,:a,:b] { 103 | (list a, b)}) 104 | (apply :foo, [1,2])}).should == [1,2] 105 | end 106 | 107 | end 108 | 109 | context "method prefixing:" do 110 | it "calls the method on the first argument" do 111 | (ComaLisp { 112 | (map [-1,-2,-3] { |e| 113 | (abs e)})}).should == [1,2,3] 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'comalisp' 5 | 6 | # Requires supporting files with custom matchers and macros, etc, 7 | # in ./support/ and its subdirectories. 8 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 9 | 10 | RSpec.configure do |config| 11 | 12 | end 13 | --------------------------------------------------------------------------------