base-lang 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|