voodoo 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/ast.rb +47 -0
- data/bin/voodooc +146 -0
- data/lib/voodoo.rb +74 -0
- data/lib/voodoo/code_generator.rb +74 -0
- data/lib/voodoo/compiler.rb +45 -0
- data/lib/voodoo/config.rb +43 -0
- data/lib/voodoo/generators/amd64_elf_generator.rb +28 -0
- data/lib/voodoo/generators/amd64_nasm_generator.rb +288 -0
- data/lib/voodoo/generators/command_postprocessor.rb +30 -0
- data/lib/voodoo/generators/common_code_generator.rb +238 -0
- data/lib/voodoo/generators/gas_generator.rb +91 -0
- data/lib/voodoo/generators/generator_api1.rb +95 -0
- data/lib/voodoo/generators/i386_elf_generator.rb +62 -0
- data/lib/voodoo/generators/i386_nasm_generator.rb +177 -0
- data/lib/voodoo/generators/mips_gas_generator.rb +148 -0
- data/lib/voodoo/generators/nasm_elf_generator.rb +55 -0
- data/lib/voodoo/generators/nasm_generator.rb +679 -0
- data/lib/voodoo/parser.rb +283 -0
- metadata +82 -0
data/bin/ast.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Ast
|
2
|
+
# A program consists of zero or more elements,
|
3
|
+
# each one of which can be a label, an action,
|
4
|
+
# a function definition, or a comment
|
5
|
+
class Program
|
6
|
+
def initialize elements
|
7
|
+
@elements = elements
|
8
|
+
end
|
9
|
+
attr_accessor :elements
|
10
|
+
end
|
11
|
+
|
12
|
+
# A comment contains text
|
13
|
+
class Comment
|
14
|
+
def initialize text
|
15
|
+
@text = text
|
16
|
+
end
|
17
|
+
attr_accessor :text
|
18
|
+
end
|
19
|
+
|
20
|
+
# A label contains text
|
21
|
+
class Label
|
22
|
+
def initialize text
|
23
|
+
@text = text
|
24
|
+
end
|
25
|
+
attr_accessor :text
|
26
|
+
end
|
27
|
+
|
28
|
+
# A function definition has an argument list and contains
|
29
|
+
# a number of actions and/or comments
|
30
|
+
class Function
|
31
|
+
def initialize args, actions
|
32
|
+
@args = args
|
33
|
+
@statements = actions
|
34
|
+
end
|
35
|
+
attr_accessor :args, :actions
|
36
|
+
end
|
37
|
+
|
38
|
+
# An action contains an operation and zero or more
|
39
|
+
# arguments
|
40
|
+
class Action
|
41
|
+
def initialize op, args
|
42
|
+
@op = op
|
43
|
+
@args = args
|
44
|
+
end
|
45
|
+
attr_accessor :op, :args
|
46
|
+
end
|
47
|
+
end
|
data/bin/voodooc
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'getoptlong'
|
4
|
+
|
5
|
+
require 'voodoo'
|
6
|
+
|
7
|
+
usage="USAGE: voodooc [options] <input file>"
|
8
|
+
help=<<EOT
|
9
|
+
Input file denotes the file to compile. The special name '-' causes
|
10
|
+
voodooc to read from standard input. In that case, the -o option is
|
11
|
+
mandatory.
|
12
|
+
|
13
|
+
Valid options are:
|
14
|
+
|
15
|
+
-a <architecture>
|
16
|
+
--arch <architecture>
|
17
|
+
--architecture <architecture>
|
18
|
+
Select target architecture. Use -a help to get a list of
|
19
|
+
supported architectures.
|
20
|
+
|
21
|
+
-f <format>
|
22
|
+
--format <format>
|
23
|
+
--output-format <format>
|
24
|
+
Select output format. Use -a <architecture> -f help to get a list of
|
25
|
+
supported output formats for architecture.
|
26
|
+
|
27
|
+
-o <output file>
|
28
|
+
--output <output file>
|
29
|
+
--output-file <output file>
|
30
|
+
Set output file name. The special name '-' causes voodooc to write
|
31
|
+
to standard output.
|
32
|
+
EOT
|
33
|
+
|
34
|
+
input_file = nil
|
35
|
+
output_file = nil
|
36
|
+
architecture = Voodoo::Config.default_architecture
|
37
|
+
output_format = Voodoo::Config.default_format
|
38
|
+
|
39
|
+
#
|
40
|
+
# Process command line
|
41
|
+
#
|
42
|
+
|
43
|
+
opts = GetoptLong.new(
|
44
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT ],
|
45
|
+
['--arch', '--architecture', '-a', GetoptLong::REQUIRED_ARGUMENT ],
|
46
|
+
['--output-file', '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ],
|
47
|
+
['--output-format', '--format', '-f', GetoptLong::REQUIRED_ARGUMENT ]
|
48
|
+
)
|
49
|
+
|
50
|
+
# Process options
|
51
|
+
opts.each do |opt, arg|
|
52
|
+
case opt
|
53
|
+
when '--help'
|
54
|
+
puts usage
|
55
|
+
puts help
|
56
|
+
exit
|
57
|
+
when '--arch'
|
58
|
+
architecture = arg.to_sym
|
59
|
+
when '--output-file'
|
60
|
+
output_file = arg
|
61
|
+
when '--output-format'
|
62
|
+
output_format = arg.to_sym
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if architecture == :help
|
67
|
+
# List supported architectures
|
68
|
+
puts "Supported architectures:"
|
69
|
+
puts Voodoo::CodeGenerator.architectures
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
if output_format == :help
|
74
|
+
# List supported output formats
|
75
|
+
puts "Supported output formats for architecture #{architecture}:"
|
76
|
+
puts Voodoo::CodeGenerator.formats architecture
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get input file name
|
81
|
+
if ARGV.length == 1
|
82
|
+
input_file = ARGV[0]
|
83
|
+
else
|
84
|
+
if ARGV.length < 1
|
85
|
+
$stderr.puts "no input files"
|
86
|
+
else
|
87
|
+
$stderr.puts "Too many arguments"
|
88
|
+
end
|
89
|
+
$stderr.puts usage
|
90
|
+
exit 0x80
|
91
|
+
end
|
92
|
+
|
93
|
+
# Select code generator based on output format
|
94
|
+
begin
|
95
|
+
generator = Voodoo::CodeGenerator.get_generator :architecture => architecture,
|
96
|
+
:format => output_format
|
97
|
+
rescue NotImplementedError
|
98
|
+
if Voodoo::CodeGenerator.architecture_supported? architecture
|
99
|
+
$stderr.puts "Format #{output_format} is not supported for architecture #{architecture}"
|
100
|
+
$stderr.puts "Supported formats for #{architecture} are:"
|
101
|
+
$stderr.puts Voodoo::CodeGenerator.formats(architecture)
|
102
|
+
else
|
103
|
+
$stderr.puts "Architecture #{architecture} is not supported."
|
104
|
+
$stderr.puts "Supported architectures are:"
|
105
|
+
$stderr.puts Voodoo::CodeGenerator.architectures
|
106
|
+
end
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
#
|
112
|
+
# Compile
|
113
|
+
#
|
114
|
+
|
115
|
+
if input_file == '-'
|
116
|
+
infile = $stdin
|
117
|
+
unless output_file
|
118
|
+
$stderr.puts "The -o option is mandatory when reading from standard input"
|
119
|
+
exit 0x80
|
120
|
+
end
|
121
|
+
else
|
122
|
+
infile = open input_file
|
123
|
+
# Set output_file if not already set
|
124
|
+
output_file = generator.output_file_name input_file unless output_file
|
125
|
+
end
|
126
|
+
|
127
|
+
if output_file == '-'
|
128
|
+
outfile = $stdout
|
129
|
+
else
|
130
|
+
outfile = open output_file, 'w'
|
131
|
+
end
|
132
|
+
|
133
|
+
begin
|
134
|
+
parser = Voodoo::Parser.new infile
|
135
|
+
compiler = Voodoo::Compiler.new parser, generator, outfile
|
136
|
+
|
137
|
+
compiler.compile
|
138
|
+
rescue
|
139
|
+
if output_file != '-'
|
140
|
+
File::unlink(output_file) if File::exists?(output_file)
|
141
|
+
end
|
142
|
+
raise
|
143
|
+
ensure
|
144
|
+
outfile.close
|
145
|
+
infile.close
|
146
|
+
end
|
data/lib/voodoo.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'voodoo/config'
|
2
|
+
require 'voodoo/code_generator'
|
3
|
+
require 'voodoo/compiler'
|
4
|
+
require 'voodoo/parser'
|
5
|
+
|
6
|
+
# = Voodoo - Code Generation for Multiple Target Platforms
|
7
|
+
#
|
8
|
+
# This module implements a compiler for the {Voodoo programming
|
9
|
+
# language}[http://inglorion.net/documents/designs/voodoo/], a simple
|
10
|
+
# programming language designed to be a thin abstraction of the CPU's
|
11
|
+
# native instruction set.
|
12
|
+
#
|
13
|
+
# The compiler consists of three parts:
|
14
|
+
#
|
15
|
+
# 1. The parser (Voodoo::Parser), which reads
|
16
|
+
# Voodoo[http://inglorion.net/documents/designs/voodoo/] source code and
|
17
|
+
# turns it into Ruby[http://www.ruby-lang.org/] objects.
|
18
|
+
#
|
19
|
+
# 2. The code generator (a class that implements the methods of
|
20
|
+
# Voodoo::CommonCodeGenerator), which provides methods that generate
|
21
|
+
# code for the target platform.
|
22
|
+
#
|
23
|
+
# 3. The compiler driver (Voodoo::Compiler), which reads from the parser
|
24
|
+
# and calls the appropriate methods of the code generator.
|
25
|
+
#
|
26
|
+
# Both the parser and the code generators can be used on their own. For
|
27
|
+
# example, you could use the code generator to generate native code for
|
28
|
+
# your own programming language without first creating a
|
29
|
+
# Voodoo[http://inglorion.net/documents/designs/voodoo/] program.
|
30
|
+
#
|
31
|
+
# Instead of instantiating a code generator directly, it is recommended
|
32
|
+
# that you use Voodoo::CodeGenerator.get_generator to obtain a suitable
|
33
|
+
# code generator for your target platform.
|
34
|
+
#
|
35
|
+
# A few examples to clarify the usage of the module:
|
36
|
+
#
|
37
|
+
# The following code compiles the source file <tt>test.voo</tt> to an ELF
|
38
|
+
# object file called <tt>test.o</tt> containing object code for i386:
|
39
|
+
#
|
40
|
+
# require 'voodoo'
|
41
|
+
#
|
42
|
+
# File.open('test.voo') do |infile|
|
43
|
+
# parser = Voodoo::Parser.new infile
|
44
|
+
# generator = Voodoo::CodeGenerator.get_generator :architecture => :i386,
|
45
|
+
# :format => :elf
|
46
|
+
# File.open('test.o', 'w') do |outfile|
|
47
|
+
# compiler = Voodoo::Compiler.new parser, generator, outfile
|
48
|
+
# compiler.compile
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# The following code uses the code generator API to create an object file
|
53
|
+
# without the need to create a
|
54
|
+
# Voodoo[http://inglorion.net/documents/designs/voodoo/] program:
|
55
|
+
#
|
56
|
+
# require 'voodoo'
|
57
|
+
#
|
58
|
+
# generator = Voodoo::CodeGenerator.get_generator
|
59
|
+
#
|
60
|
+
# generator.add :functions, [:export, :fact], [:label, :fact]
|
61
|
+
# generator.add_function [:n],
|
62
|
+
# [:ifle, [:n, 1],
|
63
|
+
# # then
|
64
|
+
# [[:return, 1]],
|
65
|
+
# # else
|
66
|
+
# [[:let, :x, :sub, :n, 1],
|
67
|
+
# [:set, :x, :call, :fact, :x],
|
68
|
+
# [:return, :mul, :n, :x]]]
|
69
|
+
#
|
70
|
+
# File.open('fact.o', 'w') { |outfile| generator.write outfile }
|
71
|
+
#
|
72
|
+
|
73
|
+
module Voodoo
|
74
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'voodoo/config'
|
2
|
+
|
3
|
+
module Voodoo
|
4
|
+
# Module for selecting code generators.
|
5
|
+
#
|
6
|
+
# The code generation API is described in Voodoo::CommonCodeGenerator.
|
7
|
+
module CodeGenerator
|
8
|
+
# Hash using target architectures as keys.
|
9
|
+
# Each entry is a hash mapping output format to generator class.
|
10
|
+
@@generators = {}
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Register a code generator.
|
15
|
+
# Example:
|
16
|
+
# Voodoo::CodeGenerator.register_generator I386NasmGenerator,
|
17
|
+
# :architecture => :i386,
|
18
|
+
# :format => :nasm
|
19
|
+
def register_generator klass, params
|
20
|
+
if params.has_key?(:architecture) && params.has_key?(:format)
|
21
|
+
arch = params[:architecture].to_sym
|
22
|
+
format = params[:format].to_sym
|
23
|
+
format_hash = @@generators[arch] || {}
|
24
|
+
format_hash[format] = klass
|
25
|
+
@@generators[arch] = format_hash
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Need to specify :architecture and :format"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get a code generator for the specified parameters
|
32
|
+
def get_generator params = {}
|
33
|
+
params[:architecture] = Config.default_architecture \
|
34
|
+
unless params[:architecture]
|
35
|
+
params[:format] = Config.default_format unless params[:format]
|
36
|
+
arch = params[:architecture].to_sym
|
37
|
+
format = params[:format].to_sym
|
38
|
+
format_hash = @@generators[arch]
|
39
|
+
klass = format_hash ? format_hash[format] : nil
|
40
|
+
if klass
|
41
|
+
return klass.new params
|
42
|
+
else
|
43
|
+
raise NotImplementedError, "No code generator for architecture #{arch} and format #{format}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Tests if a given architecture is supported
|
48
|
+
def architecture_supported? arch
|
49
|
+
@@generators.has_key? arch.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get an array of supported architectures
|
53
|
+
def architectures
|
54
|
+
@@generators.keys
|
55
|
+
end
|
56
|
+
|
57
|
+
# Tests if a given format is supported for a given architecture
|
58
|
+
def format_supported? arch, format
|
59
|
+
architecture_supported?(arch.to_sym) &&
|
60
|
+
@@generators[arch.to_sym].has_key?(format.to_sym)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get an array of supported formats for a given architecture
|
64
|
+
def formats architecture
|
65
|
+
@@generators[architecture.to_sym].keys
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Load generators
|
71
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'generators', '*.rb')).each do |file|
|
72
|
+
name = file.sub(/.*(voodoo\/generators\/.*)\.rb/, "\\1")
|
73
|
+
require name
|
74
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Voodoo
|
2
|
+
# Voodoo compiler driver.
|
3
|
+
# The compiler driver reads input from a parser (see
|
4
|
+
# Voodoo::Parser), feeds it to a code generator (see
|
5
|
+
# Voodoo::CommonCodeGenerator), and writes the generated code.
|
6
|
+
#
|
7
|
+
# An example of its usage can be found on the main page for the
|
8
|
+
# Voodoo module.
|
9
|
+
class Compiler
|
10
|
+
# Initialize a compiler.
|
11
|
+
#
|
12
|
+
# Parameters:
|
13
|
+
# [parser] the parser to be used (see Voodoo::Parser)
|
14
|
+
# [code_generator] the code generator to be used
|
15
|
+
# (see Voodoo::CommonCodeGenerator)
|
16
|
+
# [output] an IO object. The generated code will be written to it
|
17
|
+
def initialize parser, code_generator, output
|
18
|
+
@parser = parser
|
19
|
+
@generator = code_generator
|
20
|
+
@output = output
|
21
|
+
end
|
22
|
+
|
23
|
+
# Perform the compilation.
|
24
|
+
def compile
|
25
|
+
section = :code
|
26
|
+
while true
|
27
|
+
statement = @parser.parse_top_level
|
28
|
+
|
29
|
+
break if statement == nil
|
30
|
+
next if statement.empty?
|
31
|
+
|
32
|
+
case statement[0]
|
33
|
+
when :section
|
34
|
+
section = statement[1]
|
35
|
+
when :function
|
36
|
+
@generator.add section, [:function, statement[1]] + statement[2]
|
37
|
+
else
|
38
|
+
@generator.add section, statement
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@generator.write @output
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Voodoo
|
2
|
+
# Methods to get and set configuration parameters
|
3
|
+
module Config
|
4
|
+
# Class that holds configuration parameters
|
5
|
+
class Configuration
|
6
|
+
def initialize
|
7
|
+
@default_architecture = :amd64
|
8
|
+
@default_format = :elf
|
9
|
+
@nasm_command = "/usr/bin/nasm"
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :default_architecture,
|
13
|
+
:default_format,
|
14
|
+
:nasm_command
|
15
|
+
end
|
16
|
+
|
17
|
+
DEFAULT_CONFIGURATION = Configuration.new
|
18
|
+
|
19
|
+
module_function
|
20
|
+
|
21
|
+
def default_architecture
|
22
|
+
DEFAULT_CONFIGURATION.default_architecture
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_architecture= value
|
26
|
+
DEFAULT_CONFIGURATION.default_architecture = value
|
27
|
+
end
|
28
|
+
def default_format
|
29
|
+
DEFAULT_CONFIGURATION.default_format
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_format= value
|
33
|
+
DEFAULT_CONFIGURATION.default_format = value
|
34
|
+
end
|
35
|
+
def nasm_command
|
36
|
+
DEFAULT_CONFIGURATION.nasm_command
|
37
|
+
end
|
38
|
+
|
39
|
+
def nasm_command= value
|
40
|
+
DEFAULT_CONFIGURATION.nasm_command = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'voodoo/generators/amd64_nasm_generator'
|
4
|
+
require 'voodoo/generators/nasm_elf_generator'
|
5
|
+
|
6
|
+
module Voodoo
|
7
|
+
# Generator that produces ELF objects for amd64
|
8
|
+
class AMD64ELFGenerator < DelegateClass(AMD64NasmGenerator)
|
9
|
+
def initialize params = {}
|
10
|
+
@nasmgenerator = AMD64NasmGenerator.new params
|
11
|
+
super(@nasmgenerator)
|
12
|
+
@elfgenerator = NasmELFGenerator.new @nasmgenerator, '-f elf64'
|
13
|
+
end
|
14
|
+
|
15
|
+
def output_file_name input_name
|
16
|
+
input_name.sub(/\.voo$/, '') + '.o'
|
17
|
+
end
|
18
|
+
|
19
|
+
def write io
|
20
|
+
@elfgenerator.write io
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register class
|
25
|
+
Voodoo::CodeGenerator.register_generator AMD64ELFGenerator,
|
26
|
+
:architecture => :amd64,
|
27
|
+
:format => :elf
|
28
|
+
end
|