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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 352641028b34da47dec624f8cb3e135e6c336e47f854931649104b0c9a29629c
4
- data.tar.gz: 490c1fa4fd25ec278bf2dd12e67f0cecc1b53da7a3eee848daee81121ddbff4e
3
+ metadata.gz: 479899f8818cfda3f541a9cbfda0ed3251359f660a527255054f8263c77152d8
4
+ data.tar.gz: 047b1a3051f3e1d4a63ab30c9c8d92c1800a0dd957ece661a1cee928a45fb630
5
5
  SHA512:
6
- metadata.gz: b7d1e64864df4c093c56c70c2d212013c926082e213f8aad86ead4120e2f3cb0da5df3153402fa0a70da5716723a71163a1172a23a6a577cee4e23e5337725ed
7
- data.tar.gz: 054abcc979edead6e60520bba2926a7614899fe73e349f81418ac87b617829de6d196b98c85b7f8156fb27b727a3fc45c0641440a5d8cdbc1667b133be145d54
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 to up to 1MB (1048575).
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 | + | duplicates the top entry on the stack
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
- jump | 8 | - | jumps to the location indicated by the top entry on the stack
68
- bltz | 9 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is less than 0
69
- bgtz | 10 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is greater than 0
70
- betz | 11 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is equal to 0
71
- bnetz | 12 | -- | jumps to the location indicated by the top entry on the stack if the second entry on the stack is not equal to 0
72
- out | 13 | - | outputs the top entry on the stack to stdout, interpreted as a unicode character
73
- halt | 13 | | halts the program
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
- push marker
86
- jump
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
- macro increment push 1, add
103
-
104
- push "A"
105
- increment
106
- out # outputs a "B"
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, so the intention isn't to add any more operations than what is
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::Compiler.new.compile(ARGV[0])
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
- puts program.join(",")
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(file)
13
- lines = File.read(file).split("\n")
12
+ def compile(data)
13
+ lines = data.split("\n")
14
14
  parse(lines)
15
-
16
- main = @labels["main"] || 0
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
- @memory[index] = parse_arg(@memory[index], index)
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] = @memory.length
50
- @memory += parse_static($2) if $2
52
+ @labels[$1] = memory.length
53
+ memory.concat(parse_static($2)) if $2
51
54
 
52
55
  when /\Apush\s+(.+)\z/
53
- @memory += [VM::COMMANDS.index("push"), $1]
54
- @parsing << [@memory.length - 1, number]
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
- @memory << op_code
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}"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Base
2
+ VERSION = "0.2.0"
3
+ end
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.shift
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
@@ -1,3 +1,5 @@
1
+ require_relative 'base/version'
2
+ require_relative 'base/program'
1
3
  require_relative 'base/stack'
2
4
  require_relative 'base/compiler'
3
5
  require_relative 'base/vm'
@@ -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
@@ -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.1.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