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 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