Compare commits
16 Commits
2ed2a4932c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bc798f11fe | |||
| d40ea2a8ae | |||
| eb32577d38 | |||
| e740fd7688 | |||
| 5ce0d41a53 | |||
| b79d717f1a | |||
| 60c320dfea | |||
| 7fe5cec941 | |||
| 89709fef97 | |||
| 45f3e51890 | |||
| 61fb44bbce | |||
| 230a50c532 | |||
| a96d503095 | |||
| caaae87344 | |||
| 86ee6557cf | |||
| a7e24c059b |
56
README.md
56
README.md
@@ -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
|
||||
|
||||
7
programs/basic_arithmetic.chalk
Normal file
7
programs/basic_arithmetic.chalk
Normal 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
21
programs/basic_call.chalk
Normal 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();
|
||||
}
|
||||
3
programs/basic_draw_number.chalk
Normal file
3
programs/basic_draw_number.chalk
Normal file
@@ -0,0 +1,3 @@
|
||||
fun main(): u0 {
|
||||
draw_number(69, 1, 1);
|
||||
}
|
||||
3
programs/basic_empty.chalk
Normal file
3
programs/basic_empty.chalk
Normal file
@@ -0,0 +1,3 @@
|
||||
fun main(): u0 {
|
||||
|
||||
}
|
||||
17
programs/basic_if.chalk
Normal file
17
programs/basic_if.chalk
Normal 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;
|
||||
}
|
||||
}
|
||||
4
programs/basic_input.chalk
Normal file
4
programs/basic_input.chalk
Normal file
@@ -0,0 +1,4 @@
|
||||
fun main(): u0 {
|
||||
var a = get_key();
|
||||
var b = get_key();
|
||||
}
|
||||
12
programs/basic_sprite.chalk
Normal file
12
programs/basic_sprite.chalk
Normal file
@@ -0,0 +1,12 @@
|
||||
sprite dum [
|
||||
` x x `
|
||||
` x x `
|
||||
` x x `
|
||||
` `
|
||||
`x x`
|
||||
` xxxxxx `
|
||||
]
|
||||
|
||||
fun main(): u0 {
|
||||
draw_sprite(dum, 0, 0);
|
||||
}
|
||||
8
programs/basic_while.chalk
Normal file
8
programs/basic_while.chalk
Normal 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
14
programs/comb_fib.chalk
Normal 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
13
programs/comb_input.chalk
Normal 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);
|
||||
}
|
||||
}
|
||||
39
programs/comb_sprite.chalk
Normal file
39
programs/comb_sprite.chalk
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
programs/comb_sprite_2.chalk
Normal file
34
programs/comb_sprite_2.chalk
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
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, file)
|
||||
generate_binary(table, all_instructions, binary)
|
||||
binary.concat sprite_bytes
|
||||
binary.each do |byte|
|
||||
file.write_byte byte
|
||||
end
|
||||
file.close
|
||||
end
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]+",
|
||||
|
||||
@@ -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
97
src/chalk/sprite.cr
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
25
src/test.cr
Normal 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
|
||||
Reference in New Issue
Block a user