lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I'm feeling evil too.. so here goes a type check implementation with token filters ;o)

Attached is type_check.lua which does the work -- needs the dsilver-token-filter-patch -- and a type_test.lua to demonstrate it's use.. sample output follows:

./lua -ltype_check -ltype_test
Test... true
I now know this is a number:    123
OK
Test... false
OK ./type_test.lua:3: bad argument #1 to 'basic_number' (number expected, got string)
Test... true
Oh, it's not nil anyway:        45.6
OK
Test... true
Oh, it's not nil anyway:        not a number either
OK
Test... false
OK ./type_test.lua:7: bad argument #1 to 'num_or_string' (number or string expected, got table)
Test... true
Alot of options here, in order: still a string nil function: 0x52aec0 nil
OK
Test... true
Alot of options here, in order: table: 0x52b0a0 333 function: 0x52a0e0 nil
OK
Test... false
OK ./type_test.lua:11: bad argument #4 to 'a_bit_more_complex' (userdata or nil expected, got function)
Test... true
I now have exactly two args passed.. oh well, the third could be an attempted arg as well, but it was nil any way
OK
Test... false
OK ./type_test.lua:15: bad argument #3 to 'enforce_num_args' (nil expected, got string)
Test... false
OK ./type_test.lua:15: bad argument #2 to 'enforce_num_args' (string expected, got nil)

I'm not feeling guilty enough yet to be sorry..

enjoy!

//Andreas

Ps.
And thanks to the Organizers and all other Oh-Six participants for a great workshop! (no names, or I'd feel I left ppl out)


askok@dnainternet.net skrev:

Me, too :) Not as productive as Daniel, but I did finish this late last night, so here goes...

Ref: checking Lua function usage, and return values.

-asko

On Tue, 05 Sep 2006 22:38:39 +0200
 Daniel Silverstone <dsilvers@digital-scurf.org> wrote:
As per my agreement with lhf and various others at the Lua workshop this
year, I hereby confess my sins:
http://blog.digital-scurf.org/tech/lua-with-macros.html

I'm sorry.

D.



--[[
	Syntax:

	function [name]( [type [:type]*] argName [, ...] )
	end

	Valid type's are:
		number
		string
		table
		userdata
		function
		nil

	Written under heavy inspiration by Andreas Stenius
	Parts copied from pump.lua that came with Daniel Silverstone's
	token filter patch.
--]]

local function debug_put(put)
	return function( l, t, v )
		print("< ", l, t, v)
		put(l, t, v)
	end
end

local function debug_get(get)
	return function()
		local l, t, v = get()
		print(" >", l, t, v)
		return l, t, v
	end
end

local function consume_func_args(get,put)
	local types = {}
	local curr_type
	local function add_type(line, v)
		if not curr_type then
			curr_type = {
				line = line,
				type = { v }
			}
		else
			table.insert( curr_type.type, v )
		end
	end

	while true do
		local line, tok, val = get()
		if tok == ")" then 
			if curr_type then
				table.insert( types, curr_type )
			end
			put( line, tok, val )
			return types
		end
		
		if tok == "<name>" then
			if  val == "number" or
				val == "string" or
				val == "table" or
				val == "userdata"
			then
				add_type( line, val )
			else
				if curr_type then
					curr_type.name = val
				end
				put( line, tok, val )
			end
		elseif 
			tok == "function" or
			tok == "nil"
		then
			add_type( line, tok )
		elseif tok ~= ":" then
			if curr_type and tok == "," then
				table.insert( types, curr_type )
				curr_type = nil
			end
			put( line, tok, val )
		end
	end
end

local function intercept_function_def(get,put)
	local func_name = ""
	while true do
		local line, tok, val = get()
		put( line, tok, val )
		if tok == "(" then 
			local type_asserts = consume_func_args(get,put)
			for argn, argv in ipairs(type_asserts) do
				local type_string = ""
				local multiple_types = false
				put( argv.line, "<name>", "assert" )
				put( argv.line, "(" )
				for idx_t, t in ipairs(argv.type) do
					if multiple_types then 
						put( argv.line, "or" ) 
						type_string = type_string .. " or "
					end
					type_string = type_string .. t
					put( argv.line, "<name>", "type" )
					put( argv.line, "(" )
					put( argv.line, "<name>", argv.name )
					put( argv.line, ")" )
					put( argv.line, "==" )
					put( argv.line, "<string>", t )
					multiple_types = true
				end
				put( argv.line, ",")
				put( argv.line, "<string>", string.format( 
					"bad argument #%d to '%s' (%s expected, got ",
					argn, #func_name > 0 and func_name or "(anonymous)",
					type_string )
				)
				put( argv.line, ".." )
				put( argv.line, "<name>", "type" )
				put( argv.line, "(" )
				put( argv.line, "<name>", argv.name )
				put( argv.line, ")" )
				put( argv.line, ".." )
				put( argv.line, "<string>", ")" )
				put( argv.line, ")" )
			end
			break
		else
			func_name = func_name .. (tok ~= "<name>" and tok or val)
		end
	end
end

local function type_check(get,put)
	put("type_check init") -- Eaten during the pipe setup
	while true do
		local line, tok, val = get()
		put( line, tok, val )
		if tok == "function" then
			intercept_function_def(get,put)
		end
	end
end

local function pipe(f,get,put)
   put = put or coroutine.yield
   local F=coroutine.wrap(f)
   --F(debug_get(get),debug_put(put))
   F(get,put)
   return F
end


local pipeline

local getter
function _TOKEN_PUMP(_getter, source)
   if _getter then
      getter = _getter
      pipeline = pipe(type_check, getter)
   else
      return pipeline()
   end
end
-- this file requires that type_check.lua has already been loaded

function basic_number( number i )
	print( "I now know this is a number: ", i )
end

function num_or_string( number:string val )
	print( "Oh, it's not nil anyway: ", val )
end

function a_bit_more_complex( table:string opt, number:nil foo, function func, userdata:nil ud )
	print( "Alot of options here, in order: ", opt, foo, func, ud )
end

function enforce_num_args( number a, string b, nil _ )
	print( "I now have exactly two args passed.. oh well, the third could be an attempted arg as well, but it was nil any way" )
end

function test_call( expected, f, ...)
	print( "Test...", expected )
	local ok, msg = pcall( f, ... )
	if ok == expected then
		print( "OK ", msg or "")
	else
		print( "FAILED ", msg )
	end
end

test_call( true, basic_number, 123 )
test_call( false, basic_number, "not a number" )
test_call( true, num_or_string, 45.6 )
test_call( true, num_or_string, "not a number either" )
test_call( false, num_or_string, { "bogus" } )
test_call( true, a_bit_more_complex, "still a string", nil, print )
test_call( true, a_bit_more_complex, table, 333, pairs, nil )
test_call( false, a_bit_more_complex, "another string", 222, io.open, io.close )
test_call( true, enforce_num_args, 456, "qwerty" )
test_call( false, enforce_num_args, 456, "qwerty", "aha!" )
test_call( false, enforce_num_args, 456 )