Compare commits

...

18 Commits

Author SHA1 Message Date
bc798f11fe Add README. 2018-09-19 17:51:12 -07:00
d40ea2a8ae Add some more complex programs. 2018-08-08 22:22:06 -07:00
eb32577d38 Add a clear function. 2018-08-08 22:21:57 -07:00
e740fd7688 Optimizer is still broken. 2018-08-08 22:21:50 -07:00
5ce0d41a53 Add a sprite draw function. 2018-08-08 21:47:47 -07:00
b79d717f1a Generate sprite data. 2018-08-06 23:43:46 -07:00
60c320dfea Add sprites to source code and table. 2018-08-06 21:51:53 -07:00
7fe5cec941 Merge branch 'master' of https://dev.danilafe.com/Chip-8-Wizardry/chalk 2018-08-05 20:32:32 -07:00
89709fef97 Split symbol table into have a separate hash for each entry type. 2018-08-05 20:32:10 -07:00
45f3e51890 Add a sprite representation. 2018-08-05 20:27:43 -07:00
61fb44bbce Add more small test programs. 2018-08-05 14:48:51 -07:00
230a50c532 Dedicate a throwaway register. 2018-08-05 13:36:54 -07:00
a96d503095 Properly throw away unused expressions in blocks. 2018-08-05 13:35:16 -07:00
caaae87344 Add non-empty test programs. 2018-08-05 13:34:12 -07:00
86ee6557cf Fix bug in codegen. 2018-08-05 13:11:12 -07:00
a7e24c059b Begin working on a testing script. 2018-08-05 00:59:27 -07:00
2ed2a4932c Add an output file parameter. 2018-08-04 13:49:29 -07:00
1150e2b3ef Add log level. 2018-08-03 16:36:27 -07:00
26 changed files with 548 additions and 75 deletions

View File

@@ -1,22 +1,64 @@
# chalk
TODO: Write a description here
Chalk is a basic compiler from a toy language into CHIP-8 bytecode.
It supports some higher-level constructs like looks and if-statements,
and has some higher-level functions for drawing numbers and sprites.
## Installation
TODO: Write installation instructions here
To compile chalk, simply run
```Bash
crystal build --release src/chalk.cr
```
## Usage
### Syntax
Chalk has minimal syntax. All code goes into functions, which are declared as follows:
```
fun function(param, another_param): return_type {
TODO: Write usage instructions here
}
```
All parameters are assumed to be of type `u8`, an unsigned byte.
The return type can be either `u8` or `u0`, the latter being the same as `void` in C.
Users can declare variables using the `var` keyword:
```
var name = 5;
```
Assigning to existing variables is performed as such:
```
name = 6;
```
It's possible to call another function, which uses the CHIP-8 stack. This stack
is used exclusively for function calls, and may not exceed 12, as per the specifications
of the CHIP-8 virtual machine.
```
var result = double(name);
```
Additionally, to ensure that user-defined variables stored
in registers are not modified, chalk uses its own custom stack, placed at the end of the program.
This stack is accessed by modifying a stack pointer register, which holds the offset from the beginning of the stack. Because CHIP-8 registers can only hold up 8-bit unsinged numbers, the stack is therefore limited to 254 items.
## Development
If statements take "truthy" values to be those that have a nonzero value. As such,
`if (0) {}` will never run, while `while(5) {}` will never terminate. Input can be awaited
using the `get_key` function, which returns the number of the next key pressed.
TODO: Write development instructions here
Sprites can be declared using a quasi-visual syntax:
```
sprite dum [
` x x `
` x x `
` x x `
` `
`x x`
` xxxxxx `
]
```
These sprites can then be used in calls to `draw_sprite(sprite, x, y)`.
## Contributing
1. Fork it (<https://github.com/your-github-user/chalk/fork>)
1. Fork it (<https://github.com/DanilaFe/chalk/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
@@ -24,4 +66,4 @@ TODO: Write development instructions here
## Contributors
- [your-github-user](https://github.com/your-github-user) Danila Fedorin - creator, maintainer
- [DanilaFe](https://github.com/DanilaFe) Danila Fedorin - creator, maintainer

View File

@@ -0,0 +1,7 @@
fun main(): u0 {
1+2+3;
var a = 5+5;
var b = a;
var a = a + b;
var c = a - b;
}

21
programs/basic_call.chalk Normal file
View File

@@ -0,0 +1,21 @@
fun double(a): u8 {
return a + a;
}
fun quadruple(a): u8 {
return double(double(a));
}
fun reuse(): u0 {
var one = 2;
var two = 4;
var three = 6;
}
fun main(): u0 {
var a = quadruple(4);
var one = 1;
var two = 2;
var three = 3;
reuse();
}

View File

@@ -0,0 +1,3 @@
fun main(): u0 {
draw_number(69, 1, 1);
}

View File

@@ -0,0 +1,3 @@
fun main(): u0 {
}

17
programs/basic_if.chalk Normal file
View File

@@ -0,0 +1,17 @@
fun main(): u0 {
var a = 3+3;
if(a) {
a = 0;
}
var b = 0;
if(b) {
b = 4;
}
if(b) {
b = 4;
} else {
b = 5;
}
}

View File

@@ -0,0 +1,4 @@
fun main(): u0 {
var a = get_key();
var b = get_key();
}

View File

@@ -0,0 +1,12 @@
sprite dum [
` x x `
` x x `
` x x `
` `
`x x`
` xxxxxx `
]
fun main(): u0 {
draw_sprite(dum, 0, 0);
}

View File

@@ -0,0 +1,8 @@
fun main(): u0 {
var a = 5;
var b = 0;
while(a) {
b = b + 2;
a = a - 1;
}
}

14
programs/comb_fib.chalk Normal file
View File

@@ -0,0 +1,14 @@
fun main(): u0 {
var a = 1;
var b = 1;
while(233 - a) {
draw_number(a, 24, 13);
set_delay(30);
var temp = a;
a = b;
b = b + temp;
while(get_delay()){}
draw_number(temp, 24, 13);
}
draw_number(a, 24, 13);
}

13
programs/comb_input.chalk Normal file
View File

@@ -0,0 +1,13 @@
fun main(): u0 {
var a = 1;
var b = 0;
var previous = 0;
draw_number(0, 24, 13);
while(a) {
previous = b;
b = get_key();
draw_number(previous, 24, 13);
draw_number(b, 24, 13);
}
}

View File

@@ -0,0 +1,39 @@
sprite dum [
` x x `
` x x `
` x x `
` `
`x x`
` xxxxxx `
]
fun main(): u8 {
var x = 0;
var y = 5;
var vy = 0;
var ay = 1;
var mode = 0;
while(1) {
draw_sprite(dum, x, y);
set_delay(1);
while(get_delay()){}
draw_sprite(dum, x, y);
if(5 - y) {
} else {
mode = 0;
}
if(26 - y) {
} else {
mode = 1;
}
if(mode) {
vy = vy - ay;
y = y - vy;
} else {
y = y + vy;
vy = vy + ay;
}
}
}

View File

@@ -0,0 +1,34 @@
sprite dum [
` x x `
` x x `
` x x `
` `
`x x`
` xxxxxx `
]
fun main(): u8 {
var x = 0;
var y = 27;
var vx = 1;
var vy = 0;
var a = 1;
while(1) {
var draw_y = 32 - y;
draw_sprite(dum, x, draw_y);
set_delay(1);
while(get_delay()){}
vy = vy - a;
y = y + vy;
x = x + vx;
if(6-y) {
} else {
var vtemp = vy;
vy = 0 - vtemp;
vy = vy + 1;
}
clear();
}
}

View File

@@ -19,7 +19,7 @@ module Chalk
@table = Table.new table
@function.params.each do |param|
@table[param] = VarEntry.new @registers
@table.set_var param, VarEntry.new @registers
@registers += 1
end
end
@@ -74,9 +74,8 @@ module Chalk
def generate!(tree, table, target, free)
case tree
when Trees::TreeId
entry = table[tree.id]?
raise "Unknown variable" unless entry &&
entry.is_a?(VarEntry)
entry = table.get_var? tree.id
raise "Unknown variable" unless entry
loadr target, entry.register
when Trees::TreeLit
load target, tree.lit
@@ -85,28 +84,26 @@ module Chalk
generate! tree.right, table, free, free + 1
opr tree.op, target, free
when Trees::TreeCall
entry = table[tree.name]?.not_nil!
entry = table.get_function?(tree.name).not_nil!
raise "Unknown function" unless entry.is_a?(FunctionEntry)
generate! tree, entry.function, table, target, free
when Trees::TreeBlock
table = Table.new(table)
tree.children.each do |child|
free += generate! child, table, free, free + 1
free += generate! child, table, THROWAWAY_REG, free
end
when Trees::TreeVar
entry = table[tree.name]?
entry = table.get_var? tree.name
if entry == nil
entry = VarEntry.new free
free += 1
table[tree.name] = entry
table.set_var tree.name, entry
end
raise "Unknown variable" unless entry.is_a?(VarEntry)
generate! tree.expr, table, entry.register, free
generate! tree.expr, table, entry.as(VarEntry).register, free
return 1
when Trees::TreeAssign
entry = table[tree.name]?
raise "Unknown variable" unless entry &&
entry.is_a?(VarEntry)
entry = table.get_var? tree.name
raise "Unknown variable" unless entry
generate! tree.expr, table, entry.register, free
when Trees::TreeIf
generate! tree.condition, table, free, free + 1
@@ -134,7 +131,8 @@ module Chalk
cond_jump.offset = @instructions.size - old_size + 1
after_jump.offset = before_cond - instructions.size + 1
when Trees::TreeReturn
generate! tree.rvalue, table, RETURN_REG, free
generate! tree.rvalue, table, free, free + 1
loadr RETURN_REG, free
ret
end
return 0
@@ -142,7 +140,7 @@ module Chalk
# Generates code for the function that was given to it.
def generate!
generate!(@function.block, @table, -1, @registers)
generate!(@function.block, @table, 0, @registers)
return @instructions
end
end

View File

@@ -12,7 +12,7 @@ module Chalk
def initialize(@config : Ui::Config)
@logger = Logger.new STDOUT
@logger.debug("Initialized compiler")
@logger.level = Logger::DEBUG
@logger.level = @config.loglevel
end
# Reads a file an extracts instances of
@@ -33,8 +33,9 @@ module Chalk
@logger.debug("Beginning constant folding")
folder = Trees::ConstantFolder.new
trees.map! do |tree|
next tree unless tree.is_a?(Trees::TreeFunction)
@logger.debug("Constant folding #{tree.name}")
tree.apply(folder).as(Trees::TreeFunction)
tree.apply(folder)
end
@logger.debug("Done constant folding")
return trees
@@ -48,16 +49,26 @@ module Chalk
table = Table.new
@logger.debug("Creating symbol table")
trees.each do |tree|
@logger.debug("Storing #{tree.name} in symbol table")
table[tree.name] = FunctionEntry.new tree
case tree
when Trees::TreeSprite
@logger.debug("Storing sprite #{tree.name} in symbol table")
table.set_sprite tree.name, SpriteEntry.new tree.sprite
when Trees::TreeFunction
@logger.debug("Storing function #{tree.name} in symbol table")
table.set_function tree.name, FunctionEntry.new tree
else
@logger.debug("Unexpected tree type in input.")
end
end
@logger.debug("Done creating symbol table")
table["get_key"] = FunctionEntry.new Builtin::InlineAwaitKeyFunction.new
table["set_delay"] = FunctionEntry.new Builtin::InlineSetDelayFunction.new
table["get_delay"] = FunctionEntry.new Builtin::InlineGetDelayFunction.new
table["set_sound"] = FunctionEntry.new Builtin::InlineSetSoundFunction.new
table["draw_number"] = FunctionEntry.new Builtin::InlineDrawNumberFunction.new
table.set_function "get_key", FunctionEntry.new Builtin::InlineAwaitKeyFunction.new
table.set_function "set_delay", FunctionEntry.new Builtin::InlineSetDelayFunction.new
table.set_function "get_delay", FunctionEntry.new Builtin::InlineGetDelayFunction.new
table.set_function "set_sound", FunctionEntry.new Builtin::InlineSetSoundFunction.new
table.set_function "draw_number", FunctionEntry.new Builtin::InlineDrawNumberFunction.new
table.set_function "draw_sprite", FunctionEntry.new Builtin::InlineDrawSpriteFunction.new
table.set_function "clear", FunctionEntry.new Builtin::InlineClearFunction.new
return table
end
@@ -71,7 +82,7 @@ module Chalk
@logger.debug("Generating code for #{tree.name}")
code = generator.generate!
code << instruction
return optimizer.optimize(code)
return code # optimizer.optimize(code)
end
# Generate code for a builtin function. Neither the *table* nor the *instruction*
@@ -93,6 +104,11 @@ module Chalk
return code
end
private def create_code(trees : Array(Trees::Tree), table)
functions = trees.select &.is_a?(Trees::TreeFunction)
return create_code(functions.map &.as(Trees::TreeFunction), table)
end
# Runs in the tree `Ui::OutputMode`. The file is
# tokenized and parsed, and the result is printed
# to the standard output.
@@ -126,9 +142,9 @@ module Chalk
binary = instructions.map_with_index { |it, i| it.to_bin(table, instructions.size, i).to_u16 }
binary.each do |inst|
first = (inst >> 8).to_u8
dest.write_byte(first)
dest << first
second = (inst & 0xff).to_u8
dest.write_byte(second)
dest << second
end
end
@@ -143,8 +159,8 @@ module Chalk
first = open.first
open.delete first
entry = table[first]?
raise "Unknown function" unless entry && entry.is_a?(FunctionEntry)
entry = table.get_function? first
raise "Unknown function" unless entry
function = entry.function
next if function.is_a?(Builtin::InlineFunction)
done << first
@@ -166,13 +182,13 @@ module Chalk
names = collect_calls(table)
names.delete "main"
main_entry = table["main"]?.as(FunctionEntry)
main_entry = table.get_function?("main").not_nil!
all_instructions.concat create_code(main_entry.function.as(Trees::TreeFunction),
table, Ir::JumpRelativeInstruction.new 0)
main_entry.addr = 0
names.each do |name|
entry = table[name]?.as(FunctionEntry)
entry = table.get_function?(name).not_nil!
entry.addr = all_instructions.size
function = entry.function
raise "Trying to compile inlined function" if function.is_a?(Builtin::InlineFunction)
@@ -180,8 +196,22 @@ module Chalk
all_instructions << Ir::ReturnInstruction.new
end
file = File.open("out.ch8", "w")
generate_binary(table, all_instructions, file)
sprite_bytes = [] of UInt8
offset = 0
table.sprites.each do |k, v|
data = v.sprite.encode
v.addr = offset + all_instructions.size * 2
offset += data.size
sprite_bytes.concat data
end
binary = [] of UInt8
file = File.open(@config.output, "w")
generate_binary(table, all_instructions, binary)
binary.concat sprite_bytes
binary.each do |byte|
file.write_byte byte
end
file.close
end

View File

@@ -22,10 +22,20 @@ module Chalk
getter mode : OutputMode
# Sets the mode in which the compiler should operate.
setter mode : OutputMode
# Gets the log level.
getter loglevel : Logger::Severity
# Sets the log level.
setter loglevel : Logger::Severity
# Gets the output file destination.
getter output : String
# Sets the output file destination.
setter output : String
# Creates a new configuration.
def initialize(@file = "",
@mode = OutputMode::Tree)
@mode = OutputMode::Tree,
@loglevel = Logger::Severity::DEBUG,
@output : String = "out.ch8")
end
# Reads a configuration from the command line options.
@@ -34,20 +44,35 @@ module Chalk
OptionParser.parse! do |parser|
parser.banner = "Usage: chalk [arguments]"
parser.on("-m", "--mode=MODE", "Set the mode of the compiler.") do |mode|
case mode.downcase
when "tree", "t"
config.mode = OutputMode::Tree
when "intermediate", "i"
config.mode = OutputMode::Intermediate
when "binary", "b"
config.mode = OutputMode::Binary
else
puts "Invalid mode type. Using default."
end
hash = {
"tree" => OutputMode::Tree,
"t" => OutputMode::Tree,
"intermediate" => OutputMode::Intermediate,
"i" => OutputMode::Intermediate,
"binary" => OutputMode::Binary,
"b" => OutputMode::Binary
}
puts "Invalid mode type. Using default." if !hash.has_key?(mode)
config.mode = hash[mode]? || OutputMode::Tree
end
parser.on("-f", "--file=FILE", "Set the input file to compile.") do |file|
config.file = file
end
parser.on("-o", "--output=OUT", "Sets the output file.") do |out|
config.output = out
end
parser.on("-l", "--log=LOG", "Set the log level of the compiler.") do |log|
hash = {
"debug" => Logger::Severity::DEBUG,
"fatal" => Logger::Severity::FATAL,
"error" => Logger::Severity::ERROR,
"info" => Logger::Severity::INFO,
"unknown" => Logger::Severity::UNKNOWN,
"warn" => Logger::Severity::WARN
}
puts "Invalid log level. Using default." if !hash.has_key?(log)
config.loglevel = hash[log]? || Logger::Severity::DEBUG
end
parser.on("-h", "--help", "Show this message.") { puts parser }
end
return config

View File

@@ -4,6 +4,8 @@ module Chalk
RETURN_REG = 14
# The register into which the "stack pointer" is stored.
STACK_REG = 13
# Register used for throwing away values.
THROWAWAY_REG = 12
# Module to emit instructions and store
# them into an existing array.

View File

@@ -81,5 +81,31 @@ module Chalk
return Compiler::FunctionType.new([Compiler::Type::U8] * 3, Compiler::Type::U0)
end
end
class InlineDrawSpriteFunction < InlineFunction
def generate!(emitter, params, table, target, free)
raise "First parameter should be a sprite name." if !params[0].is_a?(Trees::TreeId)
sprite_name = params[0].as(Trees::TreeId).id
sprite = table.get_sprite?(sprite_name).not_nil!.sprite
emitter.generate! params[1], table, free, free + 1
emitter.generate! params[2], table, free + 1, free + 2
emitter.instructions << Ir::SetISpriteInstruction.new params[0].as(Trees::TreeId).id
emitter.instructions << Ir::DrawInstruction.new free, free + 1, sprite.height.to_i32
end
def type
return Compiler::FunctionType.new([Compiler::Type::U8] * 3, Compiler::Type::U0)
end
end
class InlineClearFunction < InlineFunction
def generate!(emitter, params, table, target, free)
emitter.instructions << Ir::ClearInstruction.new
end
def type
return Compiler::FunctionType.new(([] of Compiler::Type), Compiler::Type::U0)
end
end
end
end

View File

@@ -282,7 +282,7 @@ module Chalk
end
def to_bin(table, stack, index)
return 0x2000 | (table[name]?.as(Compiler::FunctionEntry).addr * 2 + 0x200)
return 0x2000 | (table.get_function?(name).as(Compiler::FunctionEntry).addr * 2 + 0x200)
end
end
@@ -297,6 +297,21 @@ module Chalk
end
end
class SetISpriteInstruction < Instruction
getter name
def initialize(@name : String)
end
def to_s(io)
io << "setispr " << @name
end
def to_bin(table, stack, index)
return 0xa000 | (table.get_sprite?(@name).not_nil!.addr + 0x200)
end
end
# Instruction to add a register to I.
class AddIRegInstruction < Instruction
def initialize(@reg : Int32)

View File

@@ -7,6 +7,7 @@ module Chalk
Any,
Str,
Id,
SpriteRow,
LitDec,
LitBin,
LitHex,
@@ -54,6 +55,8 @@ module Chalk
TokenType::Str.value)
@lexer.add_pattern("[a-zA-Z_][a-zA-Z_0-9]*",
TokenType::Id.value)
@lexer.add_pattern("`[ x]*`",
TokenType::SpriteRow.value)
@lexer.add_pattern("[0-9]+",
TokenType::LitDec.value)
@lexer.add_pattern("0b[0-1]+",

View File

@@ -6,6 +6,20 @@ module Chalk
class Parser
include ParserBuilder
private def create_sprite
type(Compiler::TokenType::KwSprite)
.then(type(Compiler::TokenType::Id))
.then(char('['))
.then(many(type(Compiler::TokenType::SpriteRow)))
.then(char(']'))
.transform do |array|
array = array.flatten
name = array[1].string
sprite = Compiler::Sprite.new(array[3..array.size - 2].map &.string)
Trees::TreeSprite.new(name, sprite).as(Trees::Tree)
end
end
# Creates a parser for a type.
private def create_type
either(type(Compiler::TokenType::KwU0),
@@ -185,14 +199,14 @@ module Chalk
Compiler::TokenType::KwU8 => Compiler::Type::U8,
Compiler::TokenType::KwU12 => Compiler::Type::U12
}
Trees::TreeFunction.new(name, params, table[type], code)
Trees::TreeFunction.new(name, params, table[type], code).as(Trees::Tree)
end
return func
end
def initialize
_, block = create_statement_block
@parser = many(create_func(block, create_type)).as(BasicParser(Array(Trees::TreeFunction)))
@parser = many(either(create_func(block, create_type), create_sprite)).as(BasicParser(Array(Trees::Tree)))
end
# Parses the given tokens into a tree.

97
src/chalk/sprite.cr Normal file
View File

@@ -0,0 +1,97 @@
module Chalk
module Compiler
class Sprite
def initialize
@pixels = Hash(UInt8, UInt8).new(default_value: 0_u8)
end
def height
return (@pixels.keys.max || 0_u8) + 1
end
def initialize(string, blank_char = ' ')
@pixels = Hash(UInt8, UInt8).new(default_value: 0_u8)
string.split("\n").each_with_index do |s, i|
break if i > 15
index = 0
byte = 0_u8
s.each_char do |char|
break if index > 7
bit = (char == blank_char) ? 0_u8 : 1_u8
byte |= (bit << (7 - index))
index += 1
end
@pixels[i.to_u8] = byte
end
end
def initialize(tokens)
raise "Invalid sprite" if tokens.size > 15
@pixels = Hash(UInt8, UInt8).new(default_value: 0_u8)
tokens.each_with_index do |token, i|
byte = 0_u8
substring = token[1..token.size - 2]
raise "Invalid sprite" if substring.size > 8
bit = 0b10000000
substring.each_char do |char|
byte |= bit if char == 'x'
bit >>= 1
end
@pixels[i.to_u8] = byte
end
end
def set_pixel(x, y)
raise "Invalid x-coordinate" if x > 7
raise "Invalid y-coordinate" if y > 15
x = x.to_u8
y = y.to_u8
char = @pixels.fetch y, 0_u8
char |= (1 << (7 - x))
@pixels[y] = char
end
def unset_pixel(x, y)
raise "Invalid x-coordinate" if x > 7
raise "Invalid y-coordinate" if y > 15
x = x.to_u8
y = y.to_u8
char = @pixels.fetch y, 0_u8
char &= ~(1 << (7 - x))
@pixels[y] = char
end
def toggle_pixel(x, y)
raise "Invalid x-coordinate" if x > 7
raise "Invalid y-coordinate" if y > 15
x = x.to_u8
y = y.to_u8
char = @pixels.fetch y, 0_u8
char ^= (1 << (7 - x))
@pixels[y] = char
end
def draw(io = STDOUT, blank_char = ' ', ink_char = 'x')
until_y = @pixels.keys.max?
return unless until_y
(0..until_y).each do |y|
row = @pixels.fetch y, 0_u8
pointer = 0b10000000
while pointer != 0
draw_pixel = (row & pointer) != 0
io << (draw_pixel ? ink_char : blank_char)
pointer = pointer >> 1
end
io << '\n'
end
end
def encode
if until_y = @pixels.keys.max?
return (0..until_y).map { |it| @pixels.fetch it, 0_u8 }
end
return [0_u8]
end
end
end
end

View File

@@ -1,11 +1,9 @@
require "./sprite.cr"
module Chalk
module Compiler
# An entry in the symbol table.
class Entry
end
# An entry that represents a function in the symbol table.
class FunctionEntry < Entry
class FunctionEntry
# Gets the function stored in this entry.
getter function
# Gets the address in code of this function.
@@ -23,7 +21,7 @@ module Chalk
end
# An entry that represents a variable in the symbol table.
class VarEntry < Entry
class VarEntry
# Gets the register occupied by the variable
# in this entry.
getter register
@@ -36,32 +34,47 @@ module Chalk
end
end
class SpriteEntry
property sprite : Sprite
property addr : Int32
def initialize(@sprite, @addr = -1)
end
end
# A symbol table.
class Table
# Gets the parent of this table.
getter parent
# Gets the functions hash.
getter functions
# Gets the variables hash.
getter vars
# Gets the sprites hash.
getter sprites
def initialize(@parent : Table? = nil)
@data = {} of String => Entry
@functions = {} of String => FunctionEntry
@vars = {} of String => VarEntry
@sprites = {} of String => SpriteEntry
end
# Looks up the given *key* first in this table,
# then in its parent, continuing recursively.
def []?(key)
if entry = @data[key]?
return entry
end
return @parent.try &.[key]?
macro table_functions(name)
def get_{{name}}?(key)
@{{name}}s[key]? || @parent.try &.get_{{name}}?(key)
end
def set_{{name}}(key, value)
@{{name}}s[key] = value
end
end
# Stores an *entry* under the given *key* into this table.
def []=(key, entry)
@data[key] = entry
end
table_functions function
table_functions var
table_functions sprite
def to_s(io)
@parent.try &.to_s(io)
io << @data.map { |k, v| k + ": " + v.to_s }.join("\n")
end
end
end

View File

@@ -296,5 +296,13 @@ module Chalk
r.reduce(self, [@rvalue.reduce(r)])
end
end
class TreeSprite < Tree
property name : String
property sprite : Compiler::Sprite
def initialize(@name, @sprite)
end
end
end
end

View File

@@ -12,8 +12,8 @@ module Chalk
end
def reduce(t : TreeCall, children)
entry = @table[t.name]?
raise "Unknwon function" unless entry && entry.is_a?(Compiler::FunctionEntry)
entry = @table.get_function? t.name
raise "Unknwon function" unless entry
type = entry.function.type
raise "Invalid parameters" if type.param_types.size != children.size
children.each_with_index do |child, i|

25
src/test.cr Normal file
View File

@@ -0,0 +1,25 @@
require "./chalk/*"
module Chalk
regex = /([^.]+)\.chalk/
source_dir = "programs"
dest_dir = "out"
Dir.mkdir_p dest_dir
exit if !File.directory? source_dir
Dir.new(source_dir)
.children
.compact_map { |it| regex.match(it) }
.each do |match|
config = Ui::Config.new file: (source_dir + File::SEPARATOR + match[0]),
output: (dest_dir + File::SEPARATOR + match[1] + ".ch8"),
loglevel: Logger::Severity::ERROR,
mode: Ui::OutputMode::Binary
compiler = Compiler::Compiler.new config
begin
compiler.run
rescue e
puts "Exception compiling #{match[0]}"
end
end
end