Compare commits
5 Commits
b79d717f1a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bc798f11fe | |||
| d40ea2a8ae | |||
| eb32577d38 | |||
| e740fd7688 | |||
| 5ce0d41a53 |
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
|
||||
|
||||
@@ -2,8 +2,11 @@ sprite dum [
|
||||
` x x `
|
||||
` x x `
|
||||
` x x `
|
||||
` `
|
||||
`x x`
|
||||
` xxxxxx `
|
||||
]
|
||||
|
||||
fun main(): u0 {
|
||||
|
||||
draw_sprite(dum, 0, 0);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,8 @@ module Chalk
|
||||
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
|
||||
|
||||
@@ -80,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*
|
||||
@@ -180,15 +182,6 @@ module Chalk
|
||||
names = collect_calls(table)
|
||||
names.delete "main"
|
||||
|
||||
sprite_bytes = [] of UInt8
|
||||
offset = 0
|
||||
table.sprites.each do |k, v|
|
||||
data = v.sprite.encode
|
||||
v.offset = offset
|
||||
offset += data.size
|
||||
sprite_bytes.concat data
|
||||
end
|
||||
|
||||
main_entry = table.get_function?("main").not_nil!
|
||||
all_instructions.concat create_code(main_entry.function.as(Trees::TreeFunction),
|
||||
table, Ir::JumpRelativeInstruction.new 0)
|
||||
@@ -203,9 +196,19 @@ 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, binary)
|
||||
binary.concat sprite_bytes
|
||||
binary.each do |byte|
|
||||
file.write_byte byte
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,6 +5,10 @@ module Chalk
|
||||
@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|
|
||||
|
||||
@@ -36,9 +36,9 @@ module Chalk
|
||||
|
||||
class SpriteEntry
|
||||
property sprite : Sprite
|
||||
property offset : Int32
|
||||
property addr : Int32
|
||||
|
||||
def initialize(@sprite, @offset = -1)
|
||||
def initialize(@sprite, @addr = -1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user