# Copyright (c) 2012-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Velo
require 'velo/debug'
require 'velo/exceptions'
require 'velo/parser'
require 'velo/ast'
debug "loading runtime"
# the built-in objects, for convenience of other sources
$Object = nil
$String = nil
$IO = nil
# title is for debugging only. methods themselves do not have names.
class VeloMethod
def initialize title, fun
@title = title
@fun = fun
@obj = nil
end
def bind_object obj
@obj = obj
end
def run args
@fun.call @obj, args
end
def to_s
"VeloMethod(#{@title})"
end
end
# parents will be [] for Object, [Object] for all other objects
class VeloObject
def initialize title
@title = title
@parents = []
@parents.push $Object if not $Object.nil?
@attrs = {}
@contents = nil
end
def to_s
"VeloObject('#{@title}')"
end
def set ident, obj
@attrs[ident] = obj
debug "set #{ident} to #{obj} on #{self}"
end
# let this object delegate to another object
def velo_extend obj
debug "extending #{self} w/#{obj}"
@parents.unshift obj
end
# look up an identifier on this object, or any of its delegates
def lookup ident
debug "lookup #{ident} on #{self}"
result = lookup_impl ident, []
debug "lookup result: #{result}"
if result.nil?
raise VeloAttributeNotFound, "could not locate '#{ident}' on #{self}"
end
if result.is_a? VeloMethod
debug "binding obtained method #{result} to object #{self}"
result.bind_object self
end
result
end
# look up an identifier on this object, or any of its delegates
def lookup_impl ident, trail
debug "lookup_impl #{ident} on #{self}"
if trail.include? self
debug "we've already seen this object, stopping search"
return nil
end
trail.push self
if @attrs.has_key? ident
debug "found here (#{self}), it's #{@attrs[ident]}"
@attrs[ident]
else
x = nil
for parent in @parents
x = parent.lookup_impl ident, trail
break if not x.nil?
end
x
end
end
def contents
@contents
end
def contents= c
@contents = c
end
end
def make_string_literal text
o = VeloObject.new "#{@text}"
o.velo_extend $String
o.contents = text
o
end
### establish the objectbase ###
$Object = VeloObject.new 'Object'
$Object.set 'extend', VeloMethod.new('extend', proc { |obj, args|
obj.velo_extend args[0]
})
$Object.set 'self', VeloMethod.new('self', proc { |obj, args|
obj
})
$Object.set 'new', VeloMethod.new('new', proc { |obj, args|
o = VeloObject.new 'new'
if not args[0].nil?
o.velo_extend args[0]
end
o
})
$Object.set 'if', VeloMethod.new('if', proc { |obj, args|
debug args
method = nil
choice = args[0].contents.empty? ? 2 : 1
method = args[choice].lookup 'create'
method.run [obj]
})
$String = VeloObject.new 'String'
$String.set 'concat', VeloMethod.new('concat', proc { |obj, args|
debug "concat #{obj} #{args[0]}"
make_string_literal(obj.contents + args[0].contents)
})
$String.set 'create', VeloMethod.new('create', proc { |obj, args|
p = Parser.new obj.contents
s = p.script
debug "create! #{s} #{args[0]} arg count #{args.length}"
s.eval args[0], []
args[0]
})
$String.set 'method', VeloMethod.new('method', proc { |obj, args|
# obj is the string to turn into a method
debug "turning #{obj} into a method"
p = Parser.new obj.contents
s = p.script
VeloMethod.new('*created*', proc { |obj, args|
s.eval obj, args
})
})
$String.set 'equals', VeloMethod.new('equals', proc { |obj, args|
if obj.contents == args[0].contents
make_string_literal "true"
else
make_string_literal ""
end
})
$IO = VeloObject.new 'IO'
$IO.set 'print', VeloMethod.new('print', proc { |obj, args|
puts args[0].contents
})
$Object.set 'Object', $Object
$Object.set 'String', $String
$Object.set 'IO', $IO
### ... ###
if $0 == __FILE__
$debug = true
$Object.set 'foo', VeloMethod.new('foo', proc { |obj, args|
puts "foo method called on #{obj} with args #{args}!"
})
$String.set 'bar', VeloMethod.new('bar', proc { |obj, args|
puts "bar method called on #{obj} with args #{args}!"
})
velo_Shimmy = VeloObject.new 'Shimmy'
velo_Shimmy.velo_extend $String
(velo_Shimmy.lookup 'bar').run [1,2,3]
end