- faster, stronger and lighter parser

- more robust comment removal
- disable plugins by default


git-svn-id: http://cmock.svn.sourceforge.net/svnroot/cmock/trunk@140 bf332499-1b4d-0410-844d-d2d48d5cc64c
This commit is contained in:
mvandervoord
2009-08-24 13:57:11 +00:00
parent 6bb989a3c0
commit c524ae7c9d
104 changed files with 522 additions and 13800 deletions
-4
View File
@@ -2,12 +2,8 @@
# Setup our load path:
[
'lib',
'vendor/gems/polyglot-0.2.5/lib/',
'vendor/gems/treetop-1.2.5/lib/',
].each do |dir|
$LOAD_PATH.unshift( File.join( File.expand_path(File.dirname(__FILE__) + "/../"), dir) )
end
require 'rubygems'
require 'treetop'
-2
View File
@@ -5,8 +5,6 @@
'vendor/behaviors/lib',
'vendor/hardmock/lib',
'vendor/unity/auto/',
'vendor/gems/polyglot-0.2.5/lib/',
'vendor/gems/treetop-1.2.5/lib/',
'test/system/'
].each do |dir|
$LOAD_PATH.unshift( File.join( File.expand_path(File.dirname(__FILE__) + "/../"), dir) )
+10 -24
View File
@@ -1,7 +1,5 @@
require File.expand_path(File.dirname(__FILE__)) + "/../config/production_environment"
require "cmock_function_prototype_node_classes"
require "cmock_function_prototype_parser"
require "cmock_header_parser"
require "cmock_generator"
require "cmock_file_writer"
@@ -10,18 +8,16 @@ require "cmock_plugin_manager"
require "cmock_generator_utils"
require "cmock_unityhelper_parser"
#gem install test-unit -v 1.2.3
ruby_version = RUBY_VERSION.split('.')
if (ruby_version[1].to_i == 9) and (ruby_version[2].to_i > 1)
require 'gems'
gem 'test-unit'
require 'test/unit'
end
class CMock
def initialize(options=nil)
@cfg = CMockConfig.new(options)
cm_config = CMockConfig.new(options)
cm_unityhelper = CMockUnityHelperParser.new(cm_config)
cm_writer = CMockFileWriter.new(cm_config)
cm_gen_utils = CMockGeneratorUtils.new(cm_config, {:unity_helper => cm_unityhelper})
cm_gen_plugins = CMockPluginManager.new(cm_config, cm_gen_utils)
@cm_parser = CMockHeaderParser.new(cm_config)
@cm_generator = CMockGenerator.new(cm_config, cm_writer, cm_gen_utils, cm_gen_plugins)
end
def setup_mocks(files)
@@ -34,23 +30,13 @@ class CMock
def generate_mock(src)
name = File.basename(src, '.h')
path = File.dirname(src)
@cfg.set_path(path)
cm_parser = CMockHeaderParser.new(CMockFunctionPrototypeParser.new, File.read(src), @cfg, "#{name}.h")
cm_unityhelper = CMockUnityHelperParser.new(@cfg)
cm_writer = CMockFileWriter.new(@cfg)
cm_gen_utils = CMockGeneratorUtils.new(@cfg, {:unity_helper => cm_unityhelper})
cm_gen_plugins = CMockPluginManager.new(@cfg, cm_gen_utils)
cm_generator = CMockGenerator.new(@cfg, name, cm_writer, cm_gen_utils, cm_gen_plugins)
puts "Creating mock for #{name}..."
parsed_stuff = cm_parser.parse
cm_generator.create_mock(parsed_stuff)
@cm_generator.create_mock(name, @cm_parser.parse(File.read(src)))
end
end
# Command Line Support ###############################
if ($0 == __FILE__)
usage = "usage: ruby #{__FILE__} (-oOptionsFile) File(s)ToMock"
+3 -2
View File
@@ -5,13 +5,14 @@ class CMockConfig
{
:mock_path => 'mocks',
:mock_prefix => 'Mock',
:plugins => ['cexception', 'ignore'],
:plugins => [],
:includes => [],
:attributes => ['__ramfunc', '__irq', '__fiq'],
:attributes => ['__ramfunc', '__irq', '__fiq', 'register', 'extern'],
:enforce_strict_ordering => false,
:cexception_include => nil,
:unity_helper => false,
:treat_as => {},
:treat_as_void => [],
:memcmp_if_unknown => true,
:when_no_prototypes => :warn, #the options being :ignore, :warn, or :error
:when_ptr_star =>:compare_data, #the options being :compare_ptr, :compare_data, :compare_array
@@ -1,337 +0,0 @@
module CMockFunctionPrototype
module FunctionPrototypeUtils
def replace_brackets(string, replace='')
return string.gsub(/(\s*\[\s*[0-9]*\])+/, replace)
end
def make_cmock_arg_name(index)
return "cmock_arg#{index+1}"
end
def make_function_pointer_param_typedef_name(arg_list_index, function_name)
return "FUNC_PTR_#{function_name.upcase}_PARAM_#{arg_list_index+1}_T"
end
def make_function_pointer_return_typedef_name(function_name)
return "FUNC_PTR_#{function_name.upcase}_RETURN_T"
end
end
class FunctionPrototypeStandardNode < Treetop::Runtime::SyntaxNode
def get_declaration
return "#{get_return_type} #{get_function_name}#{argument_list.normalized_argument_list}"
end
def get_return_type
return return_type.text_value
end
# preformatted string for _Return statements e.g. "int toReturn"
def get_return_type_with_name
return "#{return_type.text_value} toReturn"
end
def get_function_name
return name.text_value
end
def get_argument_list
return argument_list.smart_argument_list
end
def get_arguments
return argument_list.arguments_array
end
def get_var_arg
return argument_list.var_arg
end
def get_typedefs
return argument_list.typedefs_array
end
end
class FunctionPrototypeFunctionPointerReturnNode < Treetop::Runtime::SyntaxNode
include FunctionPrototypeUtils
def get_declaration
return "#{return_type.text_value} (*#{get_function_name}#{function_arglist.normalized_argument_list})#{function_return_arglist.normalized_argument_list}"
end
def get_return_type
return make_function_pointer_return_typedef_name(get_function_name)
end
# preformatted string for _Return statements e.g. "void (*toReturn)(void)"
def get_return_type_with_name
return "#{return_type.text_value} (*toReturn)#{function_return_arglist.normalized_argument_list}"
end
def get_function_name
return name.text_value
end
def get_argument_list
return function_arglist.smart_argument_list
end
def get_arguments
return function_arglist.arguments_array
end
def get_var_arg
return function_arglist.var_arg
end
def get_typedefs
typename = make_function_pointer_return_typedef_name(get_function_name)
return ["typedef #{return_type.text_value} (*#{typename})#{function_return_arglist.normalized_argument_list};"]
end
end
class ArgumentListNode < Treetop::Runtime::SyntaxNode
include FunctionPrototypeUtils
def initialize(*params)
super(*params)
@var_arg_found = false
end
def var_arg
return '...' if @var_arg_found
return nil
end
# produce a simple argument list with pointers and white space normalized
# (i.e. don't add custom param names, etc.)
def normalized_argument_list
list = []
arguments.elements.each do |element|
list << element.argument.text_value
end
return '(void)' if (list.size == 0)
return '(void)' if (list.size == 1 and list[0] == 'void')
return '( ' + list.join(', ') + ' )'
end
# produce an argument list with pointers and white space normalized as well as auto-generated names for missing argument names
def smart_argument_list
list = []
arguments.elements.each_with_index do |element, index|
arg = element.argument
if (arg.class == CMockFunctionPrototype::TypeWithNameNode)
list << arg.type_and_smart_name_string(index)
elsif (arg.class == CMockFunctionPrototype::FunctionPointerNode)
list << arg.type_and_smart_name_string(index)
elsif (arg.class == CMockFunctionPrototype::VarArgNode)
@var_arg_found = true
# consume var args
else
list << arg.text_value
end
end
return 'void' if (list.size == 0)
return list.join(', ')
end
def arguments_array
list = []
arguments.elements.each_with_index do |element, index|
arg = element.argument
if (arg.class == CMockFunctionPrototype::TypeWithNameNode)
list << arg.type_and_smart_name_hash(index)
elsif (arg.class == CMockFunctionPrototype::FunctionPointerNode)
list << arg.type_and_smart_name_hash(index, self.parent.name.text_value)
elsif (arg.class == CMockFunctionPrototype::VarArgNode)
# consume var args
elsif (arg.class == CMockFunctionPrototype::VoidNode)
# consume void
else
end
end
return list
end
def typedefs_array
list = []
arguments.elements.each_with_index do |element, index|
arg = element.argument
if (arg.class == CMockFunctionPrototype::FunctionPointerNode)
list << arg.typedef(index, self.parent.name.text_value)
end
end
return list
end
end
class FunctionPointerNode < Treetop::Runtime::SyntaxNode
include FunctionPrototypeUtils
def text_value
name_and_args = get_deepest_name_and_args_node
return "#{return_type.text_value} (* const #{name_and_args.name.text_value})#{name_and_args.argument_list.normalized_argument_list}" if not (name_and_args.const.text_value.blank?)
return "#{return_type.text_value} (*#{name_and_args.name.text_value})#{name_and_args.argument_list.normalized_argument_list}"
end
def type_and_smart_name_string(arg_list_index)
name_and_args = get_deepest_name_and_args_node
func_ptr_name = name_and_args.name.text_value
if (name_and_args.name.text_value.blank?)
func_ptr_name = make_cmock_arg_name(arg_list_index)
end
return "#{return_type.text_value} (* const #{func_ptr_name})#{name_and_args.argument_list.normalized_argument_list}" if not (name_and_args.const.text_value.blank?)
return "#{return_type.text_value} (*#{func_ptr_name})#{name_and_args.argument_list.normalized_argument_list}"
end
def type_and_smart_name_hash(arg_list_index, function_name)
name_and_args = get_deepest_name_and_args_node
typename = make_function_pointer_param_typedef_name(arg_list_index, function_name)
return { :type => typename, :name => make_cmock_arg_name(arg_list_index) } if (name_and_args.name.text_value.blank?)
return { :type => typename, :name => name_and_args.name.text_value }
end
def typedef(arg_list_index, function_name)
name_and_args = get_deepest_name_and_args_node
typename = make_function_pointer_param_typedef_name(arg_list_index, function_name)
# don't place 'const' in typedef no matter if it exists or not;
# data types that comprise mock queues can't be const
return "typedef #{return_type.text_value} (*#{typename})#{name_and_args.argument_list.normalized_argument_list};"
end
private
# dive down into nested parentheses to pull out deepest node info
def get_deepest_name_and_args_node
node = name_and_args
while (node.respond_to?(:name_and_args))
node = node.name_and_args
end
return node
end
end
class TypeWithNameNode < Treetop::Runtime::SyntaxNode
include FunctionPrototypeUtils
def text_value
return "#{type.text_value} #{name.text_value}" if not name.text_value.blank?
return "#{type.text_value}"
end
def type_and_smart_name_string(arg_list_index)
if (name.text_value.blank?)
if (type.brackets?)
return "#{replace_brackets(type.text_value)} #{make_cmock_arg_name(arg_list_index)}#{type.get_brackets}"
end
return "#{type.text_value} #{make_cmock_arg_name(arg_list_index)}"
end
return "#{type.text_value} #{name.text_value}"
end
def type_and_smart_name_hash(arg_list_index)
if (name.text_value.blank?)
if (type.brackets?)
return { :type => replace_brackets(type.text_value_no_const, '*'), :name => make_cmock_arg_name(arg_list_index) }
end
return { :type => type.text_value_no_const, :name => make_cmock_arg_name(arg_list_index) }
end
if (name.brackets?)
return { :type => "#{type.text_value_no_const}*", :name => replace_brackets(name.text_value) }
end
return { :type => type.text_value_no_const, :name => name.text_value }
end
end
class NameWithBracketsNode < Treetop::Runtime::SyntaxNode
def text_value
return super.gsub(/\s+/, '')
end
def brackets?
return !brackets.text_value.blank?
end
def get_brackets
return brackets.text_value
end
end
class NameWithSpaceNode < Treetop::Runtime::SyntaxNode
def text_value
return super.strip
end
end
class TypeNode < Treetop::Runtime::SyntaxNode
include FunctionPrototypeUtils
def text_value
type = super
type.gsub!(/\s+/, ' ') # remove extra spaces
type.gsub!(/\s\*/, '*') # remove space preceding '*'
type.gsub!(/const\*/, 'const *') # space out 'const' and '*'
return type.strip
end
def text_value_no_const
return text_value.gsub(/(^|\s+)const($|\s+)/, '')
end
def brackets?
return !brackets.text_value.blank?
end
def get_brackets
return brackets.text_value
end
end
class ArrayBracketsNode < Treetop::Runtime::SyntaxNode
def text_value
return super.gsub(/\s+/, '')
end
end
class VoidNode < Treetop::Runtime::SyntaxNode
def text_value
return super.strip
end
end
class VarArgNode < Treetop::Runtime::SyntaxNode
def text_value
return super.strip
end
end
end
File diff suppressed because it is too large Load Diff
-176
View File
@@ -1,176 +0,0 @@
grammar CMockFunctionPrototype
rule function_prototype
function_prototype_function_pointer_return / function_prototype_standard
end
rule function_prototype_standard
return_type name argument_list <FunctionPrototypeStandardNode>
end
rule function_prototype_function_pointer_return
# ex. float (*GetPtr(const char opCode))(float, float)
# const: tag so we can always access its nodes in programming even if blank
return_type
left_paren asterisk name function_arglist:argument_list right_paren
function_return_arglist:argument_list
<FunctionPrototypeFunctionPointerReturnNode>
end
rule return_type
# void is different than the type possibility 'void*'
void / type
end
rule argument_list
# without a priori knowledge of all custom types (i.e. typedefs), the parser can only recognize
# a list of primitives followed by an optional name or a single custom type followed by an optional name.
# otherwise, there's no way to distinguish the last type in a list from a possibly non-existent argument name;
# the rules in the grammar enforce this idea to ensure the parser fails rather than do something wonky
left_paren arguments:( argument:argument (comma)? )* right_paren <ArgumentListNode>
end
rule argument
func_ptr_prototype / void / type_and_name / variable_argument
end
rule variable_argument
'...' space_optional <VarArgNode>
end
rule func_ptr_prototype
# ex. int (*funcPtr)(float, char, char)
return_type name_and_args:parenthesized_func_ptr_name_with_arglist <FunctionPointerNode>
end
rule parenthesized_func_ptr_name_with_arglist
# - allow nested parentheses around name and arglist
# - use labels 'const:' and 'name:' to give programmatic access even if nodes aren't found in string
(left_paren name_and_args:parenthesized_func_ptr_name_with_arglist right_paren) /
(left_paren asterisk const:const? name:name? right_paren argument_list)
end
rule type_and_name
# - name? tagged with name: so we can always access it in programming even if blank
# - tell parser that type and optional name will always be followed by ',' '(' or ')' but don't consume them;
# this helps enforce the limits on what can be parsed in argument lists
type name:name_with_brackets? &( comma / left_paren / right_paren ) <TypeWithNameNode>
end
rule type
const?
( type_struct_union_enum /
type_void_ptr /
type_primitive /
type_custom )
brackets:array_brackets? <TypeNode>
end
rule type_struct_union_enum
('struct' / 'union' / 'enum') space_mandatory name_no_space type_const_and_ptr_suffix? space_optional
end
rule type_void_ptr
# note that type_const_and_ptr_suffix is mandatory unlike other type rules because
# rule represents void pointer (i.e. must contain a '*' by definition)
'void' type_const_and_ptr_suffix space_optional
end
rule type_primitive
# - at least one clause has to exist, not all can be '?' optional;
# hence the long and int variations instead of an optional long followed by optional everything else
# - !name_no_space limits matches to only primitives allowing custom types to be successfully
# recognized though their strings may begin with primitives (ex. integer value1, character value2)
(('unsigned' / 'signed') space_mandatory)?
(('long' space_mandatory 'int') / ('long' space_mandatory 'long') / 'long' / 'int' / 'short' / 'char' / 'float' / 'double') !name_no_space
type_const_and_ptr_suffix?
space_optional
end
rule type_custom
name_no_space type_const_and_ptr_suffix? space_optional
end
rule type_const_and_ptr_suffix
# variants of "const", "const *", "* const", "const * const", & "*"
# (of course, more than one asterisk possible in pointer cases)
(space_mandatory const asterisk+ lookahead_const) /
(space_mandatory const asterisk+) /
(space_optional asterisk+ lookahead_const) /
(space_optional asterisk+) /
(space_optional lookahead_const)
end
rule lookahead_const
# fancy lookahead statement that allows const type suffix to be distinguished from a parameter
# name that might begin with word 'const' (ex. "int const_param" or "char* const constant")
'const' ( (space_mandatory &name) / (space_optional &comma) / (space_optional &right_paren) / (space_optional &left_paren) )
end
rule name
alpha_numeric+ space_optional <NameWithSpaceNode>
end
rule name_no_space
alpha_numeric+
end
rule name_with_brackets
name brackets:array_brackets? <NameWithBracketsNode>
end
rule void
# in reality, 'void' is different than 'void*' type so limit to 'void' here and handle 'void*' in type rule;
# recognizing the VoidNode uniquely in programming is helpful
'void' space_optional !asterisk <VoidNode>
end
rule array_brackets
left_bracket right_bracket (left_bracket number right_bracket)* <ArrayBracketsNode>
end
rule const
'const' space_optional
end
rule asterisk
'*' space_optional
end
rule left_paren
'(' space_optional
end
rule right_paren
')' space_optional
end
rule left_bracket
'[' space_optional
end
rule right_bracket
']' space_optional
end
rule comma
',' space_optional
end
rule alpha_numeric
[a-zA-Z0-9_]
end
rule number
[0-9] space_optional
end
rule space_mandatory
' '+
end
rule space_optional
' '*
end
end
+9 -10
View File
@@ -2,19 +2,20 @@ $here = File.dirname __FILE__
class CMockGenerator
attr_reader :config, :file_writer, :module_name, :mock_name, :utils, :plugins
attr_accessor :config, :file_writer, :module_name, :mock_name, :utils, :plugins
def initialize(config, module_name, file_writer, utils, plugins=[])
def initialize(config, file_writer, utils, plugins=[])
@file_writer = file_writer
@module_name = module_name
@utils = utils
@plugins = plugins
@config = config
@mock_name = @config.mock_prefix + @module_name
@prefix = @config.mock_prefix
@ordered = @config.enforce_strict_ordering
end
def create_mock(parsed_stuff)
def create_mock(module_name, parsed_stuff)
@module_name = module_name
@mock_name = @prefix + @module_name
create_mock_header_file(parsed_stuff)
create_mock_source_file(parsed_stuff)
end
@@ -25,6 +26,7 @@ class CMockGenerator
@file_writer.create_file(@mock_name + ".h") do |file, filename|
create_mock_header_header(file, filename)
create_mock_header_service_call_declarations(file)
create_typedefs(file, parsed_stuff[:typedefs])
parsed_stuff[:functions].each do |function|
file << @plugins.run(:mock_function_declarations, function)
end
@@ -35,7 +37,6 @@ class CMockGenerator
def create_mock_source_file(parsed_stuff)
@file_writer.create_file(@mock_name + ".c") do |file, filename|
create_source_header_section(file, filename)
create_source_typedefs(file, parsed_stuff[:functions])
create_instance_structure(file, parsed_stuff[:functions])
create_extern_declarations(file)
create_mock_verify_function(file, parsed_stuff[:functions])
@@ -57,11 +58,9 @@ class CMockGenerator
file << "#include \"#{orig_filename}\"\n\n"
end
def create_source_typedefs(file, functions)
def create_typedefs(file, typedefs)
file << "\n"
functions.each do |function|
function[:typedefs].each {|typedef| file << "#{typedef}\n" }
end
typedefs.each {|typedef| file << "#{typedef}\n" }
file << "\n\n"
end
+176 -96
View File
@@ -1,133 +1,213 @@
class CMockHeaderParser
attr_reader :src_lines, :prototypes, :attributes
attr_accessor :funcs, :c_attributes, :treat_as_void
def initialize(parser, source, config, name)
@src_lines = []
@prototypes = []
@function_names = []
@prototype_parse_matcher = /([\d\w\s\*\(\),\[\]]+??)\(([\d\w\s\*\(\),\.\[\]]*)\)$/m
@attributes = config.attributes
@when_no_prototypes = config.when_no_prototypes
@parser = parser
@name = name
import_source(source)
def initialize(cfg)
@funcs = []
@c_attributes = (['const'] + cfg.attributes).uniq
@treat_as_void = (['void'] + cfg.treat_as_void).uniq
@declaration_parse_matcher = /([\d\w\s\*\(\),\[\]]+??)\(([\d\w\s\*\(\),\.\[\]]*)\)$/m
@standards = (['int','short','char','long','unsigned','signed'] + cfg.treat_as.keys).uniq
@when_no_prototypes = cfg.when_no_prototypes
@local_as_void = @treat_as_void
end
def parse
hash = {:functions => []}
# build prototype list
extract_prototypes
# parse all prototyes into hashes of components and add to array
@prototypes.each do |prototype|
parsed_hash = parse_prototype(prototype)
# protect against multiple prototypes (can happen when externs are pulled into preprocessed headers)
if (!@function_names.include?(parsed_hash[:name]))
@function_names << parsed_hash[:name]
hash[:functions] << parsed_hash
def parse(source)
@typedefs = []
@funcs = []
function_names = []
parse_functions( import_source(source) ).map do |decl|
func = parse_declaration(decl)
unless (function_names.include? func[:name])
@funcs << func
function_names << func[:name]
end
end
return hash
{ :includes => nil,
:functions => @funcs,
:typedefs => @typedefs
}
end
private
private unless $ThisIsOnlyATest ################
def import_source(source)
# look for any edge cases of typedef'd void;
# void must be void for cmock _ExpectAndReturn calls to process properly.
# to a certain extent, this action assumes we're chewing on pre-processed header files
void_types = source.scan(/typedef\s+(\(\s*)?void(\s*\))?\s+([\w\d]+)\s*;/)
# void must be void for cmock _ExpectAndReturn calls to process properly, not some weird typedef which equates to void
# to a certain extent, this action assumes we're chewing on pre-processed header files, otherwise we'll most likely just get stuff from @treat_as_void
@local_as_void = @treat_as_void
void_types = source.scan(/typedef\s+(?:\(\s*)?void(?:\s*\))?\s+([\w\d]+)\s*;/)
if void_types
void_types = void_types.flatten.uniq - ['(',')',nil]
void_types.each {|type| source.gsub!(/#{type}/, 'void')} if void_types.size > 0
@local_as_void += void_types.flatten.uniq.compact
end
# smush multiline macros into single line (checking for continuation character at end of line '\')
source.gsub!(/\s*\\\s*/m, ' ')
#remove comments (block and line, in three steps to ensure correct precedence)
source.gsub!(/\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks
source.gsub!(/\/\*.*?\*\//m, '') # remove block comments
source.gsub!(/\/\/.*$/, '') # remove line comments (all that remain)
# remove preprocessor statements
source.gsub!(/^\s*#.*/, '')
# enums, unions, structs, and typedefs can all contain things (e.g. function pointers) that parse like function prototypes, so yank them
# forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes
source.gsub!(/^[\w\s]*struct[^;\{\}\(\)]+;/m, '') # remove forward declared structs
source.gsub!(/^[\w\s]*(enum|union|struct)[\w\s]*\{[^\}]+\}[\w\s]*;/m, '') # remove struct definitions
source.gsub!(/(\W)(register|auto|static|restrict)(\W)/, '\1\3') # remove problem keywords
source.gsub!(/\s*=\s*['"a-zA-Z0-9_\.]+\s*/, '') # remove default value statements from argument lists
source.gsub!(/typedef.*/, '') # remove typedef statements
#scan for functions which return function pointers, because they are a pain
source.gsub!(/([\w\s]+)\(*\(\s*\*([\w\s]+)\s*\(([\w\s,]+)\)\)\s*\(([\w\s,]+)\)\)*/) do |m|
functype = "cmock_func_ptr#{@typedefs.size + 1}"
@typedefs << "typedef #{$1.strip}(*#{functype})(#{$4});"
"#{functype} #{$2.strip}(#{$3});"
end
source.gsub!(/\s*\\\s*/m, ' ') # smush multiline statements into single line
source.gsub!(/\/\*.*?\*\//m, '') # remove block comments (do it first to avoid trouble with embedded line comments)
source.gsub!(/\/\/.*$/, '') # remove line comments
source.gsub!(/#.*/, '') # remove preprocessor statements
# unions, structs, and typedefs can all contain things (e.g. function pointers) that parse like function prototypes, so yank them;
# enums might cause trouble or might not - pull 'em just to be safe
source.gsub!(/^[\w\s]*enum[\w\s]*\{[^\}]+\}[\w\s]*;/m, '') # remove enum definitions
source.gsub!(/^[\w\s]*union[\w\s]*\{[^\}]+\}[\w\s]*;/m, '') # remove union definitions
source.gsub!(/^[\w\s]*struct[^;\{\}\(\)]+;/m, '') # remove forward declared structs but leave function prototypes having struct in types
# (do before struct definitions so as to not mess up recognizing full struct definitions)
source.gsub!(/^[\w\s]*struct[\w\s]*\{[^\}]+\}[\w\s]*;/m, '') # remove struct definitions
source.gsub!(/typedef.*/, '') # remove typedef statements
source.gsub!(/\s*=\s*['"a-zA-Z0-9_\.]+\s*/, '') # remove default value statements from argument lists
#drop extra white space to make the rest go faster
source.gsub!(/^\s+/, '') # remove extra white space from beginning of line
source.gsub!(/\s+$/, '') # remove extra white space from end of line
source.gsub!(/\s*\(\s*/, '(') # remove extra white space from before left parens
source.gsub!(/\s*\)\s*/, ')') # remove extra white space from before right parens
source.gsub!(/\s+/, ' ') # remove remaining extra white space
# split source at end of statements (removing any remaining extra white space)
@src_lines = source.split(/\s*;\s*/)
# remove function pointer array declarations (they're erroneously recognized as function prototypes);
# look for something like (* blah [#]) - this can't be a function parameter list
@src_lines.delete_if {|line| !(line =~ /\(\s*\*(.*\[\d*\])??\s*\)/).nil?}
# remove functions that are externed - mocking an extern'd function in a header file is a weird condition
@src_lines.delete_if {|line| !(line =~ /(^|\s+)extern\s+/).nil?}
# remove functions that are inlined - mocking an inine function will either break compilation or lead to other oddities
@src_lines.delete_if {|line| !(line =~ /(^|\s+)inline\s+/).nil?}
# remove blank lines
@src_lines.delete_if {|line| line.strip.length == 0}
#split lines on semicolons and remove things that are obviously not what we are looking for
src_lines = source.split(/\s*;\s*/)
src_lines.delete_if {|line| !(line =~ /\(\s*\*(?:.*\[\d*\])??\s*\)/).nil?} #remove function pointer arrays
src_lines.delete_if {|line| !(line =~ /(?:^|\s+)(?:extern|inline)\s+/).nil?} #remove inline and extern functions
src_lines.delete_if {|line| line.strip.length == 0} # remove blank lines
end
def extract_prototypes
# build array of function prototypes
@src_lines.each do |line|
@prototypes << line if (line =~ @prototype_parse_matcher)
end
if @prototypes.empty?
def parse_functions(source)
funcs = []
source.each {|line| funcs << line.strip.gsub(/\s+/, ' ') if (line =~ @declaration_parse_matcher)}
if funcs.empty?
case @when_no_prototypes
when :error
raise "ERROR: No function prototypes found in '#{@name}'"
raise "ERROR: No function prototypes found!"
when :warn
puts "WARNING: No function prototypes found in '#{@name}'"
puts "WARNING: No function prototypes found!"
end
end
return funcs
end
def parse_args(arg_list)
args = []
arg_list.split(',').each do |arg|
arg.strip!
return args if (arg =~ /^\s*((\.\.\.)|(void))\s*$/) # we're done if we reach void by itself or ...
arg_elements = arg.split - @c_attributes # split up words and remove known attributes
args << {:type => arg_elements[0..-2].join(' '), :name => arg_elements[-1]} # add the lucky winners to the list
end
return args
end
def clean_args(arg_list)
if ((@local_as_void.include?(arg_list.strip)) or (arg_list.empty?))
return 'void'
else
c=0
arg_list.gsub!(/(\w)\s*\[[\s\d]*\]/,'*\1') # magically turn brackets into asterisks
arg_list.gsub!(/\s+\*/,'*') # remove space to place asterisks with type (where they belong)
arg_list.gsub!(/\*(\w)/,'* \1') # pull asterisks away from arg to place asterisks with type (where they belong)
#scan argument list for function pointers and replace them with custom types
arg_list.gsub!(/([\w\s]+)\(*\(\s*\*([\w\s]+)\)\s*\(([\w\s,]+)\)\)*/) do |m|
functype = "cmock_func_ptr#{@typedefs.size + 1}"
funcret = $1.strip
funcname = $2.strip
funcargs = $3.strip
funconst = ''
if (funcname.include? 'const')
funcname.gsub!('const','').strip!
funconst = 'const '
end
@typedefs << "typedef #{funcret}(*#{functype})(#{funcargs});"
funcname = "cmock_arg#{c+=1}" if (funcname.empty?)
"#{functype} #{funconst}#{funcname}"
end
#automatically name unnamed arguments (those that only had a type)
arg_list.split(/\s*,\s*/).map { |arg|
parts = (arg.split - ['signed', 'unsigned', 'struct', 'union', 'enum', 'const', 'const*'])
if ((parts.size < 2) or (parts[-1][-1] == 42) or (@standards.include?(parts[-1])))
"#{arg} cmock_arg#{c+=1}"
else
arg
end
}.join(', ')
end
end
def parse_prototype(prototype)
hash = {}
def parse_declaration(declaration)
decl = {}
regex_match = @declaration_parse_matcher.match(declaration)
raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil?
modifiers = []
# grab special attributes from function prototype and remove them from prototype
@attributes.each do |attribute|
if (prototype =~ /#{attribute}\s+/)
modifiers << attribute
prototype.gsub!(/#{attribute}\s+/, '')
#grab argument list
args = regex_match[2].strip
#process function attributes, return type, and name
descriptors = regex_match[1]
descriptors.gsub!(/\s+\*/,'*') #remove space to place asterisks with return type (where they belong)
descriptors.gsub!(/\*(\w)/,'* \1') #pull asterisks away from function name to place asterisks with return type (where they belong)
descriptors = descriptors.split #array of all descriptor strings
#grab name
decl[:name] = descriptors[-1] #snag name as last array item
#build attribute and return type strings
decl[:modifier] = []
decl[:return_type] = []
descriptors[0..-2].each do |word|
if @c_attributes.include?(word)
decl[:modifier] << word
else
decl[:return_type] << word
end
end
hash[:modifier] = modifiers.join(' ')
# excise these keywords from prototype (entire 'inline' and 'extern' prototypes are already gone)
['auto', 'register', 'static', 'restrict', 'volatile'].each do |keyword|
prototype.gsub!(/(,\s*|\(\s*|\*\s*|^\s*|\s+)#{keyword}\s*(,|\)|\w)/, "\\1\\2")
decl[:modifier] = decl[:modifier].join(' ')
decl[:return_type] = decl[:return_type].join(' ')
decl[:return_type] = 'void' if (@local_as_void.include?(decl[:return_type].strip))
decl[:return_string] = decl[:return_type] + " toReturn"
#remove default argument statements from mock definitions
args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*\,/, ',')
args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*/, ' ')
#check for var args
if (args =~ /\.\.\./)
decl[:var_arg] = args.match( /[\w\s]*\.\.\./ ).to_s.strip
if (args =~ /\,[\w\s]*\.\.\./)
args = args.gsub!(/\,[\w\s]*\.\.\./,'')
else
args = 'void'
end
else
decl[:var_arg] = nil
end
args = clean_args(args)
decl[:args_string] = args
decl[:args] = parse_args(args)
if (decl[:return_type].nil? or decl[:name].nil? or decl[:args].nil? or
decl[:return_type].empty? or decl[:name].empty?)
raise "Failed Parsing Declaration Prototype!\n" +
" declaration: #{declaration}\n" +
" modifier: #{decl[:modifier]}\n" +
" return: #{decl[:return_type]}\n" +
" function: #{decl[:name]}\n" +
" args:#{decl[:args]}\n"
end
parsed = @parser.parse(prototype)
raise "Failed parsing function prototype: '#{prototype}' in file '#{@name}'" if parsed.nil?
hash[:name] = parsed.get_function_name
hash[:args_string] = parsed.get_argument_list
hash[:args] = parsed.get_arguments
hash[:return_type] = parsed.get_return_type
hash[:return_string] = parsed.get_return_type_with_name
hash[:var_arg] = parsed.get_var_arg
hash[:typedefs] = parsed.get_typedefs
return hash
return decl
end
end
-13
View File
@@ -23,19 +23,6 @@ task :config, :config_file do |t, args|
configure_toolchain(args[:config_file])
end
desc "Generate parser(s) from Treetop grammar(s)"
task :treetop do
require 'rubygems'
require 'treetop'
treetop_files = FileList.new('lib/*.treetop')
compiler = Treetop::Compiler::GrammarCompiler.new
treetop_files.each do |file|
compiler.compile(file)
end
end
namespace :test do
desc "Run all unit and system tests"
task :all => ['test:units', 'test:system']
+1 -1
View File
@@ -326,7 +326,7 @@ module RakefileHelpers
mockables.each do |header|
mock_filename = 'mock_' + File.basename(header).ext('.c')
profile_this(mock_filename.gsub('.c','')) do
2.times do
10.times do
CMock.new(SYSTEST_COMPILE_MOCKABLES_PATH + 'config.yml').setup_mocks(header)
end
end
+4 -1
View File
@@ -1,7 +1,10 @@
---
:cmock:
:plugins:
-
- #none
:includes:
:mock_path: test/system/generated/
:mock_prefix: mock_
:treat_as_void:
- OSEK_TASK
- VOID_TYPE_CRAZINESS
+2 -3
View File
@@ -109,7 +109,7 @@ void OSEKOS_ISR_LINError_SCI1(void);
void OSEKOS_ISR_SysCounter(void);
// defined multiple times (slightly different forms)
// defined multiple times (slightly different forms) These should be ignored because they are externed
extern void OSEKOS_ISR_CanTxInterrupt( void );
extern void OSEKOS_ISR_CanRxInterrupt( void );
@@ -242,7 +242,7 @@ void OSEKOSV850SyncContextLoadFromIRQ(OSEKOSSaveType);
void OSEKOSV850ASyncContextLoad(OSEKOSSaveType);
void OSEKOSV850ASyncContextLoadFromIRQ(OSEKOSSaveType);
// arrays of function pointers - the look like function prototypes
// arrays of function pointers - they look like function prototypes
void ( ( * const OSEKOStaskStartAddress [10] ) ( void ) );
StatusType (* OSEKOStaskStatuses [10][5]) ( void );
@@ -257,7 +257,6 @@ void OSEKOSV850StartContextFromIRQ
OSEK_U8 * const
);
void OSEKOSSuspendOSInterrupts(void);
void OSEKOSResumeOSInterrupts(void);
void OSEKOSSuspendAllInterrupts(void);
@@ -2,7 +2,9 @@
:cmock:
:plugins:
- # no plugins
:treat_as_void:
- VOID_TYPE_CRAZINESS_CFG
:systest:
:types: |
typedef unsigned short U16;
@@ -11,14 +13,15 @@
int x;
int y;
} POINT_T;
typedef void VOID_TYPE_CRAZINESS_CFG;
:mockable: |
// typedef edge case; must be in mockable.h for test to compile
// not ANSI C but it has been done and will break cmock if not handled
typedef void VOID_TYPE_CRAZINESS;
typedef void VOID_TYPE_CRAZINESS_LCL;
VOID_TYPE_CRAZINESS void_type_craziness1(int * a, int *b, int* c);
void void_type_craziness2(VOID_TYPE_CRAZINESS);
VOID_TYPE_CRAZINESS_LCL void_type_craziness1(int * a, int *b, int* c);
void void_type_craziness2(VOID_TYPE_CRAZINESS_CFG);
// pointer parsing exercise
U16 *ptr_return1(int a);
+8
View File
@@ -1,5 +1,13 @@
require File.expand_path(File.dirname(__FILE__)) + "/../config/test_environment"
#gem install test-unit -v 1.2.3
ruby_version = RUBY_VERSION.split('.')
if (ruby_version[1].to_i == 9) and (ruby_version[2].to_i > 1)
require 'gems'
gem 'test-unit'
end
require 'test/unit'
require 'behaviors'
require 'hardmock'
@@ -1,348 +0,0 @@
require File.expand_path(File.dirname(__FILE__)) + "/../test_helper"
require 'rubygems'
require 'treetop'
require 'cmock_function_prototype_node_classes'
require 'cmock_function_prototype_parser'
class CMockFunctionPrototypeParserTest < Test::Unit::TestCase
def setup
@parser = CMockFunctionPrototypeParser.new
end
def teardown
end
should "parse simple void function prototypes" do
parsed = @parser.parse("void foo_bar(void)")
assert_equal('void foo_bar(void)', parsed.get_declaration)
assert_equal('void', parsed.get_return_type)
assert_equal('foo_bar', parsed.get_function_name)
assert_equal('void', parsed.get_argument_list)
assert_equal([], parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar()")
assert_equal('void foo_bar(void)', parsed.get_declaration)
assert_equal('void', parsed.get_return_type)
assert_equal('foo_bar', parsed.get_function_name)
assert_equal('void', parsed.get_argument_list)
assert_equal([], parsed.get_arguments)
assert_nil(parsed.get_var_arg)
end
should "fail to parse garbage, broken function prototypes, and strings that only appear to be prototypes" do
assert_nil(@parser.parse("** !"))
assert_nil(@parser.parse("ashjfhskdh"))
assert_nil(@parser.parse("void")) # no function name or argument list
assert_nil(@parser.parse("void foo-bar(void)")) # illegal function name
assert_nil(@parser.parse("void foo_bar")) # no param list
assert_nil(@parser.parse("foo_bar(void)")) # no return type
assert_nil(@parser.parse("void ( (* const pointers[])(void) )")) # looks like function prototype but is actually array of function pointers
assert_nil(@parser.parse("void foo_bar(int (func)(int a, char b), void (*)(void))")) # no asterisk in function pointer definition
assert_nil(@parser.parse("unsigned int * (*(double foo, THING bar))(unsigned int a)")) # no function name
assert_nil(@parser.parse("unsigned int * (* func(double foo, THING bar))")) # no parameter list for function pointer return
assert_nil(@parser.parse("typedef void (*FUNCPTR)(void)")) # typedef string that looks like function prototype
assert_nil(@parser.parse("(parenthetical comment)"))
end
should "parse and normalize white space" do
parsed = @parser.parse("void foo_bar ( void )")
assert_equal("void foo_bar(void)", parsed.get_declaration)
parsed = @parser.parse("void foo_bar( int a,int b)")
assert_equal("void foo_bar( int a, int b )", parsed.get_declaration)
parsed = @parser.parse("void foo_bar( int a, int b, int , unsigned int d)")
assert_equal("void foo_bar( int a, int b, int, unsigned int d )", parsed.get_declaration)
parsed = @parser.parse("unsigned int foo_bar(unsigned char * const )")
assert_equal("unsigned int foo_bar( unsigned char* const )", parsed.get_declaration)
parsed = @parser.parse("int foo_bar(const unsigned char * * ptr )")
assert_equal("int foo_bar( const unsigned char** ptr )", parsed.get_declaration)
parsed = @parser.parse("void foo_bar ( int (* function) (int, char ), void ( * ) (void ) )")
assert_equal("void foo_bar( int (*function)( int, char ), void (*)(void) )", parsed.get_declaration)
parsed = @parser.parse("float ( * GetPtr( const char opCode))( float, float)")
assert_equal("float (*GetPtr( const char opCode ))( float, float )", parsed.get_declaration)
end
should "parse out arguments from an argument list into an array of hashes" do
# function pointers & var args tested elsewhere
# void is a special argument that yields no params to mock
parsed = @parser.parse("void foo_bar(void)")
assert_equal('void', parsed.get_argument_list)
assert_equal([], parsed.get_arguments)
parsed = @parser.parse("void foo_bar(int a, unsigned int b)")
assert_equal('int a, unsigned int b', parsed.get_argument_list)
assert_equal([
{:type => 'int', :name => 'a'},
{:type => 'unsigned int', :name => 'b'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(double a, float b, unsigned short c)")
assert_equal('double a, float b, unsigned short c', parsed.get_argument_list)
assert_equal([
{:type => 'double', :name => 'a'},
{:type => 'float', :name => 'b'},
{:type => 'unsigned short', :name => 'c'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(struct THINGER * a, struct JIMBOB b)")
assert_equal('struct THINGER* a, struct JIMBOB b', parsed.get_argument_list)
assert_equal([
{:type => 'struct THINGER*', :name => 'a'},
{:type => 'struct JIMBOB', :name => 'b'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(union STARS_AND_STRIPES * a, union AFL_CIO b)")
assert_equal('union STARS_AND_STRIPES* a, union AFL_CIO b', parsed.get_argument_list)
assert_equal([
{:type => 'union STARS_AND_STRIPES*', :name => 'a'},
{:type => 'union AFL_CIO', :name => 'b'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
# make sure primitve types in param names don't gum up the parsing works
parsed = @parser.parse("void foo_bar(const unsigned int const_param, int int_param, int integer, char character, int* const constant)")
assert_equal('const unsigned int const_param, int int_param, int integer, char character, int* const constant', parsed.get_argument_list)
assert_equal([
{:type => 'unsigned int', :name => 'const_param'},
{:type => 'int', :name => 'int_param'},
{:type => 'int', :name => 'integer'},
{:type => 'char', :name => 'character'},
{:type => 'int*', :name => 'constant'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
# make sure custom types containing primitive names don't gum up the parsing works
parsed = @parser.parse("void foo_bar(integer param, character thing, longint * junk, constant value, int32_t const number)")
assert_equal('integer param, character thing, longint* junk, constant value, int32_t const number', parsed.get_argument_list)
assert_equal([
{:type => 'integer', :name => 'param'},
{:type => 'character', :name => 'thing'},
{:type => 'longint*', :name => 'junk'},
{:type => 'constant', :name => 'value'},
{:type => 'int32_t', :name => 'number'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(signed char * abc, const unsigned long int xyz_123, unsigned int const abc_123, long long arm_of_the_law)")
assert_equal('signed char* abc, const unsigned long int xyz_123, unsigned int const abc_123, long long arm_of_the_law', parsed.get_argument_list)
assert_equal([
{:type => 'signed char*', :name => 'abc'},
{:type => 'unsigned long int', :name => 'xyz_123'},
{:type => 'unsigned int', :name => 'abc_123'},
{:type => 'long long', :name => 'arm_of_the_law'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(CUSTOM_TYPE abc, CUSTOM_TYPE* xyz_123, CUSTOM_TYPE const abcxyz, struct CUSTOM_TYPE const * const abc123)")
assert_equal('CUSTOM_TYPE abc, CUSTOM_TYPE* xyz_123, CUSTOM_TYPE const abcxyz, struct CUSTOM_TYPE const * const abc123', parsed.get_argument_list)
assert_equal([
{:type => 'CUSTOM_TYPE', :name => 'abc'},
{:type => 'CUSTOM_TYPE*', :name => 'xyz_123'},
{:type => 'CUSTOM_TYPE', :name => 'abcxyz'},
{:type => 'struct CUSTOM_TYPE*', :name => 'abc123'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(CUSTOM_TYPE thing1[], int thing2 [ ], char thing3 [][2 ][ 3])")
assert_equal('CUSTOM_TYPE thing1[], int thing2[], char thing3[][2][3]', parsed.get_argument_list)
assert_equal([
{:type => 'CUSTOM_TYPE*', :name => 'thing1'},
{:type => 'int*', :name => 'thing2'},
{:type => 'char*', :name => 'thing3'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
end
should "fail to parse arguments that mix multiple custom types or a custom type and a primitive" do
# parser can only recognize strings of primitves followed by optional name or
# a single custom type followed by optional name;
# without knowing custom types a priori there's no way to parse all possible combinations
assert_nil(@parser.parse("void foo_bar(unsigned CUSTOM_TYPE abc)"))
assert_nil(@parser.parse("void foo_bar(CUSTOM_TYPE1 CUSTOM_TYPE2 abc)"))
assert_nil(@parser.parse("void foo_bar(CUSTOM_TYPE, CUSTOM_TYPE1 CUSTOM_TYPE2 abc)"))
assert_nil(@parser.parse("void foo_bar(CUSTOM_TYPE1 CUSTOM_TYPE2 abc, CUSTOM_TYPE1 CUSTOM_TYPE2 xyz)"))
end
should "parse out simple return types" do
# function pointers tested elsewhere
parsed = @parser.parse("void foo_bar(void)")
assert_equal('void', parsed.get_return_type)
parsed = @parser.parse("void * foo_bar(void)")
assert_equal('void*', parsed.get_return_type)
assert_equal("void* toReturn", parsed.get_return_type_with_name)
parsed = @parser.parse("unsigned int foo_bar(void)")
assert_equal('unsigned int', parsed.get_return_type)
assert_equal("unsigned int toReturn", parsed.get_return_type_with_name)
parsed = @parser.parse("unsigned long int foo_bar(void)")
assert_equal('unsigned long int', parsed.get_return_type)
assert_equal("unsigned long int toReturn", parsed.get_return_type_with_name)
parsed = @parser.parse("CUSTOM_TYPE foo_bar(void)")
assert_equal('CUSTOM_TYPE', parsed.get_return_type)
assert_equal("CUSTOM_TYPE toReturn", parsed.get_return_type_with_name)
end
should "normalize pointer notation" do
parsed = @parser.parse("void * foo(unsigned int * * * a, char * *b, int* c, int (* func)(void), CUSTOM_TYPE const* e)")
assert_equal('void*', parsed.get_return_type)
assert_equal('unsigned int*** a, char** b, int* c, int (*func)(void), CUSTOM_TYPE const * e', parsed.get_argument_list)
assert_equal([
{:type => 'unsigned int***', :name => 'a'},
{:type => 'char**', :name => 'b'},
{:type => 'int*', :name => 'c'},
{:type => 'FUNC_PTR_FOO_PARAM_4_T', :name => 'func'},
{:type => 'CUSTOM_TYPE*', :name => 'e'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
end
should "specially process var args in preparation for mocking" do
parsed = @parser.parse("void foo_bar(...)")
assert_equal('void', parsed.get_argument_list)
assert_equal([], parsed.get_arguments)
assert_equal('...', parsed.get_var_arg)
parsed = @parser.parse("void foo_bar(int a, ...)")
assert_equal('int a', parsed.get_argument_list)
assert_equal(
[{:type => 'int', :name => 'a'}],
parsed.get_arguments)
assert_equal('...', parsed.get_var_arg)
parsed = @parser.parse("void thing(void (*func)(int, ...))")
assert_equal('void (*func)( int, ... )', parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_THING_PARAM_1_T', :name => 'func'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg) # no var args for thing(), just for the function pointer param
end
should "parse prototypes handling function pointers" do
# function pointer prototypes in argument lists (i.e. no typedef)
parsed = @parser.parse("void thing(int (*func_ptr)(int, int))")
assert_equal('void thing( int (*func_ptr)( int, int ) )', parsed.get_declaration)
assert_equal('int (*func_ptr)( int, int )', parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_THING_PARAM_1_T', :name => 'func_ptr'}],
parsed.get_arguments)
parsed = @parser.parse("void foo(int (* const func_ptr)(int, int))")
assert_equal('void foo( int (* const func_ptr)( int, int ) )', parsed.get_declaration)
assert_equal('int (* const func_ptr)( int, int )', parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_FOO_PARAM_1_T', :name => 'func_ptr'}],
parsed.get_arguments)
parsed = @parser.parse("void foo_bar(void * (*func)(int *, unsigned long int, ...))")
assert_equal('void foo_bar( void* (*func)( int*, unsigned long int, ... ) )', parsed.get_declaration)
assert_equal('void* (*func)( int*, unsigned long int, ... )', parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_FOO_BAR_PARAM_1_T', :name => 'func'}],
parsed.get_arguments)
# note nested parens around name and arg list of first function pointer param
parsed = @parser.parse("void foo_bar(int (((* func1)(int, char))), void (*func2)(void))")
assert_equal('void foo_bar( int (*func1)( int, char ), void (*func2)(void) )', parsed.get_declaration)
assert_equal('int (*func1)( int, char ), void (*func2)(void)', parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_FOO_BAR_PARAM_1_T', :name => 'func1'},
{:type => 'FUNC_PTR_FOO_BAR_PARAM_2_T', :name => 'func2'}],
parsed.get_arguments)
# directly returning function pointers (i.e. no typedef)
parsed = @parser.parse("float (*func(const char opCode))(float, float)")
assert_equal('float (*func( const char opCode ))( float, float )', parsed.get_declaration)
assert_equal('FUNC_PTR_FUNC_RETURN_T', parsed.get_return_type)
assert_equal("float (*toReturn)( float, float )", parsed.get_return_type_with_name)
parsed = @parser.parse("void (* func (void))(void)")
assert_equal('void (*func(void))(void)', parsed.get_declaration)
assert_equal('FUNC_PTR_FUNC_RETURN_T', parsed.get_return_type)
assert_equal("void (*toReturn)(void)", parsed.get_return_type_with_name)
parsed = @parser.parse("unsigned int * (* func(double foo, THING bar))(unsigned int)")
assert_equal('unsigned int* (*func( double foo, THING bar ))( unsigned int )', parsed.get_declaration)
assert_equal('FUNC_PTR_FUNC_RETURN_T', parsed.get_return_type)
assert_equal("unsigned int* (*toReturn)( unsigned int )", parsed.get_return_type_with_name)
end
should "create unique typedefs for function pointer prototypes in argument lists and return types" do
# function prototype argument list handling
parsed = @parser.parse("void foo_bar(unsigned int a, void (* const func)(int *, unsigned long int, ...))")
assert_equal(
['typedef void (*FUNC_PTR_FOO_BAR_PARAM_2_T)( int*, unsigned long int, ... );'],
parsed.get_typedefs)
parsed = @parser.parse("void test_func(void (*)(int, char), unsigned int (*)(void))")
assert_equal(
['typedef void (*FUNC_PTR_TEST_FUNC_PARAM_1_T)( int, char );',
'typedef unsigned int (*FUNC_PTR_TEST_FUNC_PARAM_2_T)(void);'],
parsed.get_typedefs)
# function prototype return type handling
parsed = @parser.parse("void (* func (void))(void)")
assert_equal(
['typedef void (*FUNC_PTR_FUNC_RETURN_T)(void);'],
parsed.get_typedefs)
parsed = @parser.parse("unsigned int * (* func(double foo, THING bar))(unsigned int, ...)")
assert_equal(
['typedef unsigned int* (*FUNC_PTR_FUNC_RETURN_T)( unsigned int, ... );'],
parsed.get_typedefs)
end
should "insert unique names for top-level nameless arguments" do
parsed = @parser.parse("void foo_bar(int (*)(int, int), char* const, unsigned int c, long const, CUSTOM_THING, int[], char[][2])")
assert_equal(
'int (*cmock_arg1)( int, int ), char* const cmock_arg2, unsigned int c, long const cmock_arg4, CUSTOM_THING cmock_arg5, int cmock_arg6[], char cmock_arg7[][2]',
parsed.get_argument_list)
assert_equal(
[{:type => 'FUNC_PTR_FOO_BAR_PARAM_1_T', :name => 'cmock_arg1'},
{:type => 'char*', :name => 'cmock_arg2'},
{:type => 'unsigned int', :name => 'c'},
{:type => 'long', :name => 'cmock_arg4'},
{:type => 'CUSTOM_THING', :name => 'cmock_arg5'},
{:type => 'int*', :name => 'cmock_arg6'},
{:type => 'char*', :name => 'cmock_arg7'}],
parsed.get_arguments)
assert_nil(parsed.get_var_arg)
end
end
+10 -7
View File
@@ -42,12 +42,16 @@ class CMockGeneratorTest < Test::Unit::TestCase
#no strict handling
@config.expect.mock_prefix.returns("Mock")
@config.expect.enforce_strict_ordering.returns(false)
@cmock_generator = CMockGenerator.new(@config, @module_name, @file_writer, @utils, @plugins)
@cmock_generator = CMockGenerator.new(@config, @file_writer, @utils, @plugins)
@cmock_generator.module_name = @module_name
@cmock_generator.mock_name = "Mock#{@module_name}"
#strict handling
@config.expect.mock_prefix.returns("Mock")
@config.expect.enforce_strict_ordering.returns(true)
@cmock_generator_strict = CMockGenerator.new(@config, @module_name, @file_writer, @utils, @plugins)
@cmock_generator_strict = CMockGenerator.new(@config, @file_writer, @utils, @plugins)
@cmock_generator_strict.module_name = @module_name
@cmock_generator_strict.mock_name = "Mock#{@module_name}"
end
def teardown
@@ -55,11 +59,9 @@ class CMockGeneratorTest < Test::Unit::TestCase
should "have set up internal accessors correctly on init" do
assert_equal(@config, @cmock_generator.config)
assert_equal(@module_name, @cmock_generator.module_name)
assert_equal(@file_writer, @cmock_generator.file_writer)
assert_equal(@utils, @cmock_generator.utils)
assert_equal(@plugins, @cmock_generator.plugins)
assert_equal("Mock#{@module_name}", @cmock_generator.mock_name)
end
should "create the top of a header file" do
@@ -80,8 +82,9 @@ class CMockGeneratorTest < Test::Unit::TestCase
end
should "write typedefs" do
functions = [ { :typedefs => ['typedef unsigned char U8;', 'typedef char S8;'] },
{ :typedefs => ['typedef unsigned long U32;'] }
typedefs = [ 'typedef unsigned char U8;',
'typedef char S8;',
'typedef unsigned long U32;'
]
output = []
expected = [ "\n",
@@ -91,7 +94,7 @@ class CMockGeneratorTest < Test::Unit::TestCase
"\n\n"
]
@cmock_generator.create_source_typedefs(output, functions)
@cmock_generator.create_typedefs(output, typedefs)
assert_equal(expected, output.flatten)
end
+292 -335
View File
@@ -4,30 +4,30 @@ require 'cmock_header_parser'
class CMockHeaderParserTest < Test::Unit::TestCase
def setup
create_mocks :config, :prototype_parser, :parsed
create_mocks :config
@test_name = 'test_file.h'
@config.expect.attributes.returns(['__ramfunc', 'funky_attrib'])
@config.expect.treat_as_void.returns(['MY_FUNKY_VOID'])
@config.expect.treat_as.returns({ "BANJOS" => "INT", "TUBAS" => "HEX16"} )
@config.expect.when_no_prototypes.returns(:error)
@parser = CMockHeaderParser.new(@config)
end
def teardown
end
should "create and initialize variables to defaults appropriately" do
@parser = CMockHeaderParser.new(@prototype_parser, "", @config, @test_name)
assert_equal([], @parser.prototypes)
assert_equal([], @parser.src_lines)
assert_equal(['__ramfunc', 'funky_attrib'], @parser.attributes)
assert_equal([], @parser.funcs)
assert_equal(['const', '__ramfunc', 'funky_attrib'], @parser.c_attributes)
assert_equal(['void','MY_FUNKY_VOID'], @parser.treat_as_void)
end
should "strip out line comments" do
source =
" abcd;\n" +
"// hello;\n" +
"who // is you\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
@@ -35,25 +35,41 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"who"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
should "remove block comments" do
source =
" abcd;\n" +
"/* hello;*/\n" +
"who /* is you\n" +
"// embedded line comment */\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
" no_comments;\n" +
"// basic_line_comment;\n" +
"/* basic_block_comment;*/\n" +
"pre_block; /* start_of_block_comment;\n" +
"// embedded_line_comment_in_block_comment; */\n" +
"// /* commented_out_block_comment_line\n" +
"shown_because_block_comment_invalid_from_line_comment;\n" +
"// */\n" +
"//* shorter_commented_out_block_comment_line; \n" +
"shown_because_block_comment_invalid_from_shorter_line_comment;\n" +
"/*/\n" +
"not_shown_because_line_above_started_comment;\n" +
"//*/\n" +
"/* \n" +
"not_shown_because_block_comment_started_this_time;\n" +
"/*/\n" +
"shown_because_line_above_ended_comment_this_time;\n" +
"//*/\n"
expected =
[
"abcd",
"who"
"no_comments",
"pre_block",
"shown_because_block_comment_invalid_from_line_comment",
"shown_because_block_comment_invalid_from_shorter_line_comment",
"shown_because_line_above_ended_comment_this_time"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
@@ -62,11 +78,10 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"#when stuff_happens\n" +
"#ifdef _TEST\n" +
"#pragma stack_switch"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected = []
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source))
end
@@ -74,14 +89,26 @@ class CMockHeaderParserTest < Test::Unit::TestCase
source =
"hoo hah \\\n" +
"when \\ \n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
"hoo hah when"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
should "remove C macro definitions" do
source =
"#define this is the first line\\\n" +
"and the second\\\n" +
"and the third that should be removed\n" +
"but I'm here\n"
expected = ["but I'm here"]
assert_equal(expected, @parser.import_source(source))
end
@@ -92,14 +119,13 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"typedef who cares what really comes here \\\n" + # exercise multiline typedef
" continuation;\n" +
"this should remain!"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
"whack me? this should remain!"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
@@ -116,9 +142,8 @@ class CMockHeaderParserTest < Test::Unit::TestCase
" THING2,\n" +
"} Thinger;\n" +
"or me!!\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
assert_equal(["don't delete me!! or me!!"], @parser.src_lines)
assert_equal(["don't delete me!! or me!!"], @parser.import_source(source).map!{|s|s.strip})
end
@@ -135,9 +160,8 @@ class CMockHeaderParserTest < Test::Unit::TestCase
" char b;\n" +
"} Whatever;\n" +
"me too!!\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
assert_equal(["I want to live!! me too!!"], @parser.src_lines)
assert_equal(["I want to live!! me too!!"], @parser.import_source(source).map!{|s|s.strip})
end
@@ -158,9 +182,9 @@ class CMockHeaderParserTest < Test::Unit::TestCase
" signed char b;\n" +
"}Thinger;\n" +
"I want to live!!\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
assert_equal(["void foo(void)", "struct THINGER foo(void)", "I want to live!!"], @parser.src_lines)
assert_equal(["void foo(void)", "struct THINGER foo(void)", "I want to live!!"],
@parser.import_source(source).map!{|s|s.strip})
end
@@ -170,8 +194,9 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"uint32 extern_name_func(unsigned int);\n" +
"uint32 funcinline(unsigned int);\n" +
"extern void bar(unsigned int);\n" +
"inline void bar(unsigned int);\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
"inline void bar(unsigned int);\n" +
"extern\n" +
"void kinda_ugly_on_the_next_line(unsigned int);\n"
expected =
[
@@ -179,7 +204,7 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"uint32 funcinline(unsigned int)"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
@@ -190,39 +215,65 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"#DEFINE I JUST DON'T CARE\n" +
"#deFINE\n" +
"#define get_foo() \\\n ((Thing)foo.bar)" # exercise multiline define
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
"void hello(void)",
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
should "remove keywords that would keep things from going smoothly in the future" do
source =
"const int TheMatrix(register int Trinity, unsigned int *restrict Neo)"
expected =
[
"const int TheMatrix(int Trinity, unsigned int * Neo)",
]
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
should "handle odd case of typedef'd void" do
# some code actually typedef's void even though it's not ANSI C and is, frankly, weird
# since cmock treats void specially, we can't let void be obfuscated
source =
"typedef void SILLY_VOID_TYPE1;\n" +
"typedef (void) SILLY_VOID_TYPE2 ;\n" +
"typedef ( void ) (*FUNCPTR)(void);\n\n" + # don't get fooled by function pointer typedef with void as return type
"SILLY_VOID_TYPE2 Foo(int a, unsigned int b);\n" +
"void\n shiz(SILLY_VOID_TYPE1 *);\n" +
"void tat(FUNCPTR);\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
"void Foo(int a, unsigned int b)",
"void shiz(void *)",
"void tat(FUNCPTR)"
]
assert_equal(expected, @parser.src_lines)
# some code actually typedef's void even though it's not ANSI C and is, frankly, weird
# since cmock treats void specially, we can't let void be obfuscated
should "handle odd case of typedef'd void returned" do
source = "MY_FUNKY_VOID FunkyVoidReturned(int a)"
expected = { :var_arg=>nil,
:return_string=>"void toReturn",
:name=>"FunkyVoidReturned",
:return_type=>"void",
:modifier=>"",
:args=>[{:type=>"int", :name=>"a"}],
:args_string=>"int a" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "handle odd case of typedef'd void as arg" do
source = "int FunkyVoidAsArg(MY_FUNKY_VOID)"
expected = { :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"FunkyVoidAsArg",
:return_type=>"int",
:modifier=>"",
:args=>[],
:args_string=>"void" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "handle odd case of typedef'd void as arg pointer" do
source = "char FunkyVoidPointer(MY_FUNKY_VOID* bluh)"
expected = { :var_arg=>nil,
:return_string=>"char toReturn",
:name=>"FunkyVoidPointer",
:return_type=>"char",
:modifier=>"",
:args=>[{:type=>"MY_FUNKY_VOID*", :name=>"bluh"}],
:args_string=>"MY_FUNKY_VOID* bluh" }
assert_equal(expected, @parser.parse_declaration(source))
end
@@ -230,34 +281,30 @@ class CMockHeaderParserTest < Test::Unit::TestCase
source =
"void Foo(int a = 57, float b=37.52, char c= 'd', char* e=\"junk\");\n"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
expected =
[
"void Foo(int a, float b, char c, char* e)"
]
assert_equal(expected, @parser.src_lines)
assert_equal(expected, @parser.import_source(source).map!{|s|s.strip})
end
should "raise upon empty file" do
source = ''
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, 'thinger.h')
# ensure it's expected type of exception
assert_raise RuntimeError do
@parser.parse
@parser.parse("")
end
assert_equal([], @parser.prototypes)
assert_equal([], @parser.funcs)
# verify exception message
begin
@parser.parse
@parser.parse("")
rescue RuntimeError => e
assert_equal("ERROR: No function prototypes found in 'thinger.h'", e.message)
assert_equal("ERROR: No function prototypes found!", e.message)
end
end
@@ -268,306 +315,216 @@ class CMockHeaderParserTest < Test::Unit::TestCase
"typedef (void) SILLY_VOID_TYPE2 ;\n" +
"typedef ( void ) (*FUNCPTR)(void);\n\n" +
"#define get_foo() \\\n ((Thing)foo.bar)"
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, 'hello_world.h')
# ensure it's expected type of exception
assert_raise(RuntimeError) do
@parser.parse
@parser.parse(source)
end
assert_equal([], @parser.prototypes)
assert_equal([], @parser.funcs)
# verify exception message
begin
@parser.parse
@parser.parse(source)
rescue RuntimeError => e
assert_equal("ERROR: No function prototypes found in 'hello_world.h'", e.message)
assert_equal("ERROR: No function prototypes found!", e.message)
end
end
should "raise upon prototype parsing failure" do
source =
"int Foo(int a, unsigned int b);\n" +
"void bar \n(uint la, int de, bool da) ; \n"
@prototype_parser.expect.parse('int Foo(int a, unsigned int b)').returns(nil)
@prototype_parser.expect.parse('int Foo(int a, unsigned int b)').returns(nil)
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
source = "void (int, )"
# ensure it's expected type of exception
assert_raise(RuntimeError) do
@parser.parse
@parser.parse(source)
end
# verify exception message
begin
@parser.parse
@parser.parse(source)
rescue RuntimeError => e
assert_equal("Failed parsing function prototype: 'int Foo(int a, unsigned int b)' in file '#{@test_name}'", e.message)
assert(e.message.include? "Failed Parsing Declaration Prototype!")
end
end
should "extract and return function declarations" do
source =
"int Foo(int a, unsigned int b);\n" +
"void FunkyChicken (\n uint la,\n int de,\n bool da) ; \n" +
" void \n tat();\n" +
# following lines should yield no function prototypes:
"#define get_foo() \\\n (Thing)foo())\n" +
"ARRAY_TYPE array[((U8)10)];\n" +
"enum {\n" +
" THINGER_MASK1 = (0x0001),\n" +
" THINGER_MASK2 = (0x0001 << 1),\n" +
" THINGER_MASK3 = (0x0001 << 2) };\n" +
"void ( ( * const Tasks [10] ) ( void ) );\n" # array of function pointers
@prototype_parser.expect.parse('int Foo(int a, unsigned int b)').returns(@parsed)
@parsed.expect.get_function_name.returns('buzz lightyear')
@parsed.expect.get_argument_list.returns('woody')
@parsed.expect.get_arguments.returns([{:type => 'what up', :name => 'dawg'}])
@parsed.expect.get_return_type.returns('little')
@parsed.expect.get_return_type_with_name.returns('bo peep')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns([])
@prototype_parser.expect.parse('void FunkyChicken(uint la, int de, bool da)').returns(@parsed)
@parsed.expect.get_function_name.returns('marty')
@parsed.expect.get_argument_list.returns('mcfly')
@parsed.expect.get_arguments.returns([{:type => 'back', :name => 'to'}])
@parsed.expect.get_return_type.returns('the future')
@parsed.expect.get_return_type_with_name.returns('doc')
@parsed.expect.get_var_arg.returns(nil)
@parsed.expect.get_typedefs.returns([])
@prototype_parser.expect.parse('void tat()').returns(@parsed)
@parsed.expect.get_function_name.returns('neo')
@parsed.expect.get_argument_list.returns('the matrix')
@parsed.expect.get_arguments.returns([{:type => 'trinity', :name => 'the one'}])
@parsed.expect.get_return_type.returns('agent smith')
@parsed.expect.get_return_type_with_name.returns('morpheus')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns(['typedef unsigned int UINT;', 'typedef unsigned short USHORT;'])
expected_prototypes =
[
'int Foo(int a, unsigned int b)',
'void FunkyChicken(uint la, int de, bool da)',
'void tat()'
]
expected_hashes =
[
{
:modifier => '',
:args_string => 'woody',
:return_type => 'little',
:return_string => 'bo peep',
:var_arg => '...',
:args => [{:type => 'what up', :name => 'dawg'}],
:name => 'buzz lightyear',
:typedefs => [],
},
{
:modifier => '',
:args_string => 'mcfly',
:return_type => 'the future',
:return_string => 'doc',
:var_arg => nil,
:args => [{:type => 'back', :name => 'to'}],
:name => 'marty',
:typedefs => [],
},
{
:modifier => '',
:args_string => 'the matrix',
:return_type => 'agent smith',
:return_string => 'morpheus',
:var_arg => '...',
:args => [{:type => 'trinity', :name => 'the one'}],
:name => 'neo',
:typedefs => ['typedef unsigned int UINT;', 'typedef unsigned short USHORT;'],
},
]
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
parsed_stuff = @parser.parse
assert_equal(expected_prototypes, @parser.prototypes)
assert_equal(expected_hashes, parsed_stuff[:functions])
end
should "extract and return function declarations with retval and args" do
should "extract custom function attributes and also scrub certain C keywords" do
source =
" static int Foo( register int a, unsigned int* restrict b);\n" +
"register __ramfunc funky_attrib void \n tat();\n"
@prototype_parser.expect.parse('int Foo(int a, unsigned int* b)').returns(@parsed)
@parsed.expect.get_function_name.returns('buzz lightyear')
@parsed.expect.get_argument_list.returns('woody')
@parsed.expect.get_arguments.returns([{:type => 'what up', :name => 'dawg'}])
@parsed.expect.get_return_type.returns('little')
@parsed.expect.get_return_type_with_name.returns('bo peep')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns([])
@prototype_parser.expect.parse('void tat()').returns(@parsed)
@parsed.expect.get_function_name.returns('neo')
@parsed.expect.get_argument_list.returns('the matrix')
@parsed.expect.get_arguments.returns([{:type => 'trinity', :name => 'the one'}])
@parsed.expect.get_return_type.returns('agent smith')
@parsed.expect.get_return_type_with_name.returns('morpheus')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns([])
expected_prototypes =
[
'int Foo(int a, unsigned int* b)',
'void tat()'
]
expected_hashes =
[
{
:modifier => '',
:args_string => 'woody',
:return_type => 'little',
:return_string => 'bo peep',
:var_arg => '...',
:args => [{:type => 'what up', :name => 'dawg'}],
:name => 'buzz lightyear',
:typedefs => []
},
{
:modifier => '__ramfunc funky_attrib',
:args_string => 'the matrix',
:return_type => 'agent smith',
:return_string => 'morpheus',
:var_arg => '...',
:args => [{:type => 'trinity', :name => 'the one'}],
:name => 'neo',
:typedefs => []
},
]
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
parsed_stuff = @parser.parse
assert_equal(expected_prototypes, @parser.prototypes)
assert_equal(expected_hashes, parsed_stuff[:functions])
source = "int Foo(int a, unsigned int b)"
expected = { :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"Foo",
:return_type=>"int",
:modifier=>"",
:args=>[ {:type=>"int", :name=>"a"},
{:type=>"unsigned int", :name=>"b"}
],
:args_string=>"int a, unsigned int b" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "extract and return function declarations with no retval" do
source = "void FunkyChicken( uint la, int de, bool da)"
expected = { :var_arg=>nil,
:return_string=>"void toReturn",
:name=>"FunkyChicken",
:return_type=>"void",
:modifier=>"",
:args=>[ {:type=>"uint", :name=>"la"},
{:type=>"int", :name=>"de"},
{:type=>"bool", :name=>"da"}
],
:args_string=>"uint la, int de, bool da" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "extract and return function declarations with implied voids" do
source = "void tat()"
expected = { :var_arg=>nil,
:return_string=>"void toReturn",
:name=>"tat",
:return_type=>"void",
:modifier=>"",
:args=>[ ],
:args_string=>"void" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "extract modifiers properly" do
source = "const int TheMatrix(int Trinity, unsigned int * Neo)"
expected = { :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"TheMatrix",
:return_type=>"int",
:modifier=>"const",
:args=>[ {:type=>"int", :name=>"Trinity"},
{:type=>"unsigned int*", :name=>"Neo"}
],
:args_string=>"int Trinity, unsigned int* Neo" }
assert_equal(expected, @parser.parse_declaration(source))
end
should "should fully parse multiple prototypes" do
source = "const int TheMatrix(int Trinity, unsigned int * Neo);\n" +
"int Morpheus(int, unsigned int*);\n"
expected = [{ :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"TheMatrix",
:return_type=>"int",
:modifier=>"const",
:args=>[ {:type=>"int", :name=>"Trinity"},
{:type=>"unsigned int*", :name=>"Neo"}
],
:args_string=>"int Trinity, unsigned int* Neo" },
{ :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"Morpheus",
:return_type=>"int",
:modifier=>"",
:args=>[ {:type=>"int", :name=>"cmock_arg1"},
{:type=>"unsigned int*", :name=>"cmock_arg2"}
],
:args_string=>"int cmock_arg1, unsigned int* cmock_arg2"
}]
assert_equal(expected, @parser.parse(source)[:functions])
end
should "not extract for mocking multiply defined prototypes" do
# just in case a function is defined multiple times and we haven't already dealt with it
source =
"int Foo(int a, unsigned int b);\n" +
"void FunkyChicken (\n uint la,\n int de,\n bool da) ; \n" +
" void \n tat();\n" +
"int Foo (int, unsigned int);"
@prototype_parser.expect.parse('int Foo(int a, unsigned int b)').returns(@parsed)
@parsed.expect.get_function_name.returns('Foo')
@parsed.expect.get_argument_list.returns('woody')
@parsed.expect.get_arguments.returns([{:type => 'what up', :name => 'dawg'}])
@parsed.expect.get_return_type.returns('little')
@parsed.expect.get_return_type_with_name.returns('bo peep')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns([])
@prototype_parser.expect.parse('void FunkyChicken(uint la, int de, bool da)').returns(@parsed)
@parsed.expect.get_function_name.returns('marty')
@parsed.expect.get_argument_list.returns('mcfly')
@parsed.expect.get_arguments.returns([{:type => 'back', :name => 'to'}])
@parsed.expect.get_return_type.returns('the future')
@parsed.expect.get_return_type_with_name.returns('doc')
@parsed.expect.get_var_arg.returns(nil)
@parsed.expect.get_typedefs.returns([])
@prototype_parser.expect.parse('void tat()').returns(@parsed)
@parsed.expect.get_function_name.returns('neo')
@parsed.expect.get_argument_list.returns('the matrix')
@parsed.expect.get_arguments.returns([{:type => 'trinity', :name => 'the one'}])
@parsed.expect.get_return_type.returns('agent smith')
@parsed.expect.get_return_type_with_name.returns('morpheus')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns(['typedef unsigned int UINT;', 'typedef unsigned short USHORT;'])
@prototype_parser.expect.parse('int Foo(int, unsigned int)').returns(@parsed)
@parsed.expect.get_function_name.returns('Foo')
@parsed.expect.get_argument_list.returns('woody')
@parsed.expect.get_arguments.returns([{:type => 'what up', :name => 'dawg'}])
@parsed.expect.get_return_type.returns('little')
@parsed.expect.get_return_type_with_name.returns('bo peep')
@parsed.expect.get_var_arg.returns('...')
@parsed.expect.get_typedefs.returns([])
expected_prototypes =
[
'int Foo(int a, unsigned int b)',
'void FunkyChicken(uint la, int de, bool da)',
'void tat()',
'int Foo(int, unsigned int)'
]
expected_hashes =
[
{
:modifier => '',
:args_string => 'woody',
:return_type => 'little',
:return_string => 'bo peep',
:var_arg => '...',
:args => [{:type => 'what up', :name => 'dawg'}],
:name => 'Foo',
:typedefs => [],
},
{
:modifier => '',
:args_string => 'mcfly',
:return_type => 'the future',
:return_string => 'doc',
:var_arg => nil,
:args => [{:type => 'back', :name => 'to'}],
:name => 'marty',
:typedefs => [],
},
{
:modifier => '',
:args_string => 'the matrix',
:return_type => 'agent smith',
:return_string => 'morpheus',
:var_arg => '...',
:args => [{:type => 'trinity', :name => 'the one'}],
:name => 'neo',
:typedefs => ['typedef unsigned int UINT;', 'typedef unsigned short USHORT;'],
},
]
@parser = CMockHeaderParser.new(@prototype_parser, source, @config, @test_name)
parsed_stuff = @parser.parse
assert_equal(expected_prototypes, @parser.prototypes)
assert_equal(expected_hashes, parsed_stuff[:functions])
source = "const int TheMatrix(int Trinity, unsigned int * Neo);\n" +
"const int TheMatrix(int, unsigned int*);\n"
expected = [{ :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"TheMatrix",
:return_type=>"int",
:modifier=>"const",
:args=>[ {:type=>"int", :name=>"Trinity"},
{:type=>"unsigned int*", :name=>"Neo"}
],
:args_string=>"int Trinity, unsigned int* Neo"
}]
assert_equal(expected, @parser.parse(source)[:functions])
end
should "properly detect typedef'd variants of void and use those" do
source = "typedef (void) FUNKY_VOID_T;\n" +
"typedef void CHUNKY_VOID_T;\n" +
"FUNKY_VOID_T DrHorrible(int SingAlong);\n" +
"int CaptainHammer(CHUNKY_VOID_T);\n"
expected = [{ :var_arg=>nil,
:return_string=>"void toReturn",
:name=>"DrHorrible",
:return_type=>"void",
:modifier=>"",
:args=>[ {:type=>"int", :name=>"SingAlong"} ],
:args_string=>"int SingAlong"
},
{ :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"CaptainHammer",
:return_type=>"int",
:modifier=>"",
:args=>[ ],
:args_string=>"void"
}]
assert_equal(expected, @parser.parse(source)[:functions])
end
should "be ok with structs inside of function declarations" do
source = "int DrHorrible(struct SingAlong Blog);\n" +
"void Penny(struct const _KeepYourHeadUp_ * const BillyBuddy);\n" +
"struct TheseArentTheHammer CaptainHammer(void);\n"
expected = [{ :var_arg=>nil,
:return_string=>"int toReturn",
:name=>"DrHorrible",
:return_type=>"int",
:modifier=>"",
:args=>[ {:type=>"struct SingAlong", :name=>"Blog"} ],
:args_string=>"struct SingAlong Blog"
},
{ :var_arg=>nil,
:return_string=>"void toReturn",
:name=>"Penny",
:return_type=>"void",
:modifier=>"",
:args=>[ {:type=>"struct _KeepYourHeadUp_*", :name=>"BillyBuddy"} ],
:args_string=>"struct const _KeepYourHeadUp_* const BillyBuddy"
},
{ :var_arg=>nil,
:return_string=>"struct TheseArentTheHammer toReturn",
:name=>"CaptainHammer",
:return_type=>"struct TheseArentTheHammer",
:modifier=>"",
:args=>[ ],
:args_string=>"void"
}]
assert_equal(expected, @parser.parse(source)[:functions])
end
should "extract functions with varargs" do
source = "int XFiles(int Scully, int Mulder, ...);\n"
expected = [{ :var_arg=>"...",
:return_string=>"int toReturn",
:name=>"XFiles",
:return_type=>"int",
:modifier=>"",
:args=>[ {:type=>"int", :name=>"Scully"},
{:type=>"int", :name=>"Mulder"}
],
:args_string=>"int Scully, int Mulder"
}]
assert_equal(expected, @parser.parse(source)[:functions])
end
end
-36
View File
@@ -1,36 +0,0 @@
== 0.2.5 2009-03-04
* 1 significant fix:
* Polyglot's require may be called with a Pathname, or other object allowed by Kernel#require that doesn't support [] (Kernel#require uses to_str apparently)
== 0.2.4 2008-05-29
* 1 significant fix:
* Previous LoadError change is checked in this time (oops!)
== 0.2.3 2008-05-29
* 2 minor enhancements:
* Raise MissingSourceFile exception instead of LoadError if ActiveSupport is loaded
* Re-raise original exception new one on require load fail
== 0.2.2 2008-05-12
* 2 minor enhancements:
* Doesn't search $: when asked to load an absolute path
* Adds a helpful exception message on LoadError
== 0.2.1 2008-03-05
* 1 minor defect:
* code to raise LoadError itself raised an exception
== 0.2.0 2008-02-13
* 1 major enhancement:
* Doesn't reload on every require
== 0.1.0 2007-10-22
* 1 major enhancement:
* Initial release
-20
View File
@@ -1,20 +0,0 @@
Copyright (c) 2007 Clifford Heath
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-24
View File
@@ -1,24 +0,0 @@
History.txt
License.txt
Manifest.txt
README.txt
Rakefile
config/hoe.rb
config/requirements.rb
lib/polyglot.rb
lib/polyglot/version.rb
log/debug.log
script/destroy
script/generate
script/txt2html
setup.rb
tasks/deployment.rake
tasks/environment.rake
tasks/website.rake
test/test_helper.rb
test/test_polyglot.rb
website/index.html
website/index.txt
website/javascripts/rounded_corners_lite.inc.js
website/stylesheets/screen.css
website/template.rhtml
-87
View File
@@ -1,87 +0,0 @@
= polyglot
* http://polyglot.rubyforge.org
== DESCRIPTION:
Author: Clifford Heath, 2007
The Polyglot library allows a Ruby module to register a loader
for the file type associated with a filename extension, and it
augments 'require' to find and load matching files.
This supports the creation of DSLs having a syntax that is most
appropriate to their purpose, instead of abusing the Ruby syntax.
Files are sought using the normal Ruby search path.
== EXAMPLE:
In file rubyglot.rb, define and register a file type handler:
require 'polyglot'
class RubyglotLoader
def self.load(filename, options = nil, &block)
File.open(filename) {|file|
# Load the contents of file as Ruby code:
# Implement your parser here instead!
Kernel.eval(file.read)
}
end
end
Polyglot.register("rgl", RubyglotLoader)
In file test.rb:
require 'rubyglot' # Create my file type handler
require 'hello' # Can add extra options or even a block here
puts "Ready to go"
Hello.new
In file hello.rgl (this simple example uses Ruby code):
puts "Initializing"
class Hello
def initialize()
puts "Hello, world\n"
end
end
Run:
$ ruby test.rb
Initializing
Ready to go
Hello, world
$
== INSTALL:
sudo gem install polyglot
== LICENSE:
(The MIT License)
Copyright (c) 2007 Clifford Heath
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-4
View File
@@ -1,4 +0,0 @@
require 'config/requirements'
require 'config/hoe' # setup Hoe + all gem configuration
Dir['tasks/**/*.rake'].each { |rake| load rake }
-71
View File
@@ -1,71 +0,0 @@
require 'polyglot/version'
AUTHOR = 'Clifford Heath' # can also be an array of Authors
EMAIL = "cjheath@rubyforge.org"
DESCRIPTION = "Allows custom language loaders for specified file extensions to be hooked into require"
GEM_NAME = 'polyglot' # what ppl will type to install your gem
RUBYFORGE_PROJECT = 'polyglot' # The unix name for your project
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
@config_file = "~/.rubyforge/user-config.yml"
@config = nil
RUBYFORGE_USERNAME = "unknown"
def rubyforge_username
unless @config
begin
@config = YAML.load(File.read(File.expand_path(@config_file)))
rescue
puts <<-EOS
ERROR: No rubyforge config file found: #{@config_file}
Run 'rubyforge setup' to prepare your env for access to Rubyforge
- See http://newgem.rubyforge.org/rubyforge.html for more details
EOS
exit
end
end
RUBYFORGE_USERNAME.replace @config["username"]
end
REV = nil
# UNCOMMENT IF REQUIRED:
# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
VERS = Polyglot::VERSION::STRING + (REV ? ".#{REV}" : "")
RDOC_OPTS = ['--quiet', '--title', 'polyglot documentation',
"--opname", "index.html",
"--line-numbers",
"--main", "README",
"--inline-source"]
class Hoe
def extra_deps
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
@extra_deps
end
end
# Generate all the Rake tasks
# Run 'rake -T' to see list of generated tasks (from gem root directory)
hoe = Hoe.new(GEM_NAME, VERS) do |p|
p.author = AUTHOR
p.description = DESCRIPTION
p.email = EMAIL
p.summary = DESCRIPTION
p.url = HOMEPATH
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
p.test_globs = ["test/**/test_*.rb"]
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
# == Optional
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
#p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
end
CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
hoe.rsync_args = '-av --delete --ignore-errors'
-17
View File
@@ -1,17 +0,0 @@
require 'fileutils'
include FileUtils
require 'rubygems'
%w[rake hoe newgem rubigen].each do |req_gem|
begin
require req_gem
rescue LoadError
puts "This Rakefile requires the '#{req_gem}' RubyGem."
puts "Installation: gem install #{req_gem} -y"
exit
end
end
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
require 'polyglot'
-63
View File
@@ -1,63 +0,0 @@
$:.unshift File.dirname(__FILE__)
module Polyglot
@registrations ||= {} # Guard against reloading
@loaded ||= {}
def self.register(extension, klass)
extension = [extension] unless Enumerable === extension
extension.each{|e|
@registrations[e] = klass
}
end
def self.find(file, *options, &block)
extensions = @registrations.keys*","
is_absolute = file[0] == File::SEPARATOR || file[0] == File::ALT_SEPARATOR || file =~ /\A[A-Z]:\\/i
(is_absolute ? [""] : $:).each{|lib|
base = is_absolute ? "" : lib+File::SEPARATOR
# In Windows, repeated SEPARATOR chars have a special meaning, avoid adding them
matches = Dir[base+file+".{"+extensions+"}"]
# Revisit: Should we do more do if more than one candidate found?
$stderr.puts "Polyglot: found more than one candidate for #{file}: #{matches*", "}" if matches.size > 1
if path = matches[0]
return [ path, @registrations[path.gsub(/.*\./,'')]]
end
}
return nil
end
def self.load(*a, &b)
file = a[0].to_str
return if @loaded[file] # Check for $: changes or file time changes and reload?
begin
source_file, loader = Polyglot.find(file, *a[1..-1], &b)
if (loader)
loader.load(source_file)
@loaded[file] = true
else
msg = "Failed to load #{file} using extensions #{(@registrations.keys+["rb"]).sort*", "}"
if defined?(MissingSourceFile)
raise MissingSourceFile.new(msg, file)
else
raise LoadError.new(msg)
end
end
end
end
end
module Kernel
alias polyglot_original_require require
def require(*a, &b)
polyglot_original_require(*a, &b)
rescue LoadError => load_error
begin
Polyglot.load(*a, &b)
rescue LoadError
# Raise the original exception, possibly a MissingSourceFile with a path
raise load_error
end
end
end
-9
View File
@@ -1,9 +0,0 @@
module Polyglot #:nodoc:
module VERSION #:nodoc:
MAJOR = 0
MINOR = 2
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end
end
View File
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env ruby
APP_ROOT = File.join(File.dirname(__FILE__), '..')
begin
require 'rubigen'
rescue LoadError
require 'rubygems'
require 'rubigen'
end
require 'rubigen/scripts/destroy'
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
RubiGen::Scripts::Destroy.new.run(ARGV)
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env ruby
APP_ROOT = File.join(File.dirname(__FILE__), '..')
begin
require 'rubigen'
rescue LoadError
require 'rubygems'
require 'rubigen'
end
require 'rubigen/scripts/generate'
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
RubiGen::Scripts::Generate.new.run(ARGV)
-74
View File
@@ -1,74 +0,0 @@
#!/usr/bin/env ruby
require 'rubygems'
begin
require 'newgem'
rescue LoadError
puts "\n\nGenerating the website requires the newgem RubyGem"
puts "Install: gem install newgem\n\n"
exit(1)
end
require 'redcloth'
require 'syntax/convertors/html'
require 'erb'
require File.dirname(__FILE__) + '/../lib/polyglot/version.rb'
version = Polyglot::VERSION::STRING
download = 'http://rubyforge.org/projects/polyglot'
class Fixnum
def ordinal
# teens
return 'th' if (10..19).include?(self % 100)
# others
case self % 10
when 1: return 'st'
when 2: return 'nd'
when 3: return 'rd'
else return 'th'
end
end
end
class Time
def pretty
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
end
end
def convert_syntax(syntax, source)
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
end
if ARGV.length >= 1
src, template = ARGV
template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
else
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
exit!
end
template = ERB.new(File.open(template).read)
title = nil
body = nil
File.open(src) do |fsrc|
title_text = fsrc.readline
body_text = fsrc.read
syntax_items = []
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
ident = syntax_items.length
element, syntax, source = $1, $2, $3
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
"syntax-temp-#{ident}"
}
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
body = RedCloth.new(body_text).to_html
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
end
stat = File.stat(src)
created = stat.ctime
modified = stat.mtime
$stdout << template.result(binding)
File diff suppressed because it is too large Load Diff
-34
View File
@@ -1,34 +0,0 @@
desc 'Release the website and new gem version'
task :deploy => [:check_version, :website, :release] do
puts "Remember to create SVN tag:"
puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
"svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
puts "Suggested comment:"
puts "Tagging release #{CHANGES}"
end
desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
task :local_deploy => [:website_generate, :install_gem]
task :check_version do
unless ENV['VERSION']
puts 'Must pass a VERSION=x.y.z release version'
exit
end
unless ENV['VERSION'] == VERS
puts "Please update your version.rb to match the release version, currently #{VERS}"
exit
end
end
desc 'Install the package as a gem, without generating documentation(ri/rdoc)'
task :install_gem_no_doc => [:clean, :package] do
sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri"
end
namespace :manifest do
desc 'Recreate Manifest.txt to include ALL files'
task :refresh do
`rake check_manifest | patch -p0 > Manifest.txt`
end
end
-7
View File
@@ -1,7 +0,0 @@
task :ruby_env do
RUBY_APP = if RUBY_PLATFORM =~ /java/
"jruby"
else
"ruby"
end unless defined? RUBY_APP
end
-17
View File
@@ -1,17 +0,0 @@
desc 'Generate website files'
task :website_generate => :ruby_env do
(Dir['website/**/*.txt'] - Dir['website/version*.txt']).each do |txt|
sh %{ #{RUBY_APP} script/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
end
end
desc 'Upload website files to rubyforge'
task :website_upload do
host = "#{rubyforge_username}@rubyforge.org"
remote_dir = "/var/www/gforge-projects/#{PATH}/"
local_dir = 'website'
sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
end
desc 'Generate and upload website files'
task :website => [:website_generate, :website_upload, :publish_docs]
-2
View File
@@ -1,2 +0,0 @@
require 'test/unit'
require File.dirname(__FILE__) + '/../lib/polyglot'
-11
View File
@@ -1,11 +0,0 @@
require File.dirname(__FILE__) + '/test_helper.rb'
class TestPolyglot < Test::Unit::TestCase
def setup
end
def test_truth
assert true
end
end
-100
View File
@@ -1,100 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>
polyglot
</title>
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
<style>
</style>
<script type="text/javascript">
window.onload = function() {
settings = {
tl: { radius: 10 },
tr: { radius: 10 },
bl: { radius: 10 },
br: { radius: 10 },
antiAlias: true,
autoPad: true,
validTags: ["div"]
}
var versionBox = new curvyCorners(settings, document.getElementById("version"));
versionBox.applyCornersToAll();
}
</script>
</head>
<body>
<div id="main">
<h1>polyglot</h1>
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/polyglot"; return false'>
<p>Get Version</p>
<a href="http://rubyforge.org/projects/polyglot" class="numbers">0.2.5</a>
</div>
<h2>Poly =&gt; many, glot =&gt; languages</h2>
<h2>What</h2>
<p>Polyglot provides a registry of file types that can be loaded by<br />
calling its improved version of &#8216;require&#8217;. Each file extension<br />
that can be handled by a custom loader is registered by calling<br />
Polyglot.register(&#8220;ext&#8221;, &lt;class&gt;), and then you can simply<br />
require &#8220;somefile&#8221;, which will find and load &#8220;somefile.ext&#8221;<br />
using your custom loader.</p>
<p>This supports the creation of DSLs having a syntax that is most<br />
appropriate to their purpose, instead of abusing the Ruby syntax.</p>
<p>Required files are attempted first using the normal Ruby loader,<br />
and if that fails, Polyglot conducts a search for a file having<br />
a supported extension.</p>
<h2>Installing</h2>
<p><pre class='syntax'><span class="ident">sudo</span> <span class="ident">gem</span> <span class="ident">install</span> <span class="ident">polyglot</span></pre></p>
<h2>Example</h2>
<p>Define and register your file type loader in file rubyglot.rb:</p>
require &#8216;polyglot&#8217;
class RubyglotLoader
def self.load(filename, options = nil, &amp;block)
File.open(filename) {|file|
# Load the contents of file as Ruby code:
# Implement your parser here instead!
Kernel.eval(file.read)
}
end
end
Polyglot.register(&#8220;rgl&#8221;, RubyglotLoader)
<p>This file, hello.rgl, will be loaded (this simple example uses Ruby code):</p>
puts &#8220;Initializing&#8221;
class Hello
def initialize()
puts &#8220;Hello, world\n&#8221;
end
end
<p>Call it from file test.rb:</p>
require &#8216;rubyglot&#8217; # Create my file type handler
require &#8216;hello&#8217; # Can add extra options or even a block here
puts &#8220;Ready to go&#8221;
Hello.new
<p>Run:</p>
$ ruby test.rb
Initializing
Ready to go
Hello, world
$
<h2>How to submit patches</h2>
<p>Read the <a href="http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/">8 steps for fixing other people&#8217;s code</a> and for section <a href="http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/#8z-email">8z: Submit patch</a>, email me on the link below.</p>
<p>The trunk repository is <code>svn://rubyforge.org/var/svn/polyglot/trunk</code> for anonymous access.</p>
<h2>License</h2>
<p>This code is free to use under the terms of the <span class="caps">MIT</span> license.</p>
<h2>Contact</h2>
<p>Comments are welcome. Send an email to <a href="mailto:cjheath@rubyforge.org">Clifford Heath</a></p>
<p class="coda">
<a href="cjheath@rubyforge.org">Clifford Heath</a>, 29th January 2009<br>
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
</p>
</div>
<!-- insert site tracking codes here, like Google Urchin -->
</body>
</html>
-79
View File
@@ -1,79 +0,0 @@
h1. polyglot
h2. Poly => many, glot => languages
h2. What
Polyglot provides a registry of file types that can be loaded by
calling its improved version of 'require'. Each file extension
that can be handled by a custom loader is registered by calling
Polyglot.register("ext", &lt;class&gt;), and then you can simply
require "somefile", which will find and load "somefile.ext"
using your custom loader.
This supports the creation of DSLs having a syntax that is most
appropriate to their purpose, instead of abusing the Ruby syntax.
Required files are attempted first using the normal Ruby loader,
and if that fails, Polyglot conducts a search for a file having
a supported extension.
h2. Installing
<pre syntax="ruby">sudo gem install polyglot</pre>
h2. Example
Define and register your file type loader in file rubyglot.rb:
require 'polyglot'
class RubyglotLoader
def self.load(filename, options = nil, &block)
File.open(filename) {|file|
# Load the contents of file as Ruby code:
# Implement your parser here instead!
Kernel.eval(file.read)
}
end
end
Polyglot.register("rgl", RubyglotLoader)
This file, hello.rgl, will be loaded (this simple example uses Ruby code):
puts "Initializing"
class Hello
def initialize()
puts "Hello, world\n"
end
end
Call it from file test.rb:
require 'rubyglot' # Create my file type handler
require 'hello' # Can add extra options or even a block here
puts "Ready to go"
Hello.new
Run:
$ ruby test.rb
Initializing
Ready to go
Hello, world
$
h2. How to submit patches
Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/ and for section "8z: Submit patch":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/#8z-email, email me on the link below.
The trunk repository is <code>svn://rubyforge.org/var/svn/polyglot/trunk</code> for anonymous access.
h2. License
This code is free to use under the terms of the MIT license.
h2. Contact
Comments are welcome. Send an email to "Clifford Heath":mailto:cjheath@rubyforge.org
@@ -1,285 +0,0 @@
/****************************************************************
* *
* curvyCorners *
* ------------ *
* *
* This script generates rounded corners for your divs. *
* *
* Version 1.2.9 *
* Copyright (c) 2006 Cameron Cooke *
* By: Cameron Cooke and Tim Hutchison. *
* *
* *
* Website: http://www.curvycorners.net *
* Email: info@totalinfinity.com *
* Forum: http://www.curvycorners.net/forum/ *
* *
* *
* This library is free software; you can redistribute *
* it and/or modify it under the terms of the GNU *
* Lesser General Public License as published by the *
* Free Software Foundation; either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will *
* be useful, but WITHOUT ANY WARRANTY; without even the *
* implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU Lesser General Public *
* License for more details. *
* *
* You should have received a copy of the GNU Lesser *
* General Public License along with this library; *
* Inc., 59 Temple Place, Suite 330, Boston, *
* MA 02111-1307 USA *
* *
****************************************************************/
var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1; var isMoz = document.implementation && document.implementation.createDocument; var isSafari = ((navigator.userAgent.toLowerCase().indexOf('safari')!=-1)&&(navigator.userAgent.toLowerCase().indexOf('mac')!=-1))?true:false; function curvyCorners()
{ if(typeof(arguments[0]) != "object") throw newCurvyError("First parameter of curvyCorners() must be an object."); if(typeof(arguments[1]) != "object" && typeof(arguments[1]) != "string") throw newCurvyError("Second parameter of curvyCorners() must be an object or a class name."); if(typeof(arguments[1]) == "string")
{ var startIndex = 0; var boxCol = getElementsByClass(arguments[1]);}
else
{ var startIndex = 1; var boxCol = arguments;}
var curvyCornersCol = new Array(); if(arguments[0].validTags)
var validElements = arguments[0].validTags; else
var validElements = ["div"]; for(var i = startIndex, j = boxCol.length; i < j; i++)
{ var currentTag = boxCol[i].tagName.toLowerCase(); if(inArray(validElements, currentTag) !== false)
{ curvyCornersCol[curvyCornersCol.length] = new curvyObject(arguments[0], boxCol[i]);}
}
this.objects = curvyCornersCol; this.applyCornersToAll = function()
{ for(var x = 0, k = this.objects.length; x < k; x++)
{ this.objects[x].applyCorners();}
}
}
function curvyObject()
{ this.box = arguments[1]; this.settings = arguments[0]; this.topContainer = null; this.bottomContainer = null; this.masterCorners = new Array(); this.contentDIV = null; var boxHeight = get_style(this.box, "height", "height"); var boxWidth = get_style(this.box, "width", "width"); var borderWidth = get_style(this.box, "borderTopWidth", "border-top-width"); var borderColour = get_style(this.box, "borderTopColor", "border-top-color"); var boxColour = get_style(this.box, "backgroundColor", "background-color"); var backgroundImage = get_style(this.box, "backgroundImage", "background-image"); var boxPosition = get_style(this.box, "position", "position"); var boxPadding = get_style(this.box, "paddingTop", "padding-top"); this.boxHeight = parseInt(((boxHeight != "" && boxHeight != "auto" && boxHeight.indexOf("%") == -1)? boxHeight.substring(0, boxHeight.indexOf("px")) : this.box.scrollHeight)); this.boxWidth = parseInt(((boxWidth != "" && boxWidth != "auto" && boxWidth.indexOf("%") == -1)? boxWidth.substring(0, boxWidth.indexOf("px")) : this.box.scrollWidth)); this.borderWidth = parseInt(((borderWidth != "" && borderWidth.indexOf("px") !== -1)? borderWidth.slice(0, borderWidth.indexOf("px")) : 0)); this.boxColour = format_colour(boxColour); this.boxPadding = parseInt(((boxPadding != "" && boxPadding.indexOf("px") !== -1)? boxPadding.slice(0, boxPadding.indexOf("px")) : 0)); this.borderColour = format_colour(borderColour); this.borderString = this.borderWidth + "px" + " solid " + this.borderColour; this.backgroundImage = ((backgroundImage != "none")? backgroundImage : ""); this.boxContent = this.box.innerHTML; if(boxPosition != "absolute") this.box.style.position = "relative"; this.box.style.padding = "0px"; if(isIE && boxWidth == "auto" && boxHeight == "auto") this.box.style.width = "100%"; if(this.settings.autoPad == true && this.boxPadding > 0)
this.box.innerHTML = ""; this.applyCorners = function()
{ for(var t = 0; t < 2; t++)
{ switch(t)
{ case 0:
if(this.settings.tl || this.settings.tr)
{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var topMaxRadius = Math.max(this.settings.tl ? this.settings.tl.radius : 0, this.settings.tr ? this.settings.tr.radius : 0); newMainContainer.style.height = topMaxRadius + "px"; newMainContainer.style.top = 0 - topMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.topContainer = this.box.appendChild(newMainContainer);}
break; case 1:
if(this.settings.bl || this.settings.br)
{ var newMainContainer = document.createElement("DIV"); newMainContainer.style.width = "100%"; newMainContainer.style.fontSize = "1px"; newMainContainer.style.overflow = "hidden"; newMainContainer.style.position = "absolute"; newMainContainer.style.paddingLeft = this.borderWidth + "px"; newMainContainer.style.paddingRight = this.borderWidth + "px"; var botMaxRadius = Math.max(this.settings.bl ? this.settings.bl.radius : 0, this.settings.br ? this.settings.br.radius : 0); newMainContainer.style.height = botMaxRadius + "px"; newMainContainer.style.bottom = 0 - botMaxRadius + "px"; newMainContainer.style.left = 0 - this.borderWidth + "px"; this.bottomContainer = this.box.appendChild(newMainContainer);}
break;}
}
if(this.topContainer) this.box.style.borderTopWidth = "0px"; if(this.bottomContainer) this.box.style.borderBottomWidth = "0px"; var corners = ["tr", "tl", "br", "bl"]; for(var i in corners)
{ if(i > -1 < 4)
{ var cc = corners[i]; if(!this.settings[cc])
{ if(((cc == "tr" || cc == "tl") && this.topContainer != null) || ((cc == "br" || cc == "bl") && this.bottomContainer != null))
{ var newCorner = document.createElement("DIV"); newCorner.style.position = "relative"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; if(this.backgroundImage == "")
newCorner.style.backgroundColor = this.boxColour; else
newCorner.style.backgroundImage = this.backgroundImage; switch(cc)
{ case "tl":
newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.tr.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.left = -this.borderWidth + "px"; break; case "tr":
newCorner.style.height = topMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.tl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderTop = this.borderString; newCorner.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; newCorner.style.left = this.borderWidth + "px"; break; case "bl":
newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginRight = this.settings.br.radius - (this.borderWidth*2) + "px"; newCorner.style.borderLeft = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = -this.borderWidth + "px"; newCorner.style.backgroundPosition = "-" + (this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break; case "br":
newCorner.style.height = botMaxRadius - this.borderWidth + "px"; newCorner.style.marginLeft = this.settings.bl.radius - (this.borderWidth*2) + "px"; newCorner.style.borderRight = this.borderString; newCorner.style.borderBottom = this.borderString; newCorner.style.left = this.borderWidth + "px"
newCorner.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (botMaxRadius + this.borderWidth)) + "px"; break;}
}
}
else
{ if(this.masterCorners[this.settings[cc].radius])
{ var newCorner = this.masterCorners[this.settings[cc].radius].cloneNode(true);}
else
{ var newCorner = document.createElement("DIV"); newCorner.style.height = this.settings[cc].radius + "px"; newCorner.style.width = this.settings[cc].radius + "px"; newCorner.style.position = "absolute"; newCorner.style.fontSize = "1px"; newCorner.style.overflow = "hidden"; var borderRadius = parseInt(this.settings[cc].radius - this.borderWidth); for(var intx = 0, j = this.settings[cc].radius; intx < j; intx++)
{ if((intx +1) >= borderRadius)
var y1 = -1; else
var y1 = (Math.floor(Math.sqrt(Math.pow(borderRadius, 2) - Math.pow((intx+1), 2))) - 1); if(borderRadius != j)
{ if((intx) >= borderRadius)
var y2 = -1; else
var y2 = Math.ceil(Math.sqrt(Math.pow(borderRadius,2) - Math.pow(intx, 2))); if((intx+1) >= j)
var y3 = -1; else
var y3 = (Math.floor(Math.sqrt(Math.pow(j ,2) - Math.pow((intx+1), 2))) - 1);}
if((intx) >= j)
var y4 = -1; else
var y4 = Math.ceil(Math.sqrt(Math.pow(j ,2) - Math.pow(intx, 2))); if(y1 > -1) this.drawPixel(intx, 0, this.boxColour, 100, (y1+1), newCorner, -1, this.settings[cc].radius); if(borderRadius != j)
{ for(var inty = (y1 + 1); inty < y2; inty++)
{ if(this.settings.antiAlias)
{ if(this.backgroundImage != "")
{ var borderFract = (pixelFraction(intx, inty, borderRadius) * 100); if(borderFract < 30)
{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, 0, this.settings[cc].radius);}
else
{ this.drawPixel(intx, inty, this.borderColour, 100, 1, newCorner, -1, this.settings[cc].radius);}
}
else
{ var pixelcolour = BlendColour(this.boxColour, this.borderColour, pixelFraction(intx, inty, borderRadius)); this.drawPixel(intx, inty, pixelcolour, 100, 1, newCorner, 0, this.settings[cc].radius, cc);}
}
}
if(this.settings.antiAlias)
{ if(y3 >= y2)
{ if (y2 == -1) y2 = 0; this.drawPixel(intx, y2, this.borderColour, 100, (y3 - y2 + 1), newCorner, 0, 0);}
}
else
{ if(y3 >= y1)
{ this.drawPixel(intx, (y1 + 1), this.borderColour, 100, (y3 - y1), newCorner, 0, 0);}
}
var outsideColour = this.borderColour;}
else
{ var outsideColour = this.boxColour; var y3 = y1;}
if(this.settings.antiAlias)
{ for(var inty = (y3 + 1); inty < y4; inty++)
{ this.drawPixel(intx, inty, outsideColour, (pixelFraction(intx, inty , j) * 100), 1, newCorner, ((this.borderWidth > 0)? 0 : -1), this.settings[cc].radius);}
}
}
this.masterCorners[this.settings[cc].radius] = newCorner.cloneNode(true);}
if(cc != "br")
{ for(var t = 0, k = newCorner.childNodes.length; t < k; t++)
{ var pixelBar = newCorner.childNodes[t]; var pixelBarTop = parseInt(pixelBar.style.top.substring(0, pixelBar.style.top.indexOf("px"))); var pixelBarLeft = parseInt(pixelBar.style.left.substring(0, pixelBar.style.left.indexOf("px"))); var pixelBarHeight = parseInt(pixelBar.style.height.substring(0, pixelBar.style.height.indexOf("px"))); if(cc == "tl" || cc == "bl"){ pixelBar.style.left = this.settings[cc].radius -pixelBarLeft -1 + "px";}
if(cc == "tr" || cc == "tl"){ pixelBar.style.top = this.settings[cc].radius -pixelBarHeight -pixelBarTop + "px";}
switch(cc)
{ case "tr":
pixelBar.style.backgroundPosition = "-" + Math.abs((this.boxWidth - this.settings[cc].radius + this.borderWidth) + pixelBarLeft) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "tl":
pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs(this.settings[cc].radius -pixelBarHeight -pixelBarTop - this.borderWidth) + "px"; break; case "bl":
pixelBar.style.backgroundPosition = "-" + Math.abs((this.settings[cc].radius -pixelBarLeft -1) - this.borderWidth) + "px -" + Math.abs((this.boxHeight + this.settings[cc].radius + pixelBarTop) -this.borderWidth) + "px"; break;}
}
}
}
if(newCorner)
{ switch(cc)
{ case "tl":
if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "tr":
if(newCorner.style.position == "absolute") newCorner.style.top = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.topContainer) this.topContainer.appendChild(newCorner); break; case "bl":
if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.left = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break; case "br":
if(newCorner.style.position == "absolute") newCorner.style.bottom = "0px"; if(newCorner.style.position == "absolute") newCorner.style.right = "0px"; if(this.bottomContainer) this.bottomContainer.appendChild(newCorner); break;}
}
}
}
var radiusDiff = new Array(); radiusDiff["t"] = Math.abs(this.settings.tl.radius - this.settings.tr.radius)
radiusDiff["b"] = Math.abs(this.settings.bl.radius - this.settings.br.radius); for(z in radiusDiff)
{ if(z == "t" || z == "b")
{ if(radiusDiff[z])
{ var smallerCornerType = ((this.settings[z + "l"].radius < this.settings[z + "r"].radius)? z +"l" : z +"r"); var newFiller = document.createElement("DIV"); newFiller.style.height = radiusDiff[z] + "px"; newFiller.style.width = this.settings[smallerCornerType].radius+ "px"
newFiller.style.position = "absolute"; newFiller.style.fontSize = "1px"; newFiller.style.overflow = "hidden"; newFiller.style.backgroundColor = this.boxColour; switch(smallerCornerType)
{ case "tl":
newFiller.style.bottom = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.topContainer.appendChild(newFiller); break; case "tr":
newFiller.style.bottom = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.topContainer.appendChild(newFiller); break; case "bl":
newFiller.style.top = "0px"; newFiller.style.left = "0px"; newFiller.style.borderLeft = this.borderString; this.bottomContainer.appendChild(newFiller); break; case "br":
newFiller.style.top = "0px"; newFiller.style.right = "0px"; newFiller.style.borderRight = this.borderString; this.bottomContainer.appendChild(newFiller); break;}
}
var newFillerBar = document.createElement("DIV"); newFillerBar.style.position = "relative"; newFillerBar.style.fontSize = "1px"; newFillerBar.style.overflow = "hidden"; newFillerBar.style.backgroundColor = this.boxColour; newFillerBar.style.backgroundImage = this.backgroundImage; switch(z)
{ case "t":
if(this.topContainer)
{ if(this.settings.tl.radius && this.settings.tr.radius)
{ newFillerBar.style.height = topMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.tl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.tr.radius - this.borderWidth + "px"; newFillerBar.style.borderTop = this.borderString; if(this.backgroundImage != "")
newFillerBar.style.backgroundPosition = "-" + (topMaxRadius + this.borderWidth) + "px 0px"; this.topContainer.appendChild(newFillerBar);}
this.box.style.backgroundPosition = "0px -" + (topMaxRadius - this.borderWidth) + "px";}
break; case "b":
if(this.bottomContainer)
{ if(this.settings.bl.radius && this.settings.br.radius)
{ newFillerBar.style.height = botMaxRadius - this.borderWidth + "px"; newFillerBar.style.marginLeft = this.settings.bl.radius - this.borderWidth + "px"; newFillerBar.style.marginRight = this.settings.br.radius - this.borderWidth + "px"; newFillerBar.style.borderBottom = this.borderString; if(this.backgroundImage != "")
newFillerBar.style.backgroundPosition = "-" + (botMaxRadius + this.borderWidth) + "px -" + (this.boxHeight + (topMaxRadius + this.borderWidth)) + "px"; this.bottomContainer.appendChild(newFillerBar);}
}
break;}
}
}
if(this.settings.autoPad == true && this.boxPadding > 0)
{ var contentContainer = document.createElement("DIV"); contentContainer.style.position = "relative"; contentContainer.innerHTML = this.boxContent; contentContainer.className = "autoPadDiv"; var topPadding = Math.abs(topMaxRadius - this.boxPadding); var botPadding = Math.abs(botMaxRadius - this.boxPadding); if(topMaxRadius < this.boxPadding)
contentContainer.style.paddingTop = topPadding + "px"; if(botMaxRadius < this.boxPadding)
contentContainer.style.paddingBottom = botMaxRadius + "px"; contentContainer.style.paddingLeft = this.boxPadding + "px"; contentContainer.style.paddingRight = this.boxPadding + "px"; this.contentDIV = this.box.appendChild(contentContainer);}
}
this.drawPixel = function(intx, inty, colour, transAmount, height, newCorner, image, cornerRadius)
{ var pixel = document.createElement("DIV"); pixel.style.height = height + "px"; pixel.style.width = "1px"; pixel.style.position = "absolute"; pixel.style.fontSize = "1px"; pixel.style.overflow = "hidden"; var topMaxRadius = Math.max(this.settings["tr"].radius, this.settings["tl"].radius); if(image == -1 && this.backgroundImage != "")
{ pixel.style.backgroundImage = this.backgroundImage; pixel.style.backgroundPosition = "-" + (this.boxWidth - (cornerRadius - intx) + this.borderWidth) + "px -" + ((this.boxHeight + topMaxRadius + inty) -this.borderWidth) + "px";}
else
{ pixel.style.backgroundColor = colour;}
if (transAmount != 100)
setOpacity(pixel, transAmount); pixel.style.top = inty + "px"; pixel.style.left = intx + "px"; newCorner.appendChild(pixel);}
}
function insertAfter(parent, node, referenceNode)
{ parent.insertBefore(node, referenceNode.nextSibling);}
function BlendColour(Col1, Col2, Col1Fraction)
{ var red1 = parseInt(Col1.substr(1,2),16); var green1 = parseInt(Col1.substr(3,2),16); var blue1 = parseInt(Col1.substr(5,2),16); var red2 = parseInt(Col2.substr(1,2),16); var green2 = parseInt(Col2.substr(3,2),16); var blue2 = parseInt(Col2.substr(5,2),16); if(Col1Fraction > 1 || Col1Fraction < 0) Col1Fraction = 1; var endRed = Math.round((red1 * Col1Fraction) + (red2 * (1 - Col1Fraction))); if(endRed > 255) endRed = 255; if(endRed < 0) endRed = 0; var endGreen = Math.round((green1 * Col1Fraction) + (green2 * (1 - Col1Fraction))); if(endGreen > 255) endGreen = 255; if(endGreen < 0) endGreen = 0; var endBlue = Math.round((blue1 * Col1Fraction) + (blue2 * (1 - Col1Fraction))); if(endBlue > 255) endBlue = 255; if(endBlue < 0) endBlue = 0; return "#" + IntToHex(endRed)+ IntToHex(endGreen)+ IntToHex(endBlue);}
function IntToHex(strNum)
{ base = strNum / 16; rem = strNum % 16; base = base - (rem / 16); baseS = MakeHex(base); remS = MakeHex(rem); return baseS + '' + remS;}
function MakeHex(x)
{ if((x >= 0) && (x <= 9))
{ return x;}
else
{ switch(x)
{ case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F";}
}
}
function pixelFraction(x, y, r)
{ var pixelfraction = 0; var xvalues = new Array(1); var yvalues = new Array(1); var point = 0; var whatsides = ""; var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x,2))); if ((intersect >= y) && (intersect < (y+1)))
{ whatsides = "Left"; xvalues[point] = 0; yvalues[point] = intersect - y; point = point + 1;}
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y+1,2))); if ((intersect >= x) && (intersect < (x+1)))
{ whatsides = whatsides + "Top"; xvalues[point] = intersect - x; yvalues[point] = 1; point = point + 1;}
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(x+1,2))); if ((intersect >= y) && (intersect < (y+1)))
{ whatsides = whatsides + "Right"; xvalues[point] = 1; yvalues[point] = intersect - y; point = point + 1;}
var intersect = Math.sqrt((Math.pow(r,2) - Math.pow(y,2))); if ((intersect >= x) && (intersect < (x+1)))
{ whatsides = whatsides + "Bottom"; xvalues[point] = intersect - x; yvalues[point] = 0;}
switch (whatsides)
{ case "LeftRight":
pixelfraction = Math.min(yvalues[0],yvalues[1]) + ((Math.max(yvalues[0],yvalues[1]) - Math.min(yvalues[0],yvalues[1]))/2); break; case "TopRight":
pixelfraction = 1-(((1-xvalues[0])*(1-yvalues[1]))/2); break; case "TopBottom":
pixelfraction = Math.min(xvalues[0],xvalues[1]) + ((Math.max(xvalues[0],xvalues[1]) - Math.min(xvalues[0],xvalues[1]))/2); break; case "LeftBottom":
pixelfraction = (yvalues[0]*xvalues[1])/2; break; default:
pixelfraction = 1;}
return pixelfraction;}
function rgb2Hex(rgbColour)
{ try{ var rgbArray = rgb2Array(rgbColour); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); var hexColour = "#" + IntToHex(red) + IntToHex(green) + IntToHex(blue);}
catch(e){ alert("There was an error converting the RGB value to Hexadecimal in function rgb2Hex");}
return hexColour;}
function rgb2Array(rgbColour)
{ var rgbValues = rgbColour.substring(4, rgbColour.indexOf(")")); var rgbArray = rgbValues.split(", "); return rgbArray;}
function setOpacity(obj, opacity)
{ opacity = (opacity == 100)?99.999:opacity; if(isSafari && obj.tagName != "IFRAME")
{ var rgbArray = rgb2Array(obj.style.backgroundColor); var red = parseInt(rgbArray[0]); var green = parseInt(rgbArray[1]); var blue = parseInt(rgbArray[2]); obj.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + opacity/100 + ")";}
else if(typeof(obj.style.opacity) != "undefined")
{ obj.style.opacity = opacity/100;}
else if(typeof(obj.style.MozOpacity) != "undefined")
{ obj.style.MozOpacity = opacity/100;}
else if(typeof(obj.style.filter) != "undefined")
{ obj.style.filter = "alpha(opacity:" + opacity + ")";}
else if(typeof(obj.style.KHTMLOpacity) != "undefined")
{ obj.style.KHTMLOpacity = opacity/100;}
}
function inArray(array, value)
{ for(var i = 0; i < array.length; i++){ if (array[i] === value) return i;}
return false;}
function inArrayKey(array, value)
{ for(key in array){ if(key === value) return true;}
return false;}
function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true;}
else if (elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r;}
else { elm['on' + evType] = fn;}
}
function removeEvent(obj, evType, fn, useCapture){ if (obj.removeEventListener){ obj.removeEventListener(evType, fn, useCapture); return true;} else if (obj.detachEvent){ var r = obj.detachEvent("on"+evType, fn); return r;} else { alert("Handler could not be removed");}
}
function format_colour(colour)
{ var returnColour = "#ffffff"; if(colour != "" && colour != "transparent")
{ if(colour.substr(0, 3) == "rgb")
{ returnColour = rgb2Hex(colour);}
else if(colour.length == 4)
{ returnColour = "#" + colour.substring(1, 2) + colour.substring(1, 2) + colour.substring(2, 3) + colour.substring(2, 3) + colour.substring(3, 4) + colour.substring(3, 4);}
else
{ returnColour = colour;}
}
return returnColour;}
function get_style(obj, property, propertyNS)
{ try
{ if(obj.currentStyle)
{ var returnVal = eval("obj.currentStyle." + property);}
else
{ if(isSafari && obj.style.display == "none")
{ obj.style.display = ""; var wasHidden = true;}
var returnVal = document.defaultView.getComputedStyle(obj, '').getPropertyValue(propertyNS); if(isSafari && wasHidden)
{ obj.style.display = "none";}
}
}
catch(e)
{ }
return returnVal;}
function getElementsByClass(searchClass, node, tag)
{ var classElements = new Array(); if(node == null)
node = document; if(tag == null)
tag = '*'; var els = node.getElementsByTagName(tag); var elsLen = els.length; var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)"); for (i = 0, j = 0; i < elsLen; i++)
{ if(pattern.test(els[i].className))
{ classElements[j] = els[i]; j++;}
}
return classElements;}
function newCurvyError(errorMessage)
{ return new Error("curvyCorners Error:\n" + errorMessage)
}
@@ -1,138 +0,0 @@
body {
background-color: #E1D1F1;
font-family: "Georgia", sans-serif;
font-size: 16px;
line-height: 1.6em;
padding: 1.6em 0 0 0;
color: #333;
}
h1, h2, h3, h4, h5, h6 {
color: #444;
}
h1 {
font-family: sans-serif;
font-weight: normal;
font-size: 4em;
line-height: 0.8em;
letter-spacing: -0.1ex;
margin: 5px;
}
li {
padding: 0;
margin: 0;
list-style-type: square;
}
a {
color: #5E5AFF;
background-color: #DAC;
font-weight: normal;
text-decoration: underline;
}
blockquote {
font-size: 90%;
font-style: italic;
border-left: 1px solid #111;
padding-left: 1em;
}
.caps {
font-size: 80%;
}
#main {
width: 45em;
padding: 0;
margin: 0 auto;
}
.coda {
text-align: right;
color: #77f;
font-size: smaller;
}
table {
font-size: 90%;
line-height: 1.4em;
color: #ff8;
background-color: #111;
padding: 2px 10px 2px 10px;
border-style: dashed;
}
th {
color: #fff;
}
td {
padding: 2px 10px 2px 10px;
}
.success {
color: #0CC52B;
}
.failed {
color: #E90A1B;
}
.unknown {
color: #995000;
}
pre, code {
font-family: monospace;
font-size: 90%;
line-height: 1.4em;
color: #ff8;
background-color: #111;
padding: 2px 10px 2px 10px;
}
.comment { color: #aaa; font-style: italic; }
.keyword { color: #eff; font-weight: bold; }
.punct { color: #eee; font-weight: bold; }
.symbol { color: #0bb; }
.string { color: #6b4; }
.ident { color: #ff8; }
.constant { color: #66f; }
.regex { color: #ec6; }
.number { color: #F99; }
.expr { color: #227; }
#version {
float: right;
text-align: right;
font-family: sans-serif;
font-weight: normal;
background-color: #B3ABFF;
color: #141331;
padding: 15px 20px 10px 20px;
margin: 0 auto;
margin-top: 15px;
border: 3px solid #141331;
}
#version .numbers {
display: block;
font-size: 4em;
line-height: 0.8em;
letter-spacing: -0.1ex;
margin-bottom: 15px;
}
#version p {
text-decoration: none;
color: #141331;
background-color: #B3ABFF;
margin: 0;
padding: 0;
}
#version a {
text-decoration: none;
color: #141331;
background-color: #B3ABFF;
}
.clickable {
cursor: pointer;
cursor: hand;
}
-48
View File
@@ -1,48 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>
<%= title %>
</title>
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
<style>
</style>
<script type="text/javascript">
window.onload = function() {
settings = {
tl: { radius: 10 },
tr: { radius: 10 },
bl: { radius: 10 },
br: { radius: 10 },
antiAlias: true,
autoPad: true,
validTags: ["div"]
}
var versionBox = new curvyCorners(settings, document.getElementById("version"));
versionBox.applyCornersToAll();
}
</script>
</head>
<body>
<div id="main">
<h1><%= title %></h1>
<div id="version" class="clickable" onclick='document.location = "<%= download %>"; return false'>
<p>Get Version</p>
<a href="<%= download %>" class="numbers"><%= version %></a>
</div>
<%= body %>
<p class="coda">
<a href="cjheath@rubyforge.org">Clifford Heath</a>, <%= modified.pretty %><br>
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
</p>
</div>
<!-- insert site tracking codes here, like Google Urchin -->
</body>
</html>
-164
View File
@@ -1,164 +0,0 @@
Tutorial
========
Languages can be split into two components, their *syntax* and their *semantics*. It's your understanding of English syntax that tells you the stream of words "Sleep furiously green ideas colorless" is not a valid sentence. Semantics is deeper. Even if we rearrange the above sentence to be "Colorless green ideas sleep furiously", which is syntactically correct, it remains nonsensical on a semantic level. With Treetop, you'll be dealing with languages that are much simpler than English, but these basic concepts apply. Your programs will need to address both the syntax and the semantics of the languages they interpret.
Treetop equips you with powerful tools for each of these two aspects of interpreter writing. You'll describe the syntax of your language with a *parsing expression grammar*. From this description, Treetop will generate a Ruby parser that transforms streams of characters written into your language into *abstract syntax trees* representing their structure. You'll then describe the semantics of your language in Ruby by defining methods on the syntax trees the parser generates.
Parsing Expression Grammars, The Basics
=======================================
The first step in using Treetop is defining a grammar in a file with the `.treetop` extension. Here's a grammar that's useless because it's empty:
# my_grammar.treetop
grammar MyGrammar
end
Next, you start filling your grammar with rules. Each rule associates a name with a parsing expression, like the following:
# my_grammar.treetop
# You can use a .tt extension instead if you wish
grammar MyGrammar
rule hello
'hello chomsky'
end
end
The first rule becomes the *root* of the grammar, causing its expression to be matched when a parser for the grammar is fed a string. The above grammar can now be used in a Ruby program. Notice how a string matching the first rule parses successfully, but a second nonmatching string does not.
# use_grammar.rb
require 'rubygems'
require 'treetop'
Treetop.load 'my_grammar'
# or just:
# require 'my_grammar' # This works because Polyglot hooks "require" to find and load Treetop files
parser = MyGrammarParser.new
puts parser.parse('hello chomsky') # => Treetop::Runtime::SyntaxNode
puts parser.parse('silly generativists!') # => nil
Users of *regular expressions* will find parsing expressions familiar. They share the same basic purpose, matching strings against patterns. However, parsing expressions can recognize a broader category of languages than their less expressive brethren. Before we get into demonstrating that, lets cover some basics. At first parsing expressions won't seem much different. Trust that they are.
Terminal Symbols
----------------
The expression in the grammar above is a terminal symbol. It will only match a string that matches it exactly. There are two other kinds of terminal symbols, which we'll revisit later. Terminals are called *atomic expressions* because they aren't composed of smaller expressions.
Ordered Choices
---------------
Ordered choices are *composite expressions*, which allow for any of several subexpressions to be matched. These should be familiar from regular expressions, but in parsing expressions, they are delimited by the `/` character. Its important to note that the choices are prioritized in the order they appear. If an earlier expression is matched, no subsequent expressions are tried. Here's an example:
# my_grammar.treetop
grammar MyGrammar
rule hello
'hello chomsky' / 'hello lambek'
end
end
# fragment of use_grammar.rb
puts parser.parse('hello chomsky') # => Treetop::Runtime::SyntaxNode
puts parser.parse('hello lambek') # => Treetop::Runtime::SyntaxNode
puts parser.parse('silly generativists!') # => nil
Note that once a choice rule has matched the text using a particular alternative at a particular location in the input and hence has succeeded, that choice will never be reconsidered, even if the chosen alternative causes another rule to fail where a later alternative wouldn't have. It's always a later alternative, since the first to succeed is final - why keep looking when you've found what you wanted? This is a feature of PEG parsers that you need to understand if you're going to succeed in using Treetop. In order to memoize success and failures, such decisions cannot be reversed. Luckily Treetop provides a variety of clever ways you can tell it to avoid making the wrong decisions. But more on that later.
Sequences
---------
Sequences are composed of other parsing expressions separated by spaces. Using sequences, we can tighten up the above grammar.
# my_grammar.treetop
grammar MyGrammar
rule hello
'hello ' ('chomsky' / 'lambek')
end
end
Note the use of parentheses to override the default precedence rules, which bind sequences more tightly than choices.
Once the whole sequence has been matched, the result is memoized and the details of the match will not be reconsidered for that location in the input.
Nonterminal Symbols
-------------------
Here we leave regular expressions behind. Nonterminals allow expressions to refer to other expressions by name. A trivial use of this facility would allow us to make the above grammar more readable should the list of names grow longer.
# my_grammar.treetop
grammar MyGrammar
rule hello
'hello ' linguist
end
rule linguist
'chomsky' / 'lambek' / 'jacobsen' / 'frege'
end
end
The true power of this facility, however, is unleashed when writing *recursive expressions*. Here is a self-referential expression that can match any number of open parentheses followed by any number of closed parentheses. This is theoretically impossible with regular expressions due to the *pumping lemma*.
# parentheses.treetop
grammar Parentheses
rule parens
'(' parens ')' / ''
end
end
The `parens` expression simply states that a `parens` is a set of parentheses surrounding another `parens` expression or, if that doesn't match, the empty string. If you are uncomfortable with recursion, its time to get comfortable, because it is the basis of language. Here's a tip: Don't try and imagine the parser circling round and round through the same rule. Instead, imagine the rule is *already* defined while you are defining it. If you imagine that `parens` already matches a string of matching parentheses, then its easy to think of `parens` as an open and closing parentheses around another set of matching parentheses, which conveniently, you happen to be defining. You know that `parens` is supposed to represent a string of matched parentheses, so trust in that meaning, even if you haven't fully implemented it yet.
Repetition
----------
Any item in a rule may be followed by a '+' or a '*' character, signifying one-or-more and zero-or-more occurrences of that item. Beware though; the match is greedy, and if it matches too many items and causes subsequent items in the sequence to fail, the number matched will never be reconsidered. Here's a simple example of a rule that will never succeed:
# toogreedy.treetop
grammar TooGreedy
rule a_s
'a'* 'a'
end
end
The 'a'* will always eat up any 'a's that follow, and the subsequent 'a' will find none there, so the whole rule will fail. You might need to use lookahead to avoid matching too much.
Negative Lookahead
------------------
When you need to ensure that the following item *doesn't* match in some case where it might otherwise, you can use negat!ve lookahead, which is an item preceeded by a ! - here's an example:
# postcondition.treetop
grammar PostCondition
rule conditional_sentence
( !conditional_keyword word )+ conditional_keyword [ \t]+ word*
end
rule word
([a-zA-Z]+ [ \t]+)
end
rule conditional_keyword
'if' / 'while' / 'until'
end
end
Even though the rule `word` would match any of the conditional keywords, the first words of a conditional_sentence must not be conditional_keywords. The negative lookahead prevents that matching, and prevents the repetition from matching too much input. Note that the lookahead may be a grammar rule of any complexity, including one that isn't used elsewhere in your grammar.
Positive lookahead
------------------
Sometimes you want an item to match, but only if the *following* text would match some pattern. You don't want to consume that following text, but if it's not there, you want this rule to fail. You can append a positive lookahead like this to a rule by appending the lookahead rule preceeded by an & character.
Features to cover in the talk
=============================
* Treetop files
* Grammar definition
* Rules
* Loading a grammar
* Compiling a grammar with the `tt` command
* Accessing a parser for the grammar from Ruby
* Parsing Expressions of all kinds
? Left recursion and factorization
- Here I can talk about function application, discussing how the operator
could be an arbitrary expression
* Inline node class eval blocks
* Node class declarations
* Labels
* Use of super within within labels
* Grammar composition with include
* Use of super with grammar composition
-20
View File
@@ -1,20 +0,0 @@
dir = File.dirname(__FILE__)
require 'rubygems'
require 'rake'
$LOAD_PATH.unshift(File.join(dir, 'vendor', 'rspec', 'lib'))
require 'spec/rake/spectask'
Gem::manage_gems
require 'rake/gempackagetask'
task :default => :spec
Spec::Rake::SpecTask.new do |t|
t.pattern = 'spec/**/*spec.rb'
end
load "./treetop.gemspec"
Rake::GemPackageTask.new($gemspec) do |pkg|
pkg.need_tar = true
end
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env ruby
require 'rubygems'
gem 'treetop'
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
require 'treetop'
if ARGV.empty?
puts "Usage:\n\ntt foo.treetop bar.treetop ...\n or\ntt foo.treetop -o alternate_name.rb\n\n"
exit
end
compiler = Treetop::Compiler::GrammarCompiler.new
while !ARGV.empty?
treetop_file = ARGV.shift
if !File.exist?(treetop_file)
puts "Error: file '#{treetop_file}' doesn't exist\n\n"
exit(2)
end
if ARGV.size >= 2 && ARGV[1] == '-o'
ARGV.shift # explicit output file name option
compiler.compile(treetop_file, ARGV.shift)
else
# list of input files option
compiler.compile(treetop_file)
end
end
@@ -1,103 +0,0 @@
#Google Group
I've created a <a href="http://groups.google.com/group/treetop-dev">Google Group</a> as a better place to organize discussion and development.
treetop-dev@google-groups.com
#Contributing
Visit <a href="http://github.com/nathansobo/treetop/tree/master">the Treetop repository page on GitHub</a> in your browser for more information about checking out the source code.
I like to try Rubinius's policy regarding commit rights. If you submit one patch worth integrating, I'll give you commit rights. We'll see how this goes, but I think it's a good policy.
##Getting Started with the Code
Treetop compiler is interesting in that it is implemented in itself. Its functionality revolves around `metagrammar.treetop`, which specifies the grammar for Treetop grammars. I took a hybrid approach with regard to definition of methods on syntax nodes in the metagrammar. Methods that are more syntactic in nature, like those that provide access to elements of the syntax tree, are often defined inline, directly in the grammar. More semantic methods are defined in custom node classes.
Iterating on the metagrammar is tricky. The current testing strategy uses the last stable version of Treetop to parse the version under test. Then the version under test is used to parse and functionally test the various pieces of syntax it should recognize and translate to Ruby. As you change `metagrammar.treetop` and its associated node classes, note that the node classes you are changing are also used to support the previous stable version of the metagrammar, so must be kept backward compatible until such time as a new stable version can be produced to replace it.
##Tests
Most of the compiler's tests are functional in nature. The grammar under test is used to parse and compile piece of sample code. Then I attempt to parse input with the compiled output and test its results.
#What Needs to be Done
##Small Stuff
* Improve the `tt` command line tool to allow `.treetop` extensions to be elided in its arguments.
* Generate and load temp files with `Treetop.load` rather than evaluating strings to improve stack trace readability.
* Allow `do/end` style blocks as well as curly brace blocks. This was originally omitted because I thought it would be confusing. It probably isn't.
##Big Stuff
####Transient Expressions
Currently, every parsing expression instantiates a syntax node. This includes even very simple parsing expressions, like single characters. It is probably unnecessary for every single expression in the parse to correspond to its own syntax node, so much savings could be garnered from a transient declaration that instructs the parser only to attempt a match without instantiating nodes.
###Generate Rule Implementations in C
Parsing expressions are currently compiled into simple Ruby source code that comprises the body of parsing rules, which are translated into Ruby methods. The generator could produce C instead of Ruby in the body of these method implementations.
###Global Parsing State and Semantic Backtrack Triggering
Some programming language grammars are not entirely context-free, requiring that global state dictate the behavior of the parser in certain circumstances. Treetop does not currently expose explicit parser control to the grammar writer, and instead automatically constructs the syntax tree for them. A means of semantic parser control compatible with this approach would involve callback methods defined on parsing nodes. Each time a node is successfully parsed it will be given an opportunity to set global state and optionally trigger a parse failure on _extrasyntactic_ grounds. Nodes will probably need to define an additional method that undoes their changes to global state when there is a parse failure and they are backtracked.
Here is a sketch of the potential utility of such mechanisms. Consider the structure of YAML, which uses indentation to indicate block structure.
level_1:
level_2a:
level_2b:
level_3a:
level_2c:
Imagine a grammar like the following:
rule yaml_element
name ':' block
/
name ':' value
end
rule block
indent yaml_elements outdent
end
rule yaml_elements
yaml_element (samedent yaml_element)*
end
rule samedent
newline spaces {
def after_success(parser_state)
spaces.length == parser_state.indent_level
end
}
end
rule indent
newline spaces {
def after_success(parser_state)
if spaces.length == parser_state.indent_level + 2
parser_state.indent_level += 2
true
else
false # fail the parse on extrasyntactic grounds
end
end
def undo_success(parser_state)
parser_state.indent_level -= 2
end
}
end
rule outdent
newline spaces {
def after_success(parser_state)
if spaces.length == parser_state.indent_level - 2
parser_state.indent_level -= 2
true
else
false # fail the parse on extrasyntactic grounds
end
end
def undo_success(parser_state)
parser_state.indent_level += 2
end
}
end
In this case a block will be detected only if a change in indentation warrants it. Note that this change in the state of indentation must be undone if a subsequent failure causes this node not to ultimately be incorporated into a successful result.
I am by no means sure that the above sketch is free of problems, or even that this overall strategy is sound, but it seems like a promising path.
@@ -1,65 +0,0 @@
#Grammar Composition
A unique property of parsing expression grammars is that they are _closed under composition_. This means that when you compose two grammars they yield another grammar that can be composed yet again. This is a radical departure from parsing frameworks require on lexical scanning, which makes compositionally impossible. Treetop's facilities for composition are built upon those of Ruby.
##The Mapping of Treetop Constructs to Ruby Constructs
When Treetop compiles a grammar definition, it produces a module and a class. The module contains methods implementing all of the rules defined in the grammar. The generated class is a subclass of Treetop::Runtime::CompiledParser and includes the module. For example:
grammar Foo
...
end
results in a Ruby module named `Foo` and a Ruby class named `FooParser` that `include`s the `Foo` module.
##Using Mixin Semantics to Compose Grammars
Because grammars are just modules, they can be mixed into one another. This enables grammars to share rules.
grammar A
rule a
'a'
end
end
grammar B
include A
rule ab
a 'b'
end
end
Grammar `B` above references rule `a` defined in a separate grammar that it includes. Because module inclusion places modules in the ancestor chain, rules may also be overridden with the use of the `super` keyword accessing the overridden rule.
grammar A
rule a
'a'
end
end
grammar B
include A
rule a
super / 'b'
end
end
Now rule `a` in grammar `B` matches either `'a'` or `'b'`.
##Motivation
Imagine a grammar for Ruby that took account of SQL queries embedded in strings within the language. That could be achieved by combining two existing grammars.
grammar RubyPlusSQL
include Ruby
include SQL
rule expression
ruby_expression
end
rule ruby_string
ruby_quote sql_expression ruby_quote / ruby_string
end
end
##Work to be Done
It has become clear that the include facility in grammars would be more useful if it had the ability to name prefix all rules from the included grammar to avoid collision. This is a planned but currently unimplemented feature.
-90
View File
@@ -1,90 +0,0 @@
<p class="intro_text">
Treetop is a language for describing languages. Combining the elegance of Ruby with cutting-edge <em>parsing expression grammars</em>, it helps you analyze syntax with revolutionarily ease.
</p>
sudo gem install treetop
#Intuitive Grammar Specifications
Parsing expression grammars (PEGs) are simple to write and easy to maintain. They are a simple but powerful generalization of regular expressions that are easier to work with than the LALR or LR-1 grammars of traditional parser generators. There's no need for a tokenization phase, and _lookahead assertions_ can be used for a limited degree of context-sensitivity. Here's an extremely simple Treetop grammar that matches a subset of arithmetic, respecting operator precedence:
grammar Arithmetic
rule additive
multitive '+' additive / multitive
end
rule multitive
primary '*' multitive / primary
end
rule primary
'(' additive ')' / number
end
rule number
[1-9] [0-9]*
end
end
#Syntax-Oriented Programming
Rather than implementing semantic actions that construct parse trees, Treetop lets you define methods on trees that it constructs for you automatically. You can define these methods directly within the grammar...
grammar Arithmetic
rule additive
multitive '+' additive {
def value
multitive.value + additive.value
end
}
/
multitive
end
# other rules below ...
end
...or associate rules with classes of nodes you wish your parsers to instantiate upon matching a rule.
grammar Arithmetic
rule additive
multitive '+' additive <AdditiveNode>
/
multitive
end
# other rules below ...
end
#Reusable, Composable Language Descriptions
Because PEGs are closed under composition, Treetop grammars can be treated like Ruby modules. You can mix them into one another and override rules with access to the `super` keyword. You can break large grammars down into coherent units or make your language's syntax modular. This is especially useful if you want other programmers to be able to reuse your work.
grammar RubyWithEmbeddedSQL
include SQL
rule string
quote sql_expression quote / super
end
end
#Acknowledgements
<a href="http://pivotallabs.com"><img id="pivotal_logo" src="./images/pivotal.gif"></a>
First, thank you to my employer Rob Mee of <a href="http://pivotallabs.com"/>Pivotal Labs</a> for funding a substantial portion of Treetop's development. He gets it.
I'd also like to thank:
* Damon McCormick for several hours of pair programming.
* Nick Kallen for lots of well-considered feedback and a few afternoons of programming.
* Brian Takita for a night of pair programming.
* Eliot Miranda for urging me rewrite as a compiler right away rather than putting it off.
* Ryan Davis and Eric Hodel for hurting my code.
* Dav Yaginuma for kicking me into action on my idea.
* Bryan Ford for his seminal work on Packrat Parsers.
* The editors of Lambda the Ultimate, where I discovered parsing expression grammars.
@@ -1,51 +0,0 @@
#Pitfalls
##Left Recursion
An weakness shared by all recursive descent parsers is the inability to parse left-recursive rules. Consider the following rule:
rule left_recursive
left_recursive 'a' / 'a'
end
Logically it should match a list of 'a' characters. But it never consumes anything, because attempting to recognize `left_recursive` begins by attempting to recognize `left_recursive`, and so goes an infinite recursion. There's always a way to eliminate these types of structures from your grammar. There's a mechanistic transformation called _left factorization_ that can eliminate it, but it isn't always pretty, especially in combination with automatically constructed syntax trees. So far, I have found more thoughtful ways around the problem. For instance, in the interpreter example I interpret inherently left-recursive function application right recursively in syntax, then correct the directionality in my semantic interpretation. You may have to be clever.
#Advanced Techniques
Here are a few interesting problems I've encountered. I figure sharing them may give you insight into how these types of issues are addressed with the tools of parsing expressions.
##Matching a String
rule string
'"' (!'"' . / '\"')* '"'
end
This expression says: Match a quote, then zero or more of any character but a quote or an escaped quote followed by a quote. Lookahead assertions are essential for these types of problems.
##Matching Nested Structures With Non-Unique Delimeters
Say I want to parse a diabolical wiki syntax in which the following interpretations apply.
** *hello* ** --> <strong><em>hello</em></strong>
* **hello** * --> <em><strong>hello</strong></em>
rule strong
'**' (em / !'*' . / '\*')+ '**'
end
rule em
'**' (strong / !'*' . / '\*')+ '**'
end
Emphasized text is allowed within strong text by virtue of `em` being the first alternative. Since `em` will only successfully parse if a matching `*` is found, it is permitted, but other than that, no `*` characters are allowed unless they are escaped.
##Matching a Keyword But Not Words Prefixed Therewith
Say I want to consider a given string a characters only when it occurs in isolation. Lets use the `end` keyword as an example. We don't want the prefix of `'enders_game'` to be considered a keyword. A naiive implementation might be the following.
rule end_keyword
'end' &space
end
This says that `'end'` must be followed by a space, but this space is not consumed as part of the matching of `keyword`. This works in most cases, but is actually incorrect. What if `end` occurs at the end of the buffer? In that case, it occurs in isolation but will not match the above expression. What we really mean is that `'end'` cannot be followed by a _non-space_ character.
rule end_keyword
'end' !(!' ' .)
end
In general, when the syntax gets tough, it helps to focus on what you really mean. A keyword is a character not followed by another character that isn't a space.
@@ -1,189 +0,0 @@
#Semantic Interpretation
Lets use the below grammar as an example. It describes parentheses wrapping a single character to an arbitrary depth.
grammar ParenLanguage
rule parenthesized_letter
'(' parenthesized_letter ')'
/
[a-z]
end
end
Matches:
* `'a'`
* `'(a)'`
* `'((a))'`
* etc.
Output from a parser for this grammar looks like this:
![Tree Returned By ParenLanguageParser](./images/paren_language_output.png)
This is a parse tree whose nodes are instances of `Treetop::Runtime::SyntaxNode`. What if we could define methods on these node objects? We would then have an object-oriented program whose structure corresponded to the structure of our language. Treetop provides two techniques for doing just this.
##Associating Methods with Node-Instantiating Expressions
Sequences and all types of terminals are node-instantiating expressions. When they match, they create instances of `Treetop::Runtime::SyntaxNode`. Methods can be added to these nodes in the following ways:
###Inline Method Definition
Methods can be added to the nodes instantiated by the successful match of an expression
grammar ParenLanguage
rule parenthesized_letter
'(' parenthesized_letter ')' {
def depth
parenthesized_letter.depth + 1
end
}
/
[a-z] {
def depth
0
end
}
end
end
Note that each alternative expression is followed by a block containing a method definition. A `depth` method is defined on both expressions. The recursive `depth` method defined in the block following the first expression determines the depth of the nested parentheses and adds one two it. The base case is implemented in the block following the second expression; a single character has a depth of 0.
###Custom `SyntaxNode` Subclass Declarations
You can instruct the parser to instantiate a custom subclass of Treetop::Runtime::SyntaxNode for an expression by following it by the name of that class enclosed in angle brackets (`<>`). The above inline method definitions could have been moved out into a single class like so.
# in .treetop file
grammar ParenLanguage
rule parenthesized_letter
'(' parenthesized_letter ')' <ParenNode>
/
[a-z] <ParenNode>
end
end
# in separate .rb file
class ParenNode < Treetop::Runtime::SyntaxNode
def depth
if nonterminal?
parenthesized_letter.depth + 1
else
0
end
end
end
##Automatic Extension of Results
Nonterminal and ordered choice expressions do not instantiate new nodes, but rather pass through nodes that are instantiated by other expressions. They can extend nodes they propagate with anonymous or declared modules, using similar constructs used with expressions that instantiate their own syntax nodes.
###Extending a Propagated Node with an Anonymous Module
rule parenthesized_letter
('(' parenthesized_letter ')' / [a-z]) {
def depth
if nonterminal?
parenthesized_letter.depth + 1
else
0
end
end
}
end
The parenthesized choice above can result in a node matching either of the two choices. Than node will be extended with methods defined in the subsequent block. Note that a choice must always be parenthesized to be associated with a following block.
###Extending A Propagated Node with a Declared Module
# in .treetop file
rule parenthesized_letter
('(' parenthesized_letter ')' / [a-z]) <ParenNode>
end
# in separate .rb file
module ParenNode
def depth
if nonterminal?
parenthesized_letter.depth + 1
else
0
end
end
end
Here the result is extended with the `ParenNode` module. Note the previous example for node-instantiating expressions, the constant in the declaration must be a module because the result is extended with it.
##Automatically-Defined Element Accessor Methods
###Default Accessors
Nodes instantiated upon the matching of sequences have methods automatically defined for any nonterminals in the sequence.
rule abc
a b c {
def to_s
a.to_s + b.to_s + c.to_s
end
}
end
In the above code, the `to_s` method calls automatically-defined element accessors for the nodes returned by parsing nonterminals `a`, `b`, and `c`.
###Labels
Subexpressions can be given an explicit label to have an element accessor method defined for them. This is useful in cases of ambiguity between two references to the same nonterminal or when you need to access an unnamed subexpression.
rule labels
first_letter:[a-z] rest_letters:(', ' letter:[a-z])* {
def letters
[first_letter] + rest_letters.map do |comma_and_letter|
comma_and_letter.letter
end
end
}
end
The above grammar uses label-derived accessors to determine the letters in a comma-delimited list of letters. The labeled expressions _could_ have been extracted to their own rules, but if they aren't used elsewhere, labels still enable them to be referenced by a name within the expression's methods.
###Overriding Element Accessors
The module containing automatically defined element accessor methods is an ancestor of the module in which you define your own methods, meaning you can override them with access to the `super` keyword. Here's an example of how this fact can improve the readability of the example above.
rule labels
first_letter:[a-z] rest_letters:(', ' letter:[a-z])* {
def letters
[first_letter] + rest_letters
end
def rest_letters
super.map { |comma_and_letter| comma_and_letter.letter }
end
}
end
##Methods Available on `Treetop::Runtime::SyntaxNode`
<table>
<tr>
<td>
<code>terminal?</code>
</td>
<td>
Was this node produced by the matching of a terminal symbol?
</td>
</tr>
<tr>
<td>
<code>nonterminal?</code>
</td>
<td>
Was this node produced by the matching of a nonterminal symbol?
</td>
<tr>
<td>
<code>text_value</code>
</td>
<td>
The substring of the input represented by this node.
</td>
<tr>
<td>
<code>elements</code>
</td>
<td>
Available only on nonterminal nodes, returns the nodes parsed by the elements of the matched sequence.
</td>
</tr>
</table>
-110
View File
@@ -1,110 +0,0 @@
require 'rubygems'
require 'erector'
require "#{File.dirname(__FILE__)}/sitegen"
class Layout < Erector::Widget
def render
html do
head do
link :rel => "stylesheet",
:type => "text/css",
:href => "./screen.css"
rawtext %(
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-3418876-1";
urchinTracker();
</script>
)
end
body do
div :id => 'top' do
div :id => 'main_navigation' do
main_navigation
end
end
div :id => 'middle' do
div :id => 'content' do
content
end
end
div :id => 'bottom' do
end
end
end
end
def main_navigation
ul do
li { link_to "Documentation", SyntacticRecognition, Documentation }
li { link_to "Contribute", Contribute }
li { link_to "Home", Index }
end
end
def content
end
end
class Index < Layout
def content
bluecloth "index.markdown"
end
end
class Documentation < Layout
abstract
def content
div :id => 'secondary_navigation' do
ul do
li { link_to 'Syntax', SyntacticRecognition }
li { link_to 'Semantics', SemanticInterpretation }
li { link_to 'Using In Ruby', UsingInRuby }
li { link_to 'Advanced Techniques', PitfallsAndAdvancedTechniques }
end
end
div :id => 'documentation_content' do
documentation_content
end
end
end
class SyntacticRecognition < Documentation
def documentation_content
bluecloth "syntactic_recognition.markdown"
end
end
class SemanticInterpretation < Documentation
def documentation_content
bluecloth "semantic_interpretation.markdown"
end
end
class UsingInRuby < Documentation
def documentation_content
bluecloth "using_in_ruby.markdown"
end
end
class PitfallsAndAdvancedTechniques < Documentation
def documentation_content
bluecloth "pitfalls_and_advanced_techniques.markdown"
end
end
class Contribute < Layout
def content
bluecloth "contributing_and_planned_features.markdown"
end
end
Layout.generate_site
-60
View File
@@ -1,60 +0,0 @@
class Layout < Erector::Widget
class << self
def inherited(page_class)
puts page_class
(@@page_classes ||= []) << page_class
end
def generate_site
@@page_classes.each do |page_class|
page_class.generate_html unless page_class.abstract?
puts page_class
end
end
def generate_html
File.open(absolute_path, 'w') do |file|
file.write(new.render)
end
end
def absolute_path
absolutize(relative_path)
end
def relative_path
"#{name.gsub('::', '_').underscore}.html"
end
def absolutize(relative_path)
File.join(File.dirname(__FILE__), "site", relative_path)
end
def abstract
@abstract = true
end
def abstract?
@abstract
end
end
def bluecloth(relative_path)
File.open(File.join(File.dirname(__FILE__), relative_path)) do |file|
rawtext BlueCloth.new(file.read).to_html
end
end
def absolutize(relative_path)
self.class.absolutize(relative_path)
end
def link_to(link_text, page_class, section_class=nil)
if instance_of?(page_class) || section_class && is_a?(section_class)
text link_text
else
a link_text, :href => page_class.relative_path
end
end
end
@@ -1,100 +0,0 @@
#Syntactic Recognition
Treetop grammars are written in a custom language based on parsing expression grammars. Literature on the subject of <a href="http://en.wikipedia.org/wiki/Parsing_expression_grammar">parsing expression grammars</a> is useful in writing Treetop grammars.
#Grammar Structure
Treetop grammars look like this:
grammar GrammarName
rule rule_name
...
end
rule rule_name
...
end
...
end
The main keywords are:
* `grammar` : This introduces a new grammar. It is followed by a constant name to which the grammar will be bound when it is loaded.
* `rule` : This defines a parsing rule within the grammar. It is followed by a name by which this rule can be referenced within other rules. It is then followed by a parsing expression defining the rule.
#Parsing Expressions
Each rule associates a name with a _parsing expression_. Parsing expressions are a generalization of vanilla regular expressions. Their key feature is the ability to reference other expressions in the grammar by name.
##Terminal Symbols
###Strings
Strings are surrounded in double or single quotes and must be matched exactly.
* `"foo"`
* `'foo'`
###Character Classes
Character classes are surrounded by brackets. Their semantics are identical to those used in Ruby's regular expressions.
* `[a-zA-Z]`
* `[0-9]`
###The Anything Symbol
The anything symbol is represented by a dot (`.`) and matches any single character.
##Nonterminal Symbols
Nonterminal symbols are unquoted references to other named rules. They are equivalent to an inline substitution of the named expression.
rule foo
"the dog " bar
end
rule bar
"jumped"
end
The above grammar is equivalent to:
rule foo
"the dog jumped"
end
##Ordered Choice
Parsers attempt to match ordered choices in left-to-right order, and stop after the first successful match.
"foobar" / "foo" / "bar"
Note that if `"foo"` in the above expression came first, `"foobar"` would never be matched.
##Sequences
Sequences are a space-separated list of parsing expressions. They have higher precedence than choices, so choices must be parenthesized to be used as the elements of a sequence.
"foo" "bar" ("baz" / "bop")
##Zero or More
Parsers will greedily match an expression zero or more times if it is followed by the star (`*`) symbol.
* `'foo'*` matches the empty string, `"foo"`, `"foofoo"`, etc.
##One or More
Parsers will greedily match an expression one or more times if it is followed by the star (`+`) symbol.
* `'foo'+` does not match the empty string, but matches `"foo"`, `"foofoo"`, etc.
##Optional Expressions
An expression can be declared optional by following it with a question mark (`?`).
* `'foo'?` matches `"foo"` or the empty string.
##Lookahead Assertions
Lookahead assertions can be used to give parsing expressions a limited degree of context-sensitivity. The parser will look ahead into the buffer and attempt to match an expression without consuming input.
###Positive Lookahead Assertion
Preceding an expression with an ampersand `(&)` indicates that it must match, but no input will be consumed in the process of determining whether this is true.
* `"foo" &"bar"` matches `"foobar"` but only consumes up to the end `"foo"`. It will not match `"foobaz"`.
###Negative Lookahead Assertion
Preceding an expression with a bang `(!)` indicates that the expression must not match, but no input will be consumed in the process of determining whether this is true.
* `"foo" !"bar"` matches `"foobaz"` but only consumes up to the end `"foo"`. It will not match `"foobar"`.
-21
View File
@@ -1,21 +0,0 @@
#Using Treetop Grammars in Ruby
##Using the Command Line Compiler
You can `.treetop` files into Ruby source code with the `tt` command line script. `tt` takes an list of files with a `.treetop` extension and compiles them into `.rb` files of the same name. You can then `require` these files like any other Ruby script. Alternately, you can supply just one `.treetop` file and a `-o` flag to name specify the name of the output file. Improvements to this compilation script are welcome.
tt foo.treetop bar.treetop
tt foo.treetop -o foogrammar.rb
##Loading A Grammar Directly
The Polyglot gem makes it possible to load `.treetop` or `.tt` files directly with `require`. This will invoke `Treetop.load`, which automatically compiles the grammar to Ruby and then evaluates the Ruby source. If you are getting errors in methods you define on the syntax tree, try using the command line compiler for better stack trace feedback. A better solution to this issue is in the works.
##Instantiating and Using Parsers
If a grammar by the name of `Foo` is defined, the compiled Ruby source will define a `FooParser` class. To parse input, create an instance and call its `parse` method with a string. The parser will return the syntax tree of the match or `nil` if there is a failure.
Treetop.load "arithmetic"
parser = ArithmeticParser.new
if parser.parse('1+1')
puts 'success'
else
puts 'failure'
end
@@ -1,551 +0,0 @@
module Arithmetic
include Treetop::Runtime
def root
@root || :expression
end
def _nt_expression
start_index = index
cached = node_cache[:expression][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_comparative
if r1.success?
r0 = r1
else
r2 = _nt_additive
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:expression][start_index] = r0
return r0
end
module Comparative0
def operand_1
elements[0]
end
def space
elements[1]
end
def operator
elements[2]
end
def space
elements[3]
end
def operand_2
elements[4]
end
end
def _nt_comparative
start_index = index
cached = node_cache[:comparative][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = _nt_additive
s0 << r1
if r1.success?
r2 = _nt_space
s0 << r2
if r2.success?
r3 = _nt_equality_op
s0 << r3
if r3.success?
r4 = _nt_space
s0 << r4
if r4.success?
r5 = _nt_additive
s0 << r5
end
end
end
end
if s0.last.success?
r0 = (BinaryOperation).new(input, i0...index, s0)
r0.extend(Comparative0)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:comparative][start_index] = r0
return r0
end
module EqualityOp0
def apply(a, b)
a == b
end
end
def _nt_equality_op
start_index = index
cached = node_cache[:equality_op][index]
if cached
@index = cached.interval.end
return cached
end
r0 = parse_terminal('==', SyntaxNode, EqualityOp0)
node_cache[:equality_op][start_index] = r0
return r0
end
module Additive0
def operand_1
elements[0]
end
def space
elements[1]
end
def operator
elements[2]
end
def space
elements[3]
end
def operand_2
elements[4]
end
end
def _nt_additive
start_index = index
cached = node_cache[:additive][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
i1, s1 = index, []
r2 = _nt_multitive
s1 << r2
if r2.success?
r3 = _nt_space
s1 << r3
if r3.success?
r4 = _nt_additive_op
s1 << r4
if r4.success?
r5 = _nt_space
s1 << r5
if r5.success?
r6 = _nt_additive
s1 << r6
end
end
end
end
if s1.last.success?
r1 = (BinaryOperation).new(input, i1...index, s1)
r1.extend(Additive0)
else
self.index = i1
r1 = ParseFailure.new(input, i1)
end
if r1.success?
r0 = r1
else
r7 = _nt_multitive
if r7.success?
r0 = r7
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:additive][start_index] = r0
return r0
end
module AdditiveOp0
def apply(a, b)
a + b
end
end
module AdditiveOp1
def apply(a, b)
a - b
end
end
def _nt_additive_op
start_index = index
cached = node_cache[:additive_op][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = parse_terminal('+', SyntaxNode, AdditiveOp0)
if r1.success?
r0 = r1
else
r2 = parse_terminal('-', SyntaxNode, AdditiveOp1)
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:additive_op][start_index] = r0
return r0
end
module Multitive0
def operand_1
elements[0]
end
def space
elements[1]
end
def operator
elements[2]
end
def space
elements[3]
end
def operand_2
elements[4]
end
end
def _nt_multitive
start_index = index
cached = node_cache[:multitive][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
i1, s1 = index, []
r2 = _nt_primary
s1 << r2
if r2.success?
r3 = _nt_space
s1 << r3
if r3.success?
r4 = _nt_multitive_op
s1 << r4
if r4.success?
r5 = _nt_space
s1 << r5
if r5.success?
r6 = _nt_multitive
s1 << r6
end
end
end
end
if s1.last.success?
r1 = (BinaryOperation).new(input, i1...index, s1)
r1.extend(Multitive0)
else
self.index = i1
r1 = ParseFailure.new(input, i1)
end
if r1.success?
r0 = r1
else
r7 = _nt_primary
if r7.success?
r0 = r7
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:multitive][start_index] = r0
return r0
end
module MultitiveOp0
def apply(a, b)
a * b
end
end
module MultitiveOp1
def apply(a, b)
a / b
end
end
def _nt_multitive_op
start_index = index
cached = node_cache[:multitive_op][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = parse_terminal('*', SyntaxNode, MultitiveOp0)
if r1.success?
r0 = r1
else
r2 = parse_terminal('/', SyntaxNode, MultitiveOp1)
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:multitive_op][start_index] = r0
return r0
end
module Primary0
def space
elements[1]
end
def expression
elements[2]
end
def space
elements[3]
end
end
module Primary1
def eval(env={})
expression.eval(env)
end
end
def _nt_primary
start_index = index
cached = node_cache[:primary][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_variable
if r1.success?
r0 = r1
else
r2 = _nt_number
if r2.success?
r0 = r2
else
i3, s3 = index, []
r4 = parse_terminal('(', SyntaxNode)
s3 << r4
if r4.success?
r5 = _nt_space
s3 << r5
if r5.success?
r6 = _nt_expression
s3 << r6
if r6.success?
r7 = _nt_space
s3 << r7
if r7.success?
r8 = parse_terminal(')', SyntaxNode)
s3 << r8
end
end
end
end
if s3.last.success?
r3 = (SyntaxNode).new(input, i3...index, s3)
r3.extend(Primary0)
r3.extend(Primary1)
else
self.index = i3
r3 = ParseFailure.new(input, i3)
end
if r3.success?
r0 = r3
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
end
node_cache[:primary][start_index] = r0
return r0
end
module Variable0
def eval(env={})
env[name]
end
def name
text_value
end
end
def _nt_variable
start_index = index
cached = node_cache[:variable][index]
if cached
@index = cached.interval.end
return cached
end
s0, i0 = [], index
loop do
r1 = parse_char_class(/[a-z]/, 'a-z', SyntaxNode)
if r1.success?
s0 << r1
else
break
end
end
if s0.empty?
self.index = i0
r0 = ParseFailure.new(input, i0)
else
r0 = SyntaxNode.new(input, i0...index, s0)
r0.extend(Variable0)
end
node_cache[:variable][start_index] = r0
return r0
end
module Number0
end
module Number1
def eval(env={})
text_value.to_i
end
end
def _nt_number
start_index = index
cached = node_cache[:number][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
i1, s1 = index, []
r2 = parse_char_class(/[1-9]/, '1-9', SyntaxNode)
s1 << r2
if r2.success?
s3, i3 = [], index
loop do
r4 = parse_char_class(/[0-9]/, '0-9', SyntaxNode)
if r4.success?
s3 << r4
else
break
end
end
r3 = SyntaxNode.new(input, i3...index, s3)
s1 << r3
end
if s1.last.success?
r1 = (SyntaxNode).new(input, i1...index, s1)
r1.extend(Number0)
else
self.index = i1
r1 = ParseFailure.new(input, i1)
end
if r1.success?
r0 = r1
r0.extend(Number1)
else
r5 = parse_terminal('0', SyntaxNode)
if r5.success?
r0 = r5
r0.extend(Number1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:number][start_index] = r0
return r0
end
def _nt_space
start_index = index
cached = node_cache[:space][index]
if cached
@index = cached.interval.end
return cached
end
s0, i0 = [], index
loop do
r1 = parse_terminal(' ', SyntaxNode)
if r1.success?
s0 << r1
else
break
end
end
r0 = SyntaxNode.new(input, i0...index, s0)
node_cache[:space][start_index] = r0
return r0
end
end
class ArithmeticParser < Treetop::Runtime::CompiledParser
include Arithmetic
end
@@ -1,97 +0,0 @@
grammar Arithmetic
rule expression
comparative / additive
end
rule comparative
operand_1:additive space operator:equality_op space operand_2:additive <BinaryOperation>
end
rule equality_op
'==' {
def apply(a, b)
a == b
end
}
end
rule additive
operand_1:multitive
space operator:additive_op space
operand_2:additive <BinaryOperation>
/
multitive
end
rule additive_op
'+' {
def apply(a, b)
a + b
end
}
/
'-' {
def apply(a, b)
a - b
end
}
end
rule multitive
operand_1:primary
space operator:multitive_op space
operand_2:multitive <BinaryOperation>
/
primary
end
rule multitive_op
'*' {
def apply(a, b)
a * b
end
}
/
'/' {
def apply(a, b)
a / b
end
}
end
rule primary
variable
/
number
/
'(' space expression space ')' {
def eval(env={})
expression.eval(env)
end
}
end
rule variable
[a-z]+ {
def eval(env={})
env[name]
end
def name
text_value
end
}
end
rule number
([1-9] [0-9]* / '0') {
def eval(env={})
text_value.to_i
end
}
end
rule space
' '*
end
end
@@ -1,7 +0,0 @@
module Arithmetic
class BinaryOperation < Treetop::Runtime::SyntaxNode
def eval(env={})
operator.apply(operand_1.eval(env), operand_2.eval(env))
end
end
end
@@ -1,54 +0,0 @@
dir = File.dirname(__FILE__)
require File.expand_path("#{dir}/test_helper")
require File.expand_path("#{dir}/arithmetic_node_classes")
Treetop.load File.expand_path("#{dir}/arithmetic")
class ArithmeticParserTest < Test::Unit::TestCase
include ParserTestHelper
def setup
@parser = ArithmeticParser.new
end
def test_number
assert_equal 0, parse('0').eval
assert_equal 1, parse('1').eval
assert_equal 123, parse('123').eval
end
def test_variable
assert_equal 0, parse('x').eval('x' => 0)
assert_equal 3, parse('x').eval('x' => 3)
assert_equal 10, parse('y').eval('y' => 10)
end
def test_addition
assert_equal 10, parse('x + 5').eval('x' => 5)
end
def test_subtraction
assert_equal 0, parse('x - 5').eval('x' => 5)
end
def test_multiplication
assert_equal 6, parse('x * 2').eval('x' => 3)
end
def test_division
assert_equal 3, parse('x / 2').eval('x' => 6)
end
def test_order_of_operations
assert_equal 11, parse('1 + 2 * 3 + 4').eval
end
def test_parentheses
assert_equal 25, parse('(5 + x) * (10 - y)').eval('x' => 0, 'y' => 5)
end
def test_equality
assert parse('4 == 4').eval
assert !parse('4 == 3').eval
end
end
@@ -1,718 +0,0 @@
module LambdaCalculus
include Treetop::Runtime
def root
@root || :program
end
include Arithmetic
module Program0
def space
elements[1]
end
def expression
elements[2]
end
end
module Program1
def expression
elements[0]
end
def more_expressions
elements[1]
end
end
module Program2
def eval(env={})
env = env.clone
last_eval = nil
expressions.each do |exp|
last_eval = exp.eval(env)
end
last_eval
end
def expressions
[expression] + more_expressions.elements.map {|elt| elt.expression}
end
end
def _nt_program
start_index = index
cached = node_cache[:program][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = _nt_expression
s0 << r1
if r1.success?
s2, i2 = [], index
loop do
i3, s3 = index, []
r4 = parse_terminal(';', SyntaxNode)
s3 << r4
if r4.success?
r5 = _nt_space
s3 << r5
if r5.success?
r6 = _nt_expression
s3 << r6
end
end
if s3.last.success?
r3 = (SyntaxNode).new(input, i3...index, s3)
r3.extend(Program0)
else
self.index = i3
r3 = ParseFailure.new(input, i3)
end
if r3.success?
s2 << r3
else
break
end
end
r2 = SyntaxNode.new(input, i2...index, s2)
s0 << r2
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Program1)
r0.extend(Program2)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:program][start_index] = r0
return r0
end
def _nt_expression
start_index = index
cached = node_cache[:expression][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_definition
if r1.success?
r0 = r1
else
r2 = _nt_conditional
if r2.success?
r0 = r2
else
r3 = _nt_application
if r3.success?
r0 = r3
else
r4 = _nt_function
if r4.success?
r0 = r4
else
r5 = super
if r5.success?
r0 = r5
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
end
end
end
node_cache[:expression][start_index] = r0
return r0
end
module Definition0
def space
elements[1]
end
def variable
elements[2]
end
def space
elements[3]
end
def expression
elements[4]
end
end
module Definition1
def eval(env)
env[variable.name] = expression.eval(env)
end
end
def _nt_definition
start_index = index
cached = node_cache[:definition][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = parse_terminal('def', SyntaxNode)
s0 << r1
if r1.success?
r2 = _nt_space
s0 << r2
if r2.success?
r3 = _nt_variable
s0 << r3
if r3.success?
r4 = _nt_space
s0 << r4
if r4.success?
r5 = _nt_expression
s0 << r5
end
end
end
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Definition0)
r0.extend(Definition1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:definition][start_index] = r0
return r0
end
module Conditional0
def space
elements[1]
end
def space
elements[3]
end
def condition
elements[4]
end
def space
elements[5]
end
def space
elements[7]
end
def true_case
elements[8]
end
def space
elements[9]
end
def space
elements[11]
end
def false_case
elements[12]
end
end
module Conditional1
def eval(env)
if condition.eval(env)
true_case.eval(env)
else
false_case.eval(env)
end
end
end
def _nt_conditional
start_index = index
cached = node_cache[:conditional][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = parse_terminal('if', SyntaxNode)
s0 << r1
if r1.success?
r2 = _nt_space
s0 << r2
if r2.success?
r3 = parse_terminal('(', SyntaxNode)
s0 << r3
if r3.success?
r4 = _nt_space
s0 << r4
if r4.success?
r5 = _nt_expression
s0 << r5
if r5.success?
r6 = _nt_space
s0 << r6
if r6.success?
r7 = parse_terminal(')', SyntaxNode)
s0 << r7
if r7.success?
r8 = _nt_space
s0 << r8
if r8.success?
r9 = _nt_expression
s0 << r9
if r9.success?
r10 = _nt_space
s0 << r10
if r10.success?
r11 = parse_terminal('else', SyntaxNode)
s0 << r11
if r11.success?
r12 = _nt_space
s0 << r12
if r12.success?
r13 = _nt_expression
s0 << r13
end
end
end
end
end
end
end
end
end
end
end
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Conditional0)
r0.extend(Conditional1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:conditional][start_index] = r0
return r0
end
def _nt_primary
start_index = index
cached = node_cache[:primary][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_application
if r1.success?
r0 = r1
else
r2 = super
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:primary][start_index] = r0
return r0
end
module Application0
def operator
elements[0]
end
def space
elements[1]
end
def expression
elements[2]
end
end
module Application1
def eval(env={})
left_associative_apply(operator.eval(env), env)
end
def left_associative_apply(operator, env)
if expression.instance_of?(Application)
expression.left_associative_apply(operator.apply(expression.operator.eval(env)), env)
else
operator.apply(expression.eval(env))
end
end
def to_s(env={})
operator.to_s(env) + ' ' + expression.to_s(env)
end
end
def _nt_application
start_index = index
cached = node_cache[:application][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = _nt_operator
s0 << r1
if r1.success?
r2 = _nt_space
s0 << r2
if r2.success?
r3 = _nt_expression
s0 << r3
end
end
if s0.last.success?
r0 = (Application).new(input, i0...index, s0)
r0.extend(Application0)
r0.extend(Application1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:application][start_index] = r0
return r0
end
def _nt_operator
start_index = index
cached = node_cache[:operator][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_function
if r1.success?
r0 = r1
else
r2 = _nt_variable
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:operator][start_index] = r0
return r0
end
def _nt_non_application
start_index = index
cached = node_cache[:non_application][index]
if cached
@index = cached.interval.end
return cached
end
i0 = index
r1 = _nt_function
if r1.success?
r0 = r1
else
r2 = _nt_variable
if r2.success?
r0 = r2
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
end
node_cache[:non_application][start_index] = r0
return r0
end
module Function0
def param
elements[1]
end
def body
elements[3]
end
end
module Function1
class Closure
attr_reader :env, :function
def initialize(function, env)
@function = function
@env = env
end
def apply(arg)
function.body.eval(function.param.bind(arg, env))
end
def to_s(other_env={})
"\\#{function.param.to_s}(#{function.body.to_s(other_env.merge(env))})"
end
end
def eval(env={})
Closure.new(self, env)
end
def to_s(env={})
eval(env).to_s
end
end
def _nt_function
start_index = index
cached = node_cache[:function][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
r1 = parse_terminal('\\', SyntaxNode)
s0 << r1
if r1.success?
r2 = _nt_variable
s0 << r2
if r2.success?
r3 = parse_terminal('(', SyntaxNode)
s0 << r3
if r3.success?
r4 = _nt_expression
s0 << r4
if r4.success?
r5 = parse_terminal(')', SyntaxNode)
s0 << r5
end
end
end
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Function0)
r0.extend(Function1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:function][start_index] = r0
return r0
end
module Variable0
def bind(value, env)
env.merge(name => value)
end
def to_s(env={})
env.has_key?(name) ? env[name].to_s : name
end
end
module Variable1
end
def _nt_variable
start_index = index
cached = node_cache[:variable][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
i1 = index
r2 = _nt_keyword
if r2.success?
r1 = ParseFailure.new(input, i1)
else
self.index = i1
r1 = SyntaxNode.new(input, index...index)
end
s0 << r1
if r1.success?
r3 = super
r3.extend(Variable0)
s0 << r3
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Variable1)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:variable][start_index] = r0
return r0
end
module Keyword0
end
def _nt_keyword
start_index = index
cached = node_cache[:keyword][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
i1 = index
r2 = parse_terminal('if', SyntaxNode)
if r2.success?
r1 = r2
else
r3 = parse_terminal('else', SyntaxNode)
if r3.success?
r1 = r3
else
self.index = i1
r1 = ParseFailure.new(input, i1)
end
end
s0 << r1
if r1.success?
i4 = index
r5 = _nt_non_space_char
if r5.success?
r4 = ParseFailure.new(input, i4)
else
self.index = i4
r4 = SyntaxNode.new(input, index...index)
end
s0 << r4
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(Keyword0)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:keyword][start_index] = r0
return r0
end
module NonSpaceChar0
end
def _nt_non_space_char
start_index = index
cached = node_cache[:non_space_char][index]
if cached
@index = cached.interval.end
return cached
end
i0, s0 = index, []
i1 = index
r2 = parse_char_class(/[ \n]/, ' \n', SyntaxNode)
if r2.success?
r1 = ParseFailure.new(input, i1)
else
self.index = i1
r1 = SyntaxNode.new(input, index...index)
end
s0 << r1
if r1.success?
r3 = parse_anything(SyntaxNode)
s0 << r3
end
if s0.last.success?
r0 = (SyntaxNode).new(input, i0...index, s0)
r0.extend(NonSpaceChar0)
else
self.index = i0
r0 = ParseFailure.new(input, i0)
end
node_cache[:non_space_char][start_index] = r0
return r0
end
def _nt_space
start_index = index
cached = node_cache[:space][index]
if cached
@index = cached.interval.end
return cached
end
s0, i0 = [], index
loop do
r1 = parse_char_class(/[ \n]/, ' \n', SyntaxNode)
if r1.success?
s0 << r1
else
break
end
end
r0 = SyntaxNode.new(input, i0...index, s0)
node_cache[:space][start_index] = r0
return r0
end
end
class LambdaCalculusParser < Treetop::Runtime::CompiledParser
include LambdaCalculus
end
@@ -1,132 +0,0 @@
grammar LambdaCalculus
include Arithmetic
rule program
expression more_expressions:(';' space expression)* {
def eval(env={})
env = env.clone
last_eval = nil
expressions.each do |exp|
last_eval = exp.eval(env)
end
last_eval
end
def expressions
[expression] + more_expressions.elements.map {|elt| elt.expression}
end
}
end
rule expression
definition / conditional / application / function / super
end
rule definition
'def' space variable space expression {
def eval(env)
env[variable.name] = expression.eval(env)
end
}
end
rule conditional
'if' space '(' space condition:expression space ')' space
true_case:expression space 'else' space false_case:expression {
def eval(env)
if condition.eval(env)
true_case.eval(env)
else
false_case.eval(env)
end
end
}
end
rule primary
application / super
end
rule application
operator space expression <Application> {
def eval(env={})
left_associative_apply(operator.eval(env), env)
end
def left_associative_apply(operator, env)
if expression.instance_of?(Application)
expression.left_associative_apply(operator.apply(expression.operator.eval(env)), env)
else
operator.apply(expression.eval(env))
end
end
def to_s(env={})
operator.to_s(env) + ' ' + expression.to_s(env)
end
}
end
rule operator
function / variable
end
rule non_application
function / variable
end
rule function
'\\' param:variable '(' body:expression ')' {
class Closure
attr_reader :env, :function
def initialize(function, env)
@function = function
@env = env
end
def apply(arg)
function.body.eval(function.param.bind(arg, env))
end
def to_s(other_env={})
"\\#{function.param.to_s}(#{function.body.to_s(other_env.merge(env))})"
end
end
def eval(env={})
Closure.new(self, env)
end
def to_s(env={})
eval(env).to_s
end
}
end
rule variable
!keyword (
super {
def bind(value, env)
env.merge(name => value)
end
def to_s(env={})
env.has_key?(name) ? env[name].to_s : name
end
}
)
end
rule keyword
('if' / 'else') !non_space_char
end
rule non_space_char
![ \n] .
end
rule space
[ \n]*
end
end
@@ -1,5 +0,0 @@
module LambdaCalculus
class Application < Treetop::Runtime::SyntaxNode
end
end
@@ -1,89 +0,0 @@
dir = File.dirname(__FILE__)
require File.expand_path("#{dir}/test_helper")
require File.expand_path("#{dir}/arithmetic_node_classes")
require File.expand_path("#{dir}/lambda_calculus_node_classes")
Treetop.load File.expand_path("#{dir}/arithmetic")
Treetop.load File.expand_path("#{dir}/lambda_calculus")
class Treetop::Runtime::SyntaxNode
def method_missing(method, *args)
raise "Node representing #{text_value} does not respond to #{method}"
end
end
class LambdaCalculusParserTest < Test::Unit::TestCase
include ParserTestHelper
def setup
@parser = LambdaCalculusParser.new
end
def test_free_variable
assert_equal 'x', parse('x').eval.to_s
end
def test_variable_binding
variable = parse('x').eval
env = variable.bind(1, {})
assert_equal 1, env['x']
end
def test_bound_variable_evaluation
assert_equal 1, parse('x').eval({'x' => 1})
end
def test_identity_function
assert_equal '\x(x)', parse('\x(x)').eval.to_s
end
def test_function_returning_constant_function
assert_equal '\x(\y(x))', parse('\x(\y(x))').eval.to_s
end
def test_identity_function_application
assert_equal 1, parse('\x(x) 1').eval
assert_equal '\y(y)', parse('\x(x) \y(y)').eval.to_s
end
def test_constant_function_construction
assert_equal '\y(1)', parse('\x(\y(x)) 1').eval.to_s
end
def test_multiple_argument_application_is_left_associative
assert_equal '\b(b)', parse('\x(\y(x y)) \a(a) \b(b)').eval.to_s
end
def test_parentheses_override_application_order
assert_equal '\y(\b(b) y)', parse('\x(\y(x y)) (\a(a) \b(b))').eval.to_s
end
def test_arithmetic_in_function_body
assert_equal 10, parse('\x(x + 5) 5').eval
end
def test_addition_of_function_results
assert_equal 20, parse('\x(x + 5) 5 + \x(15 - x) 5').eval
end
def test_conditional
result = parse('if (x) 1 else 2')
assert_equal 1, result.eval({'x' => true})
assert_equal 2, result.eval({'x' => false})
end
def test_keyword
assert @parser.parse('if').failure?
assert @parser.parse('else').failure?
assert parse('elsee').success?
assert parse('iff').success?
end
def test_program
result = parse('def fact \x(if (x == 0)
1
else
x * fact (x - 1));
fact(5)').eval
assert_equal 5 * 4 * 3 * 2, result
end
end
@@ -1,18 +0,0 @@
require 'test/unit'
require 'rubygems'
require 'treetop'
module ParserTestHelper
def assert_evals_to_self(input)
assert_evals_to(input, input)
end
def parse(input)
result = @parser.parse(input)
unless result
puts @parser.terminal_failures.join("\n")
end
assert !result.nil?
result
end
end
-11
View File
@@ -1,11 +0,0 @@
require 'rubygems'
dir = File.dirname(__FILE__)
TREETOP_ROOT = File.join(dir, 'treetop')
require File.join(TREETOP_ROOT, "ruby_extensions")
require File.join(TREETOP_ROOT, "runtime")
require File.join(TREETOP_ROOT, "compiler")
require 'polyglot'
Polyglot.register(["treetop", "tt"], Treetop)
@@ -1,45 +0,0 @@
# This file's job is to load a Treetop::Compiler::Metagrammar and Treetop::Compiler::MetagrammarParser
# into the environment by compiling the current metagrammar.treetop using a trusted version of Treetop.
require 'rubygems'
dir = File.dirname(__FILE__)
TREETOP_VERSION_REQUIRED_TO_BOOTSTRAP = '>= 1.1.5'
# Loading trusted version of Treetop to compile the compiler
gem_spec = Gem.source_index.find_name('treetop', TREETOP_VERSION_REQUIRED_TO_BOOTSTRAP).last
raise "Install a Treetop Gem version #{TREETOP_VERSION_REQUIRED_TO_BOOTSTRAP} to bootstrap." unless gem_spec
trusted_treetop_path = gem_spec.full_gem_path
require File.join(trusted_treetop_path, 'lib', 'treetop')
# Relocating trusted version of Treetop to Trusted::Treetop
Trusted = Module.new
Trusted::Treetop = Treetop
Object.send(:remove_const, :Treetop)
Object.send(:remove_const, :TREETOP_ROOT)
# Requiring version of Treetop that is under test
$exclude_metagrammar = true
require File.expand_path(File.join(dir, '..', 'treetop'))
# Compile and evaluate freshly generated metagrammar source
METAGRAMMAR_PATH = File.join(TREETOP_ROOT, 'compiler', 'metagrammar.treetop')
compiled_metagrammar_source = Trusted::Treetop::Compiler::GrammarCompiler.new.ruby_source(METAGRAMMAR_PATH)
Object.class_eval(compiled_metagrammar_source)
# The compiler under test was compiled with the trusted grammar and therefore depends on its runtime
# But the runtime in the global namespace is the new runtime. We therefore inject the trusted runtime
# into the compiler so its parser functions correctly. It will still not work for custom classes that
# explicitly subclass the wrong runtime. For now I am working around this by keeping 1 generation of
# backward compatibility in these cases.
# Treetop::Compiler::Metagrammar.module_eval do
# include Trusted::Treetop::Runtime
# end
#
# Treetop::Compiler.send(:remove_const, :MetagrammarParser)
# class Treetop::Compiler::MetagrammarParser < Trusted::Treetop::Runtime::CompiledParser
# include Treetop::Compiler::Metagrammar
# include Trusted::Treetop::Runtime
# end
$bootstrapped_gen_1_metagrammar = true
-6
View File
@@ -1,6 +0,0 @@
dir = File.dirname(__FILE__)
require File.join(dir, *%w[compiler lexical_address_space])
require File.join(dir, *%w[compiler ruby_builder])
require File.join(dir, *%w[compiler node_classes])
require File.join(dir, *%w[compiler metagrammar]) unless $exclude_metagrammar
require File.join(dir, *%w[compiler grammar_compiler])
@@ -1,40 +0,0 @@
module Treetop
module Compiler
class GrammarCompiler
def compile(source_path, target_path = source_path.gsub(/\.(treetop|tt)\Z/, '.rb'))
File.open(target_path, 'w') do |target_file|
target_file.write(ruby_source(source_path))
end
end
# compile a treetop file into ruby
def ruby_source(source_path)
ruby_source_from_string(File.read(source_path))
end
# compile a string containing treetop source into ruby
def ruby_source_from_string(s)
parser = MetagrammarParser.new
result = parser.parse(s)
unless result
raise RuntimeError.new(parser.failure_reason)
end
result.compile
end
end
end
# compile a treetop source file and load it
def self.load(path)
adjusted_path = path =~ /\.(treetop|tt)\Z/ ? path : path + '.treetop'
File.open(adjusted_path) do |source_file|
load_from_string(source_file.read)
end
end
# compile a treetop source string and load it
def self.load_from_string(s)
compiler = Treetop::Compiler::GrammarCompiler.new
Object.class_eval(compiler.ruby_source_from_string(s))
end
end
@@ -1,17 +0,0 @@
module Treetop
module Compiler
class LexicalAddressSpace
def initialize
reset_addresses
end
def next_address
@next_address += 1
end
def reset_addresses
@next_address = -1
end
end
end
end
File diff suppressed because it is too large Load Diff
@@ -1,404 +0,0 @@
module Treetop
module Compiler
grammar Metagrammar
rule treetop_file
prefix:space? module_or_grammar:(module_declaration / grammar) suffix:space? {
def compile
prefix.text_value + module_or_grammar.compile + suffix.text_value
end
}
end
rule module_declaration
prefix:('module' space [A-Z] alphanumeric_char* space) module_contents:(module_declaration / grammar) suffix:(space 'end') {
def compile
prefix.text_value + module_contents.compile + suffix.text_value
end
}
end
rule grammar
'grammar' space grammar_name space ('do' space)? declaration_sequence space? 'end' <Grammar>
end
rule grammar_name
([A-Z] alphanumeric_char*)
end
rule declaration_sequence
head:declaration tail:(space declaration)* <DeclarationSequence> {
def declarations
[head] + tail
end
def tail
super.elements.map { |elt| elt.declaration }
end
}
/
'' {
def compile(builder)
end
}
end
rule declaration
parsing_rule / include_declaration
end
rule include_declaration
'include' space [A-Z] (alphanumeric_char / '::')* {
def compile(builder)
builder << text_value
end
}
end
rule parsing_rule
'rule' space nonterminal space ('do' space)? parsing_expression space 'end' <ParsingRule>
end
rule parsing_expression
choice / sequence / primary
end
rule choice
head:alternative tail:(space? '/' space? alternative)+ <Choice> {
def alternatives
[head] + tail
end
def tail
super.elements.map {|elt| elt.alternative}
end
def inline_modules
(alternatives.map {|alt| alt.inline_modules }).flatten
end
}
end
rule sequence
head:labeled_sequence_primary tail:(space labeled_sequence_primary)+ node_class_declarations <Sequence> {
def sequence_elements
[head] + tail
end
def tail
super.elements.map {|elt| elt.labeled_sequence_primary }
end
def inline_modules
(sequence_elements.map {|elt| elt.inline_modules}).flatten +
[sequence_element_accessor_module] +
node_class_declarations.inline_modules
end
def inline_module_name
node_class_declarations.inline_module_name
end
}
end
rule alternative
sequence / primary
end
rule primary
prefix atomic {
def compile(address, builder, parent_expression=nil)
prefix.compile(address, builder, self)
end
def prefixed_expression
atomic
end
def inline_modules
atomic.inline_modules
end
def inline_module_name
nil
end
}
/
atomic suffix node_class_declarations {
def compile(address, builder, parent_expression=nil)
suffix.compile(address, builder, self)
end
def optional_expression
atomic
end
def node_class_name
node_class_declarations.node_class_name
end
def inline_modules
atomic.inline_modules + node_class_declarations.inline_modules
end
def inline_module_name
node_class_declarations.inline_module_name
end
}
/
atomic node_class_declarations {
def compile(address, builder, parent_expression=nil)
atomic.compile(address, builder, self)
end
def node_class_name
node_class_declarations.node_class_name
end
def inline_modules
atomic.inline_modules + node_class_declarations.inline_modules
end
def inline_module_name
node_class_declarations.inline_module_name
end
}
end
rule labeled_sequence_primary
label sequence_primary {
def compile(lexical_address, builder)
sequence_primary.compile(lexical_address, builder)
end
def inline_modules
sequence_primary.inline_modules
end
def label_name
if label.name
label.name
elsif sequence_primary.instance_of?(Nonterminal)
sequence_primary.text_value
else
nil
end
end
}
end
rule label
(alpha_char alphanumeric_char*) ':' {
def name
elements[0].text_value
end
}
/
'' {
def name
nil
end
}
end
rule sequence_primary
prefix atomic {
def compile(lexical_address, builder)
prefix.compile(lexical_address, builder, self)
end
def prefixed_expression
elements[1]
end
def inline_modules
atomic.inline_modules
end
def inline_module_name
nil
end
}
/
atomic suffix {
def compile(lexical_address, builder)
suffix.compile(lexical_address, builder, self)
end
def node_class_name
nil
end
def inline_modules
atomic.inline_modules
end
def inline_module_name
nil
end
}
/
atomic
end
rule suffix
repetition_suffix / optional_suffix
end
rule optional_suffix
'?' <Optional>
end
rule node_class_declarations
node_class_expression trailing_inline_module {
def node_class_name
node_class_expression.node_class_name
end
def inline_modules
trailing_inline_module.inline_modules
end
def inline_module
trailing_inline_module.inline_module
end
def inline_module_name
inline_module.module_name if inline_module
end
}
end
rule repetition_suffix
'+' <OneOrMore> / '*' <ZeroOrMore>
end
rule prefix
'&' <AndPredicate> / '!' <NotPredicate> / '~' <TransientPrefix>
end
rule atomic
terminal
/
nonterminal
/
parenthesized_expression
end
rule parenthesized_expression
'(' space? parsing_expression space? ')' <ParenthesizedExpression> {
def inline_modules
parsing_expression.inline_modules
end
}
end
rule nonterminal
!keyword_inside_grammar (alpha_char alphanumeric_char*) <Nonterminal>
end
rule terminal
quoted_string / character_class / anything_symbol
end
rule quoted_string
(single_quoted_string / double_quoted_string) {
def string
super.text_value
end
}
end
rule double_quoted_string
'"' string:(!'"' ("\\\\" / '\"' / .))* '"' <Terminal>
end
rule single_quoted_string
"'" string:(!"'" ("\\\\" / "\\'" / .))* "'" <Terminal>
end
rule character_class
'[' characters:(!']' ('\\' . /!'\\' .))+ ']' <CharacterClass> {
def characters
super.text_value
end
}
end
rule anything_symbol
'.' <AnythingSymbol>
end
rule node_class_expression
space '<' (!'>' .)+ '>' {
def node_class_name
elements[2].text_value
end
}
/
'' {
def node_class_name
nil
end
}
end
rule trailing_inline_module
space inline_module {
def inline_modules
[inline_module]
end
def inline_module_name
inline_module.module_name
end
}
/
'' {
def inline_modules
[]
end
def inline_module
nil
end
def inline_module_name
nil
end
}
end
rule inline_module
'{' (inline_module / ![{}] .)* '}' <InlineModule>
end
rule keyword_inside_grammar
('rule' / 'end') !non_space_char
end
rule non_space_char
!space .
end
rule alpha_char
[A-Za-z_]
end
rule alphanumeric_char
alpha_char / [0-9]
end
rule space
(white / comment_to_eol)+
end
rule comment_to_eol
'#' (!"\n" .)*
end
rule white
[ \t\n\r]
end
end
end
end
@@ -1,19 +0,0 @@
dir = File.dirname(__FILE__)
require File.join(dir, *%w[node_classes parsing_expression])
require File.join(dir, *%w[node_classes atomic_expression])
require File.join(dir, *%w[node_classes inline_module])
require File.join(dir, *%w[node_classes treetop_file])
require File.join(dir, *%w[node_classes grammar])
require File.join(dir, *%w[node_classes declaration_sequence])
require File.join(dir, *%w[node_classes parsing_rule])
require File.join(dir, *%w[node_classes parenthesized_expression])
require File.join(dir, *%w[node_classes nonterminal])
require File.join(dir, *%w[node_classes terminal])
require File.join(dir, *%w[node_classes anything_symbol])
require File.join(dir, *%w[node_classes character_class])
require File.join(dir, *%w[node_classes sequence])
require File.join(dir, *%w[node_classes choice])
require File.join(dir, *%w[node_classes repetition])
require File.join(dir, *%w[node_classes optional])
require File.join(dir, *%w[node_classes predicate])
require File.join(dir, *%w[node_classes transient_prefix])
@@ -1,18 +0,0 @@
module Treetop
module Compiler
class AnythingSymbol < AtomicExpression
def compile(address, builder, parent_expression = nil)
super
builder.if__ "index < input_length" do
assign_result "instantiate_node(#{node_class_name},input, index...(index + 1))"
extend_result_with_inline_module
builder << "@index += 1"
end
builder.else_ do
builder << 'terminal_parse_failure("any character")'
assign_result 'nil'
end
end
end
end
end
@@ -1,14 +0,0 @@
module Treetop
module Compiler
class AtomicExpression < ParsingExpression
def inline_modules
[]
end
def single_quote(string)
# Double any backslashes, then backslash any single-quotes:
"'#{string.gsub(/\\/) { '\\\\' }.gsub(/'/) { "\\'"}}'"
end
end
end
end
@@ -1,19 +0,0 @@
module Treetop
module Compiler
class CharacterClass < AtomicExpression
def compile(address, builder, parent_expression = nil)
super
builder.if__ "input.index(Regexp.new(#{single_quote(text_value)}), index) == index" do
assign_result "instantiate_node(#{node_class_name},input, index...(index + 1))"
extend_result_with_inline_module
builder << "@index += 1"
end
builder.else_ do
"terminal_parse_failure(#{single_quote(characters)})"
assign_result 'nil'
end
end
end
end
end
@@ -1,31 +0,0 @@
module Treetop
module Compiler
class Choice < ParsingExpression
def compile(address, builder, parent_expression = nil)
super
begin_comment(self)
use_vars :result, :start_index
compile_alternatives(alternatives)
end_comment(self)
end
def compile_alternatives(alternatives)
obtain_new_subexpression_address
alternatives.first.compile(subexpression_address, builder)
builder.if__ subexpression_success? do
assign_result subexpression_result_var
extend_result_with_declared_module
extend_result_with_inline_module
end
builder.else_ do
if alternatives.size == 1
reset_index
assign_failure start_index_var
else
compile_alternatives(alternatives[1..-1])
end
end
end
end
end
end
@@ -1,24 +0,0 @@
module Treetop
module Compiler
class DeclarationSequence < Runtime::SyntaxNode
def compile(builder)
unless rules.empty?
builder.method_declaration("root") do
builder << "@root || :#{rules.first.name}"
end
builder.newline
end
declarations.each do |declaration|
declaration.compile(builder)
builder.newline
end
end
def rules
declarations.select { |declaration| declaration.instance_of?(ParsingRule) }
end
end
end
end
@@ -1,28 +0,0 @@
module Treetop
module Compiler
class Grammar < Runtime::SyntaxNode
def compile
builder = RubyBuilder.new
builder.module_declaration "#{grammar_name.text_value}" do
builder.in(indent_level) # account for initial indentation of grammar declaration
builder << "include Treetop::Runtime"
builder.newline
declaration_sequence.compile(builder)
end
builder.newline
builder.class_declaration "#{parser_name} < Treetop::Runtime::CompiledParser" do
builder << "include #{grammar_name.text_value}"
end
end
def indent_level
input.column_of(interval.begin) - 1
end
def parser_name
grammar_name.text_value + 'Parser'
end
end
end
end
@@ -1,27 +0,0 @@
module Treetop
module Compiler
module InlineModuleMixin
attr_reader :module_name
def compile(index, builder, rule)
@module_name = "#{rule.name.treetop_camelize}#{index}"
end
end
class InlineModule < Runtime::SyntaxNode
include InlineModuleMixin
def compile(index, builder, rule)
super
builder.module_declaration(module_name) do
builder << ruby_code.gsub(/\A\n/, '').rstrip
end
end
def ruby_code
elements[1].text_value
end
end
end
end
@@ -1,13 +0,0 @@
module Treetop
module Compiler
class Nonterminal < AtomicExpression
def compile(address, builder, parent_expression = nil)
super
use_vars :result
assign_result text_value == 'super' ? 'super' : "_nt_#{text_value}"
extend_result_with_declared_module
extend_result_with_inline_module
end
end
end
end
@@ -1,19 +0,0 @@
module Treetop
module Compiler
class Optional < ParsingExpression
def compile(address, builder, parent_expression)
super
use_vars :result
obtain_new_subexpression_address
parent_expression.atomic.compile(subexpression_address, builder)
builder.if__ subexpression_success? do
assign_result subexpression_result_var
end
builder.else_ do
assign_result epsilon_node
end
end
end
end
end
@@ -1,9 +0,0 @@
module Treetop
module Compiler
class ParenthesizedExpression < ParsingExpression
def compile(address, builder, parent_expression = nil)
elements[2].compile(address, builder, parent_expression)
end
end
end
end
@@ -1,138 +0,0 @@
module Treetop
module Compiler
class ParsingExpression < Runtime::SyntaxNode
attr_reader :address, :builder, :subexpression_address, :var_symbols, :parent_expression
def compile(address, builder, parent_expression)
@address = address
@builder = builder
@parent_expression = parent_expression
end
def node_class_name
parent_expression && parent_expression.node_class_name || 'SyntaxNode'
end
def declared_module_name
parent_expression && parent_expression.node_class_name
end
def inline_module_name
parent_expression && parent_expression.inline_module_name
end
def optional_arg(arg)
if arg
", #{arg}"
else
''
end
end
def use_vars(*var_symbols)
@var_symbols = var_symbols
builder << var_initialization
end
def result_var
var(:result)
end
def accumulator_var
var(:accumulator)
end
def start_index_var
var(:start_index)
end
def subexpression_result_var
"r#{subexpression_address}"
end
def subexpression_success?
subexpression_result_var
end
def obtain_new_subexpression_address
@subexpression_address = builder.next_address
end
def accumulate_subexpression_result
builder.accumulate accumulator_var, subexpression_result_var
end
def assign_result(value_ruby)
builder.assign result_var, value_ruby
end
def extend_result(module_name)
builder.extend result_var, module_name
end
def extend_result_with_declared_module
extend_result declared_module_name if declared_module_name
end
def extend_result_with_inline_module
extend_result inline_module_name if inline_module_name
end
def reset_index
builder.assign 'self.index', start_index_var
end
def epsilon_node
"instantiate_node(SyntaxNode,input, index...index)"
end
def assign_failure(start_index_var)
assign_result("nil")
end
def var_initialization
left, right = [], []
var_symbols.each do |symbol|
if init_value(symbol)
left << var(symbol)
right << init_value(symbol)
end
end
if left.empty?
""
else
left.join(', ') + ' = ' + right.join(', ')
end
end
def var(var_symbol)
case var_symbol
when :result then "r#{address}"
when :accumulator then "s#{address}"
when :start_index then "i#{address}"
else raise "Unknown var symbol #{var_symbol}."
end
end
def init_value(var_symbol)
case var_symbol
when :accumulator then '[]'
when :start_index then 'index'
else nil
end
end
def begin_comment(expression)
#builder << "# begin #{on_one_line(expression)}"
end
def end_comment(expression)
#builder << "# end #{on_one_line(expression)}"
end
def on_one_line(expression)
expression.text_value.tr("\n", ' ')
end
end
end
end
@@ -1,55 +0,0 @@
module Treetop
module Compiler
class ParsingRule < Runtime::SyntaxNode
def compile(builder)
compile_inline_module_declarations(builder)
generate_method_definition(builder)
end
def compile_inline_module_declarations(builder)
parsing_expression.inline_modules.each_with_index do |inline_module, i|
inline_module.compile(i, builder, self)
builder.newline
end
end
def generate_method_definition(builder)
builder.reset_addresses
expression_address = builder.next_address
result_var = "r#{expression_address}"
builder.method_declaration(method_name) do
builder.assign 'start_index', 'index'
generate_cache_lookup(builder)
builder.newline
parsing_expression.compile(expression_address, builder)
builder.newline
generate_cache_storage(builder, result_var)
builder.newline
builder << "return #{result_var}"
end
end
def generate_cache_lookup(builder)
builder.if_ "node_cache[:#{name}].has_key?(index)" do
builder.assign 'cached', "node_cache[:#{name}][index]"
builder << '@index = cached.interval.end if cached'
builder << 'return cached'
end
end
def generate_cache_storage(builder, result_var)
builder.assign "node_cache[:#{name}][start_index]", result_var
end
def method_name
"_nt_#{name}"
end
def name
nonterminal.text_value
end
end
end
end
@@ -1,45 +0,0 @@
module Treetop
module Compiler
class Predicate < ParsingExpression
def compile(address, builder, parent_expression)
super
begin_comment(parent_expression)
use_vars :result, :start_index
obtain_new_subexpression_address
parent_expression.prefixed_expression.compile(subexpression_address, builder)
builder.if__(subexpression_success?) { when_success }
builder.else_ { when_failure }
end_comment(parent_expression)
end
def assign_failure
super(start_index_var)
end
def assign_success
reset_index
assign_result epsilon_node
end
end
class AndPredicate < Predicate
def when_success
assign_success
end
def when_failure
assign_failure
end
end
class NotPredicate < Predicate
def when_success
assign_failure
end
def when_failure
assign_success
end
end
end
end
@@ -1,55 +0,0 @@
module Treetop
module Compiler
class Repetition < ParsingExpression
def compile(address, builder, parent_expression)
super
repeated_expression = parent_expression.atomic
begin_comment(parent_expression)
use_vars :result, :accumulator, :start_index
builder.loop do
obtain_new_subexpression_address
repeated_expression.compile(subexpression_address, builder)
builder.if__ subexpression_success? do
accumulate_subexpression_result
end
builder.else_ do
builder.break
end
end
end
def inline_module_name
parent_expression.inline_module_name
end
def assign_and_extend_result
assign_result "instantiate_node(#{node_class_name},input, #{start_index_var}...index, #{accumulator_var})"
extend_result_with_inline_module
end
end
class ZeroOrMore < Repetition
def compile(address, builder, parent_expression)
super
assign_and_extend_result
end_comment(parent_expression)
end
end
class OneOrMore < Repetition
def compile(address, builder, parent_expression)
super
builder.if__ "#{accumulator_var}.empty?" do
reset_index
assign_failure start_index_var
end
builder.else_ do
assign_and_extend_result
end
end_comment(parent_expression)
end
end
end
end
@@ -1,68 +0,0 @@
module Treetop
module Compiler
class Sequence < ParsingExpression
def compile(address, builder, parent_expression = nil)
super
begin_comment(self)
use_vars :result, :start_index, :accumulator
compile_sequence_elements(sequence_elements)
builder.if__ "#{accumulator_var}.last" do
assign_result "instantiate_node(#{node_class_name},input, #{start_index_var}...index, #{accumulator_var})"
extend_result sequence_element_accessor_module_name if sequence_element_accessor_module_name
extend_result_with_inline_module
end
builder.else_ do
reset_index
assign_failure start_index_var
end
end_comment(self)
end
def node_class_name
node_class_declarations.node_class_name || 'SyntaxNode'
end
def compile_sequence_elements(elements)
obtain_new_subexpression_address
elements.first.compile(subexpression_address, builder)
accumulate_subexpression_result
if elements.size > 1
builder.if_ subexpression_success? do
compile_sequence_elements(elements[1..-1])
end
end
end
def sequence_element_accessor_module
@sequence_element_accessor_module ||= SequenceElementAccessorModule.new(sequence_elements)
end
def sequence_element_accessor_module_name
sequence_element_accessor_module.module_name
end
end
class SequenceElementAccessorModule
include InlineModuleMixin
attr_reader :sequence_elements
def initialize(sequence_elements)
@sequence_elements = sequence_elements
end
def compile(index, builder, rule)
super
builder.module_declaration(module_name) do
sequence_elements.each_with_index do |element, index|
if element.label_name
builder.method_declaration(element.label_name) do
builder << "elements[#{index}]"
end
builder.newline unless index == sequence_elements.size - 1
end
end
end
end
end
end
end
@@ -1,20 +0,0 @@
module Treetop
module Compiler
class Terminal < AtomicExpression
def compile(address, builder, parent_expression = nil)
super
string_length = eval(text_value).length
builder.if__ "input.index(#{text_value}, index) == index" do
assign_result "instantiate_node(#{node_class_name},input, index...(index + #{string_length}))"
extend_result_with_inline_module
builder << "@index += #{string_length}"
end
builder.else_ do
builder << "terminal_parse_failure(#{text_value})"
assign_result 'nil'
end
end
end
end
end
@@ -1,9 +0,0 @@
module Treetop
module Compiler
class TransientPrefix < ParsingExpression
def compile(address, builder, parent_expression)
parent_expression.prefixed_expression.compile(address, builder)
end
end
end
end
@@ -1,9 +0,0 @@
module Treetop
module Compiler
class TreetopFile < Runtime::SyntaxNode
def compile
(elements.map {|elt| elt.compile}).join
end
end
end
end
@@ -1,113 +0,0 @@
module Treetop
module Compiler
class RubyBuilder
attr_reader :level, :address_space, :ruby
def initialize
@level = 0
@address_space = LexicalAddressSpace.new
@ruby = ""
end
def <<(ruby_line)
return if ruby_line.blank?
ruby << ruby_line.tabto(level) << "\n"
end
def newline
ruby << "\n"
end
def indented(depth = 2)
self.in(depth)
yield
self.out(depth)
end
def class_declaration(name, &block)
self << "class #{name}"
indented(&block)
self << "end"
end
def module_declaration(name, &block)
self << "module #{name}"
indented(&block)
self << "end"
end
def method_declaration(name, &block)
self << "def #{name}"
indented(&block)
self << "end"
end
def assign(left, right)
if left.instance_of? Array
self << "#{left.join(', ')} = #{right.join(', ')}"
else
self << "#{left} = #{right}"
end
end
def extend(var, module_name)
self << "#{var}.extend(#{module_name})"
end
def accumulate(left, right)
self << "#{left} << #{right}"
end
def if__(condition, &block)
self << "if #{condition}"
indented(&block)
end
def if_(condition, &block)
if__(condition, &block)
self << 'end'
end
def else_(&block)
self << 'else'
indented(&block)
self << 'end'
end
def loop(&block)
self << 'loop do'
indented(&block)
self << 'end'
end
def break
self << 'break'
end
def in(depth = 2)
@level += depth
self
end
def out(depth = 2)
@level -= depth
self
end
def next_address
address_space.next_address
end
def reset_addresses
address_space.reset_addresses
end
protected
def indent
" " * level
end
end
end
end
@@ -1,2 +0,0 @@
dir = File.dirname(__FILE__)
require "#{dir}/ruby_extensions/string"
@@ -1,42 +0,0 @@
class String
def column_of(index)
return 1 if index == 0
newline_index = rindex("\n", index - 1)
if newline_index
index - newline_index
else
index + 1
end
end
def line_of(index)
self[0...index].count("\n") + 1
end
unless method_defined?(:blank?)
def blank?
self == ""
end
end
# The following methods are lifted from Facets 2.0.2
def tabto(n)
if self =~ /^( *)\S/
indent(n - $1.length)
else
self
end
end
def indent(n)
if n >= 0
gsub(/^/, ' ' * n)
else
gsub(/^ {0,#{-n}}/, "")
end
end
def treetop_camelize
to_s.gsub(/\/(.?)/){ "::" + $1.upcase }.gsub(/(^|_)(.)/){ $2.upcase }
end
end
-5
View File
@@ -1,5 +0,0 @@
dir = File.dirname(__FILE__)
require "#{dir}/runtime/compiled_parser"
require "#{dir}/runtime/syntax_node"
require "#{dir}/runtime/terminal_parse_failure"
require "#{dir}/runtime/interval_skip_list"
@@ -1,95 +0,0 @@
module Treetop
module Runtime
class CompiledParser
include Treetop::Runtime
attr_reader :input, :index, :terminal_failures, :max_terminal_failure_index
attr_writer :root
attr_accessor :consume_all_input
alias :consume_all_input? :consume_all_input
def initialize
self.consume_all_input = true
end
def parse(input, options = {})
prepare_to_parse(input)
@index = options[:index] if options[:index]
result = send("_nt_#{root}")
return nil if (consume_all_input? && index != input.size)
return result
end
def failure_index
max_terminal_failure_index
end
def failure_line
terminal_failures && input.line_of(failure_index)
end
def failure_column
terminal_failures && input.column_of(failure_index)
end
def failure_reason
return nil unless (tf = terminal_failures) && tf.size > 0
"Expected " +
(tf.size == 1 ?
tf[0].expected_string :
"one of #{tf.map{|f| f.expected_string}.uniq*', '}"
) +
" at line #{failure_line}, column #{failure_column} (byte #{failure_index+1})" +
" after #{input[index...failure_index]}"
end
protected
attr_reader :node_cache, :input_length
attr_writer :index
def prepare_to_parse(input)
@input = input
@input_length = input.length
reset_index
@node_cache = Hash.new {|hash, key| hash[key] = Hash.new}
@terminal_failures = []
@max_terminal_failure_index = 0
end
def reset_index
@index = 0
end
def parse_anything(node_class = SyntaxNode, inline_module = nil)
if index < input.length
result = instantiate_node(node_class,input, index...(index + 1))
result.extend(inline_module) if inline_module
@index += 1
result
else
terminal_parse_failure("any character")
end
end
def instantiate_node(node_type,*args)
if node_type.respond_to? :new
node_type.new(*args)
else
SyntaxNode.new(*args).extend(node_type)
end
end
def terminal_parse_failure(expected_string)
return nil if index < max_terminal_failure_index
if index > max_terminal_failure_index
@max_terminal_failure_index = index
@terminal_failures = []
end
terminal_failures << TerminalParseFailure.new(index, expected_string)
return nil
end
end
end
end
@@ -1,4 +0,0 @@
dir = File.dirname(__FILE__)
require "#{dir}/interval_skip_list/interval_skip_list.rb"
require "#{dir}/interval_skip_list/head_node.rb"
require "#{dir}/interval_skip_list/node.rb"
@@ -1,15 +0,0 @@
class IntervalSkipList
class HeadNode
attr_reader :height, :forward, :forward_markers
def initialize(height)
@height = height
@forward = Array.new(height, nil)
@forward_markers = Array.new(height) {|i| []}
end
def top_level
height - 1
end
end
end
@@ -1,200 +0,0 @@
class IntervalSkipList
attr_reader :probability
def initialize
@head = HeadNode.new(max_height)
@ranges = {}
@probability = 0.5
end
def max_height
3
end
def empty?
head.forward[0].nil?
end
def expire(range, length_change)
expired_markers, first_node_after_range = overlapping(range)
expired_markers.each { |marker| delete(marker) }
first_node_after_range.propagate_length_change(length_change)
end
def overlapping(range)
markers, first_node = containing_with_node(range.first)
cur_node = first_node
begin
markers.concat(cur_node.forward_markers.flatten)
cur_node = cur_node.forward[0]
end while cur_node.key < range.last
return markers.uniq, cur_node
end
def containing(n)
containing_with_node(n).first
end
def insert(range, marker)
ranges[marker] = range
first_node = insert_node(range.first)
first_node.endpoint_of.push(marker)
last_node = insert_node(range.last)
last_node.endpoint_of.push(marker)
cur_node = first_node
cur_level = first_node.top_level
while next_node_at_level_inside_range?(cur_node, cur_level, range)
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
cur_level += 1
end
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
end
while node_inside_range?(cur_node, range)
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
cur_level -= 1
end
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
end
end
def delete(marker)
range = ranges[marker]
path_to_first_node = make_path
first_node = find(range.first, path_to_first_node)
cur_node = first_node
cur_level = first_node.top_level
while next_node_at_level_inside_range?(cur_node, cur_level, range)
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
cur_level += 1
end
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
end
while node_inside_range?(cur_node, range)
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
cur_level -= 1
end
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
end
last_node = cur_node
first_node.endpoint_of.delete(marker)
if first_node.endpoint_of.empty?
first_node.delete(path_to_first_node)
end
last_node.endpoint_of.delete(marker)
if last_node.endpoint_of.empty?
path_to_last_node = make_path
find(range.last, path_to_last_node)
last_node.delete(path_to_last_node)
end
end
protected
attr_reader :head, :ranges
def insert_node(key)
path = make_path
found_node = find(key, path)
if found_node && found_node.key == key
return found_node
else
return Node.new(key, next_node_height, path)
end
end
def containing_with_node(n)
containing = []
cur_node = head
(max_height - 1).downto(0) do |cur_level|
while (next_node = cur_node.forward[cur_level]) && next_node.key <= n
cur_node = next_node
if cur_node.key == n
return containing + (cur_node.markers - cur_node.endpoint_of), cur_node
end
end
containing.concat(cur_node.forward_markers[cur_level])
end
return containing, cur_node
end
def delete_node(key)
path = make_path
found_node = find(key, path)
found_node.delete(path) if found_node.key == key
end
def find(key, path)
cur_node = head
(max_height - 1).downto(0) do |cur_level|
while (next_node = cur_node.forward[cur_level]) && next_node.key < key
cur_node = next_node
end
path[cur_level] = cur_node
end
cur_node.forward[0]
end
def make_path
Array.new(max_height, nil)
end
def next_node_height
height = 1
while rand < probability && height < max_height
height += 1
end
height
end
def can_ascend_from?(node, level)
level < node.top_level
end
def can_descend_from?(level)
level > 0
end
def node_inside_range?(node, range)
node.key < range.last
end
def next_node_at_level_inside_range?(node, level, range)
node.forward[level] && node.forward[level].key <= range.last
end
def next_node_at_level_outside_range?(node, level, range)
(node.forward[level].nil? || node.forward[level].key > range.last)
end
def mark_forward_path_at_level(node, level, marker)
node.forward_markers[level].push(marker)
next_node = node.forward[level]
next_node.markers.push(marker)
node = next_node
end
def unmark_forward_path_at_level(node, level, marker)
node.forward_markers[level].delete(marker)
next_node = node.forward[level]
next_node.markers.delete(marker)
node = next_node
end
def nodes
nodes = []
cur_node = head.forward[0]
until cur_node.nil?
nodes << cur_node
cur_node = cur_node.forward[0]
end
nodes
end
end
@@ -1,164 +0,0 @@
class IntervalSkipList
class Node < HeadNode
attr_accessor :key
attr_reader :markers, :endpoint_of
def initialize(key, height, path)
super(height)
@key = key
@markers = []
@endpoint_of = []
update_forward_pointers(path)
promote_markers(path)
end
def all_forward_markers
markers.flatten
end
def delete(path)
0.upto(top_level) do |i|
path[i].forward[i] = forward[i]
end
demote_markers(path)
end
def propagate_length_change(length_change)
cur_node = self
while cur_node do
cur_node.key += length_change
cur_node = cur_node.forward[0]
end
end
protected
def update_forward_pointers(path)
0.upto(top_level) do |i|
forward[i] = path[i].forward[i]
path[i].forward[i] = self
end
end
def promote_markers(path)
promoted = []
new_promoted = []
0.upto(top_level) do |i|
incoming_markers = path[i].forward_markers[i]
markers.concat(incoming_markers)
incoming_markers.each do |marker|
if can_be_promoted_higher?(marker, i)
new_promoted.push(marker)
forward[i].delete_marker_from_path(marker, i, forward[i+1])
else
forward_markers[i].push(marker)
end
end
promoted.each do |marker|
if can_be_promoted_higher?(marker, i)
new_promoted.push(marker)
forward[i].delete_marker_from_path(marker, i, forward[i+1])
else
forward_markers[i].push(marker)
end
end
promoted = new_promoted
new_promoted = []
end
end
def can_be_promoted_higher?(marker, level)
level < top_level && forward[level + 1] && forward[level + 1].markers.include?(marker)
end
def delete_marker_from_path(marker, level, terminus)
cur_node = self
until cur_node == terminus
cur_node.forward_markers[level].delete(marker)
cur_node.markers.delete(marker)
cur_node = cur_node.forward[level]
end
end
def demote_markers(path)
demote_inbound_markers(path)
demote_outbound_markers(path)
end
def demote_inbound_markers(path)
demoted = []
new_demoted = []
top_level.downto(0) do |i|
incoming_markers = path[i].forward_markers[i].dup
incoming_markers.each do |marker|
unless forward_node_with_marker_at_or_above_level?(marker, i)
path[i].forward_markers[i].delete(marker)
new_demoted.push(marker)
end
end
demoted.each do |marker|
path[i + 1].place_marker_on_inbound_path(marker, i, path[i])
if forward[i].markers.include?(marker)
path[i].forward_markers[i].push(marker)
else
new_demoted.push(marker)
end
end
demoted = new_demoted
new_demoted = []
end
end
def demote_outbound_markers(path)
demoted = []
new_demoted = []
top_level.downto(0) do |i|
forward_markers[i].each do |marker|
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
end
demoted.each do |marker|
forward[i].place_marker_on_outbound_path(marker, i, forward[i + 1])
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
end
demoted = new_demoted
new_demoted = []
end
end
def forward_node_with_marker_at_or_above_level?(marker, level)
level.upto(top_level) do |i|
return true if forward[i].markers.include?(marker)
end
false
end
def place_marker_on_outbound_path(marker, level, terminus)
cur_node = self
until cur_node == terminus
cur_node.forward_markers[level].push(marker)
cur_node.markers.push(marker)
cur_node = cur_node.forward[level]
end
end
def place_marker_on_inbound_path(marker, level, terminus)
cur_node = self
until cur_node == terminus
cur_node.forward_markers[level].push(marker)
cur_node = cur_node.forward[level]
cur_node.markers.push(marker)
end
end
end
end
@@ -1,72 +0,0 @@
module Treetop
module Runtime
class SyntaxNode
attr_reader :input, :interval, :elements
attr_accessor :parent
def initialize(input, interval, elements = nil)
@input = input
@interval = interval
if @elements = elements
elements.each do |element|
element.parent = self
end
end
end
def terminal?
@elements.nil?
end
def nonterminal?
!terminal?
end
def text_value
input[interval]
end
def empty?
interval.first == interval.last && interval.exclude_end?
end
def extension_modules
local_extensions =
class <<self
included_modules-Object.included_modules
end
if local_extensions.size > 0
local_extensions
else
[] # There weren't any; must be a literal node
end
end
def inspect(indent="")
em = extension_modules
interesting_methods = methods-[em.last ? em.last.methods : nil]-self.class.instance_methods
im = interesting_methods.size > 0 ? " (#{interesting_methods.join(",")})" : ""
tv = text_value
tv = "...#{tv[-20..-1]}" if tv.size > 20
indent +
self.class.to_s.sub(/.*:/,'') +
em.map{|m| "+"+m.to_s.sub(/.*:/,'')}*"" +
" offset=#{interval.first}" +
", #{tv.inspect}" +
im +
(elements && elements.size > 0 ?
":" +
(@elements||[]).map{|e|
begin
"\n"+e.inspect(indent+" ")
rescue # Defend against inspect not taking a parameter
"\n"+indent+" "+e.inspect
end
}.join("") :
""
)
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More