base-lang 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -20
- data/bin/base +27 -2
- data/lib/base/compiler.rb +16 -13
- data/lib/base/program.rb +49 -0
- data/lib/base/version.rb +3 -0
- data/lib/base/vm.rb +12 -3
- data/lib/base.rb +2 -0
- data/sample/alphabet.base +30 -0
- data/sample/int_to_string.base +95 -0
- data/sample/simple.base +33 -0
- data/sample/subroutine_macro.base +60 -0
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 479899f8818cfda3f541a9cbfda0ed3251359f660a527255054f8263c77152d8
|
4
|
+
data.tar.gz: 047b1a3051f3e1d4a63ab30c9c8d92c1800a0dd957ece661a1cee928a45fb630
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 839ea8f044ef774895405c57dda3698bedb5e71a4922277d6ceefe1e1de597efd73e9989d8748159d371d5e42d3d20cc25cfb6ffe922242e6ee85c0f9e905a4b
|
7
|
+
data.tar.gz: 837e462241f9238ca3fb7f051976f925aef26e3087bcc0bcbbf7c0a5cc5c6e7072a29bf6653f6516efcb4ab89ad3b91d9e342ec1883c160fc186a05ad9241b37
|
data/README.md
CHANGED
@@ -49,28 +49,29 @@ Run it:
|
|
49
49
|
|
50
50
|
All instructions take no arguments except the `push` instruction which takes the value to push to the stack.
|
51
51
|
|
52
|
-
Values may be an integer, a single character in double quotes, a label, or the special text `ip` which refers to
|
53
|
-
the current instruction pointer.
|
52
|
+
Values may be an integer, a single character in double quotes which is interpreted as its unicode number, a label representing a memory location, or the special text `ip` which refers to the current instruction pointer.
|
54
53
|
|
55
|
-
Signed integers of arbitrary size can be used. Memory locations start at 0 and by default
|
54
|
+
Signed integers of arbitrary size can be used. Memory locations start at 0 and by default go up to 1MB.
|
56
55
|
|
57
56
|
operation | op code | stack impact | description
|
58
57
|
-|-|-|-
|
59
58
|
debug | 0 | | enters the debugger
|
60
59
|
push (value or label) | 1, value | + | pushes the value onto the stack
|
61
60
|
discard | 2 | - | discards the top entry on the stack
|
62
|
-
duplicate | 3 |
|
61
|
+
duplicate | 3 | -++ | pushes two copies of the top entry on the stack
|
63
62
|
write | 4 | -- | writes the second entry on the stack to the memory location at the top entry on the stack
|
64
63
|
read | 5 | -+ | reads from the memory location on the stack and puts the result on the stack
|
65
64
|
add | 6 | --+ | adds the top two entries on the stack and puts the result on the stack
|
66
65
|
subtract | 7 | --+ | subtracts the top entry on the stack from the second entry on the stack and puts the result on the stack
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
multiply | 8 | --+ | multiplies the top two entries on the stack and puts the result on the stack
|
67
|
+
divide | 9 | --+ | divides the top entry on the stack from the second entry on the stack and puts the integer result on the stack
|
68
|
+
jump | 10 | - | jumps to the location indicated by the top entry on the stack
|
69
|
+
bltz | 11 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is less than 0
|
70
|
+
bgtz | 12 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is greater than 0
|
71
|
+
betz | 13 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is equal to 0
|
72
|
+
bnetz | 14 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is not equal to 0
|
73
|
+
out | 15 | - | outputs the top entry on the stack to stdout, interpreted as a unicode character
|
74
|
+
halt | 16 | | halts the program
|
74
75
|
|
75
76
|
## Compiler
|
76
77
|
|
@@ -82,8 +83,8 @@ You can use labels to mark a place in the code:
|
|
82
83
|
|
83
84
|
# infinite loop
|
84
85
|
.marker
|
85
|
-
|
86
|
-
|
86
|
+
push marker
|
87
|
+
jump
|
87
88
|
|
88
89
|
`.main` is a special label. If specified, code execution will start at this point.
|
89
90
|
|
@@ -99,11 +100,11 @@ The data can also be a string, which is interpreted as a list of bytes, or a mix
|
|
99
100
|
|
100
101
|
Simple macros can be defined as a comma-separated list of operations (or other macros, as long as they don't recurse):
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
macro increment push 1, add
|
104
|
+
|
105
|
+
push "A"
|
106
|
+
increment
|
107
|
+
out # outputs a "B"
|
107
108
|
|
108
109
|
You'll need to define a macro before you use it in your file.
|
109
110
|
|
@@ -120,5 +121,5 @@ When in the debugger, type `h` for help.
|
|
120
121
|
|
121
122
|
MIT licence.
|
122
123
|
|
123
|
-
Feel free to contribute! It's intentionally simple,
|
124
|
-
there.
|
124
|
+
Feel free to contribute! It's intentionally simple, and as a result it's unlikely we'll add more instructions or syntactic
|
125
|
+
sugar than what's already there.
|
data/bin/base
CHANGED
@@ -5,8 +5,13 @@ require_relative "../lib/base"
|
|
5
5
|
|
6
6
|
options = {}
|
7
7
|
OptionParser.new do |parser|
|
8
|
+
parser.version = Base::VERSION
|
8
9
|
parser.banner = "Usage: base [options] filename.base"
|
9
10
|
|
11
|
+
parser.on("-b", "--bytecode", "Input file is compiled bytecode") do
|
12
|
+
options[:bytecode] = true
|
13
|
+
end
|
14
|
+
|
10
15
|
parser.on("-d", "--debug", "Start debugger immediately") do
|
11
16
|
options[:debug] = true
|
12
17
|
end
|
@@ -19,6 +24,11 @@ OptionParser.new do |parser|
|
|
19
24
|
puts parser
|
20
25
|
exit 1
|
21
26
|
end
|
27
|
+
|
28
|
+
parser.on_tail("--version", "Show version") do
|
29
|
+
puts "base #{Base::VERSION}"
|
30
|
+
exit
|
31
|
+
end
|
22
32
|
end.parse!
|
23
33
|
|
24
34
|
if ARGV.length != 1
|
@@ -26,11 +36,26 @@ if ARGV.length != 1
|
|
26
36
|
exit 1
|
27
37
|
end
|
28
38
|
|
39
|
+
def file_or_stdin(file)
|
40
|
+
if file == "-"
|
41
|
+
$stdin.read
|
42
|
+
else
|
43
|
+
File.read(file)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
29
47
|
begin
|
30
|
-
program = Base::
|
48
|
+
program = Base::Program.new
|
49
|
+
|
50
|
+
if options[:bytecode]
|
51
|
+
program.decode(file_or_stdin(ARGV[0]))
|
52
|
+
else
|
53
|
+
compiler = Base::Compiler.new(program)
|
54
|
+
compiler.compile(file_or_stdin(ARGV[0]))
|
55
|
+
end
|
31
56
|
|
32
57
|
if options[:compile]
|
33
|
-
|
58
|
+
print program.encode
|
34
59
|
else
|
35
60
|
Base::VM.new(program, debug: options[:debug] || false).run
|
36
61
|
end
|
data/lib/base/compiler.rb
CHANGED
@@ -2,23 +2,26 @@ module Base
|
|
2
2
|
class Compiler
|
3
3
|
Error = Class.new(StandardError)
|
4
4
|
|
5
|
-
def initialize
|
5
|
+
def initialize(program)
|
6
|
+
@program = program
|
6
7
|
@parsing = []
|
7
|
-
@memory = []
|
8
8
|
@labels = {}
|
9
9
|
@macros = {}
|
10
10
|
end
|
11
11
|
|
12
|
-
def compile(
|
13
|
-
lines =
|
12
|
+
def compile(data)
|
13
|
+
lines = data.split("\n")
|
14
14
|
parse(lines)
|
15
|
-
|
16
|
-
|
17
|
-
[main] + @memory
|
15
|
+
@program.start_location = @labels["main"] || 0
|
16
|
+
true
|
18
17
|
end
|
19
18
|
|
20
19
|
private
|
21
20
|
|
21
|
+
def memory
|
22
|
+
@program.memory
|
23
|
+
end
|
24
|
+
|
22
25
|
def parse(lines)
|
23
26
|
lines.each.with_index do |line, index|
|
24
27
|
begin
|
@@ -30,7 +33,7 @@ module Base
|
|
30
33
|
|
31
34
|
@parsing.each do |index, line_number|
|
32
35
|
begin
|
33
|
-
|
36
|
+
memory[index] = parse_arg(memory[index], index)
|
34
37
|
rescue Error => e
|
35
38
|
raise Error, "#{e.message} on line #{line_number}"
|
36
39
|
end
|
@@ -46,12 +49,12 @@ module Base
|
|
46
49
|
when /\A\.([a-z_][a-z0-9_]*)(?:\s+(.+))?\z/i
|
47
50
|
raise Error, "label already defined" if @labels[$1]
|
48
51
|
raise Error, "can't use 'ip' as a label name" if $1 == 'ip'
|
49
|
-
@labels[$1] =
|
50
|
-
|
52
|
+
@labels[$1] = memory.length
|
53
|
+
memory.concat(parse_static($2)) if $2
|
51
54
|
|
52
55
|
when /\Apush\s+(.+)\z/
|
53
|
-
|
54
|
-
@parsing << [
|
56
|
+
memory.concat [VM::COMMANDS.index("push"), $1]
|
57
|
+
@parsing << [memory.length - 1, number]
|
55
58
|
|
56
59
|
when /\Amacro\s+(\S+)\s+(.+)/
|
57
60
|
raise Error, "label already defined" if @macros[$1]
|
@@ -60,7 +63,7 @@ module Base
|
|
60
63
|
|
61
64
|
else
|
62
65
|
if op_code = VM::COMMANDS.index(line)
|
63
|
-
|
66
|
+
memory << op_code
|
64
67
|
elsif macro = @macros[line]
|
65
68
|
if macro_entry.include?(macro)
|
66
69
|
raise Error, "recursive call to macro #{macro}"
|
data/lib/base/program.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Base
|
2
|
+
class Program
|
3
|
+
DecodeError = Class.new(StandardError)
|
4
|
+
|
5
|
+
attr_reader :memory
|
6
|
+
attr_accessor :start_location
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@start_location = 0
|
10
|
+
@memory = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode
|
14
|
+
bytecode = ['B'.ord, 'A'.ord, 'S'.ord, 'E'.ord, 2, start_location] + memory
|
15
|
+
|
16
|
+
bytecode.flat_map do |value|
|
17
|
+
bytes = []
|
18
|
+
while value >= 128
|
19
|
+
bytes << ((value & 127) | 128).chr
|
20
|
+
value >>= 7
|
21
|
+
end
|
22
|
+
bytes + [value.chr]
|
23
|
+
end.join
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode(data)
|
27
|
+
raise DecodeError, "invalid header signature" unless data[0..4] == "BASE\2"
|
28
|
+
|
29
|
+
bytecode = []
|
30
|
+
carry = 0
|
31
|
+
value = 0
|
32
|
+
data[5..-1].each_byte do |byte|
|
33
|
+
if byte & 128 == 128
|
34
|
+
value |= (byte & 127) << carry
|
35
|
+
carry += 7
|
36
|
+
else
|
37
|
+
bytecode << ((byte << carry) | value)
|
38
|
+
carry = value = 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
raise DecodeError, "unexpected end of bytecode" unless carry == 0
|
43
|
+
|
44
|
+
@start_location = bytecode[0]
|
45
|
+
@memory = bytecode[1..-1]
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/base/version.rb
ADDED
data/lib/base/vm.rb
CHANGED
@@ -6,11 +6,11 @@ module Base
|
|
6
6
|
|
7
7
|
attr_reader :ip, :stack, :memory
|
8
8
|
|
9
|
-
COMMANDS = %w(debug push discard duplicate write read add subtract jump bltz bgtz betz bnetz out halt)
|
9
|
+
COMMANDS = %w(debug push discard duplicate write read add subtract multiply divide jump bltz bgtz betz bnetz out halt)
|
10
10
|
|
11
11
|
def initialize(program, debug: false)
|
12
|
-
@ip = program.
|
13
|
-
@memory = program
|
12
|
+
@ip = program.start_location
|
13
|
+
@memory = program.memory
|
14
14
|
@debug = debug
|
15
15
|
@stack = Stack.new
|
16
16
|
end
|
@@ -71,6 +71,15 @@ module Base
|
|
71
71
|
a = stack.pop
|
72
72
|
b = stack.pop
|
73
73
|
stack.push(b - a)
|
74
|
+
when "multiply"
|
75
|
+
a = stack.pop
|
76
|
+
b = stack.pop
|
77
|
+
stack.push(b * a)
|
78
|
+
when "divide"
|
79
|
+
a = stack.pop
|
80
|
+
b = stack.pop
|
81
|
+
raise Error, "divide by 0" if a == 0
|
82
|
+
stack.push(b / a)
|
74
83
|
|
75
84
|
when "jump"
|
76
85
|
@ip = stack.pop - 1
|
data/lib/base.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
.main
|
2
|
+
push "A"
|
3
|
+
|
4
|
+
.loop
|
5
|
+
# Print letter to the screen
|
6
|
+
duplicate
|
7
|
+
out
|
8
|
+
|
9
|
+
# Check whether we've reached Z yet
|
10
|
+
duplicate
|
11
|
+
push "Z"
|
12
|
+
subtract
|
13
|
+
|
14
|
+
# If we have, we're done
|
15
|
+
push done
|
16
|
+
betz
|
17
|
+
|
18
|
+
# Advance to the next letter and loop
|
19
|
+
push 1
|
20
|
+
add
|
21
|
+
push loop
|
22
|
+
jump
|
23
|
+
|
24
|
+
.done
|
25
|
+
# Program ends
|
26
|
+
discard
|
27
|
+
push "\n"
|
28
|
+
out
|
29
|
+
halt
|
30
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
.fn_print_number
|
2
|
+
# number_buffer_ptr points to number_buffer
|
3
|
+
push number_buffer
|
4
|
+
push number_buffer_ptr
|
5
|
+
write
|
6
|
+
|
7
|
+
.dec_next_digit
|
8
|
+
duplicate
|
9
|
+
|
10
|
+
# divide by 10
|
11
|
+
push 10
|
12
|
+
divide
|
13
|
+
|
14
|
+
# write the result for next loop
|
15
|
+
duplicate
|
16
|
+
push next_number
|
17
|
+
write
|
18
|
+
|
19
|
+
# find the modulo
|
20
|
+
push 10
|
21
|
+
multiply
|
22
|
+
subtract
|
23
|
+
|
24
|
+
# convert to ASCII
|
25
|
+
push "0"
|
26
|
+
add
|
27
|
+
push number_buffer_ptr
|
28
|
+
read
|
29
|
+
write
|
30
|
+
|
31
|
+
# next_number becomes our new number
|
32
|
+
push next_number
|
33
|
+
read
|
34
|
+
|
35
|
+
# increment the pointer by 1
|
36
|
+
push number_buffer_ptr
|
37
|
+
read
|
38
|
+
push 1
|
39
|
+
add
|
40
|
+
push number_buffer_ptr
|
41
|
+
write
|
42
|
+
|
43
|
+
# process the next digit if we're not at 0
|
44
|
+
duplicate
|
45
|
+
push dec_next_digit
|
46
|
+
bgtz
|
47
|
+
|
48
|
+
.print_next_digit
|
49
|
+
# move the ptr back 1
|
50
|
+
push number_buffer_ptr
|
51
|
+
read
|
52
|
+
push 1
|
53
|
+
subtract
|
54
|
+
duplicate
|
55
|
+
push number_buffer_ptr
|
56
|
+
write
|
57
|
+
|
58
|
+
# read the ptr and print it out
|
59
|
+
read
|
60
|
+
out
|
61
|
+
|
62
|
+
# if we're not back at the start yet, loop
|
63
|
+
push number_buffer_ptr
|
64
|
+
read
|
65
|
+
push number_buffer
|
66
|
+
subtract
|
67
|
+
push print_next_digit
|
68
|
+
bgtz
|
69
|
+
|
70
|
+
# all done :)
|
71
|
+
discard
|
72
|
+
|
73
|
+
# return ip is on stack
|
74
|
+
jump
|
75
|
+
|
76
|
+
|
77
|
+
.main
|
78
|
+
# set return address
|
79
|
+
push ip
|
80
|
+
push 10
|
81
|
+
add
|
82
|
+
|
83
|
+
# call function
|
84
|
+
push 472
|
85
|
+
push fn_print_number
|
86
|
+
jump
|
87
|
+
|
88
|
+
# output newline
|
89
|
+
push "\n"
|
90
|
+
out
|
91
|
+
halt
|
92
|
+
|
93
|
+
.next_number 0
|
94
|
+
.number_buffer_ptr 0
|
95
|
+
.number_buffer 0 # following memory used for unsized buffer
|
data/sample/simple.base
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
.counter 10
|
2
|
+
|
3
|
+
.main
|
4
|
+
push "H"
|
5
|
+
out
|
6
|
+
push "e"
|
7
|
+
out
|
8
|
+
push "l"
|
9
|
+
out
|
10
|
+
push "l"
|
11
|
+
out
|
12
|
+
push "o"
|
13
|
+
out
|
14
|
+
push "\n"
|
15
|
+
out
|
16
|
+
|
17
|
+
# subtract 1 from value at label .counter
|
18
|
+
push counter
|
19
|
+
read
|
20
|
+
push 1
|
21
|
+
subtract
|
22
|
+
|
23
|
+
# write new value back to .counter, and keep the value in the stack
|
24
|
+
duplicate
|
25
|
+
push counter
|
26
|
+
write
|
27
|
+
|
28
|
+
# jump back to the start if we're not at 0 yet
|
29
|
+
push main
|
30
|
+
bgtz
|
31
|
+
|
32
|
+
# done
|
33
|
+
halt
|
@@ -0,0 +1,60 @@
|
|
1
|
+
.jsr_location 0
|
2
|
+
macro jsr push jsr_location, write, push ip, push 9, add, push jsr_location, read, jump
|
3
|
+
|
4
|
+
.main
|
5
|
+
push 10 # times around the loop
|
6
|
+
push counter
|
7
|
+
write
|
8
|
+
|
9
|
+
.loop
|
10
|
+
push message
|
11
|
+
push index
|
12
|
+
write
|
13
|
+
|
14
|
+
push subroutine
|
15
|
+
jsr
|
16
|
+
|
17
|
+
.inner
|
18
|
+
push index
|
19
|
+
read
|
20
|
+
read
|
21
|
+
duplicate
|
22
|
+
push done
|
23
|
+
betz
|
24
|
+
out
|
25
|
+
push index
|
26
|
+
read
|
27
|
+
push 1
|
28
|
+
add
|
29
|
+
push index
|
30
|
+
write
|
31
|
+
push inner
|
32
|
+
jump
|
33
|
+
|
34
|
+
.done
|
35
|
+
discard
|
36
|
+
push counter
|
37
|
+
read
|
38
|
+
push 1
|
39
|
+
subtract
|
40
|
+
duplicate
|
41
|
+
push counter
|
42
|
+
write
|
43
|
+
push loop
|
44
|
+
bgtz
|
45
|
+
|
46
|
+
halt
|
47
|
+
|
48
|
+
.subroutine
|
49
|
+
push "h"
|
50
|
+
out
|
51
|
+
push "i"
|
52
|
+
out
|
53
|
+
push "\n"
|
54
|
+
out
|
55
|
+
jump
|
56
|
+
|
57
|
+
|
58
|
+
.counter 0
|
59
|
+
.index 0
|
60
|
+
.message "Hello!\n", 0
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: base-lang
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mog Nesbitt
|
@@ -22,8 +22,14 @@ files:
|
|
22
22
|
- lib/base.rb
|
23
23
|
- lib/base/compiler.rb
|
24
24
|
- lib/base/debugger.rb
|
25
|
+
- lib/base/program.rb
|
25
26
|
- lib/base/stack.rb
|
27
|
+
- lib/base/version.rb
|
26
28
|
- lib/base/vm.rb
|
29
|
+
- sample/alphabet.base
|
30
|
+
- sample/int_to_string.base
|
31
|
+
- sample/simple.base
|
32
|
+
- sample/subroutine_macro.base
|
27
33
|
homepage: https://github.com/mogest/base-lang
|
28
34
|
licenses:
|
29
35
|
- MIT
|