salt 0.0.2

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.
@@ -0,0 +1,143 @@
1
+ require 'forwardable'
2
+ require 'salt/scanner/main'
3
+ require 'salt/scanner/token'
4
+
5
+ module Salt
6
+ # Scans a given input.
7
+ #
8
+ # @see http://ruby-doc.org/stdlib-2.1.2/libdoc/strscan/rdoc/StringScanner.html
9
+ class Scanner
10
+ extend Forwardable
11
+ include Main
12
+
13
+ # An array of the tokens that the scanner scanned.
14
+ #
15
+ # @return [Array<Token>]
16
+ attr_reader :tokens
17
+
18
+ # Scans a file. It returns the tokens resulting from scanning.
19
+ #
20
+ # @param source [String] the source to scan. This should be
21
+ # compatible with StringScanner.
22
+ # @param name [String] the name of the source file. This is
23
+ # primarily used in backtrace information.
24
+ # @return [Array<Token>]
25
+ # @see #tokens
26
+ def self.scan(source, name = '(file)')
27
+ new(source, name).scan_file
28
+ end
29
+
30
+ # Initialize the scanner with the input.
31
+ #
32
+ # @param input [String] The source to scan.
33
+ # @param source [String] the source file. This is primarily
34
+ # used in backtrace information.
35
+ def initialize(input, source = '(file)')
36
+ @source = source
37
+ @scanner = StringScanner.new(input)
38
+ @tokens = []
39
+ end
40
+
41
+ # Scans the file in parts.
42
+ #
43
+ # @raise [SyntaxError] if the source is malformed in some way.
44
+ # @return [Array<Token>] the tokens that
45
+ # were scanned in this file.
46
+ # @see #scan_first_part
47
+ # @see #scan_second_part
48
+ # @see #scan_third_part
49
+ # @see #tokens
50
+ def scan_file
51
+ @line = 1
52
+ scan
53
+ tokens
54
+ rescue SyntaxError => e
55
+ start = [@scanner.pos - 8, 0].max
56
+ stop = [@scanner.pos + 8, @scanner.string.length].min
57
+ snip = @scanner.string[start..stop].strip.inspect
58
+ char = @scanner.string[@scanner.pos]
59
+ char = if char
60
+ char.inspect
61
+ else
62
+ 'EOF'
63
+ end
64
+
65
+ new_line = "#{@source}:#{@line}: unexpected #{char} " \
66
+ "(near #{snip})"
67
+
68
+ raise e, e.message, [new_line, *e.backtrace]
69
+ end
70
+
71
+ # Scans for whitespace. If the next character is whitespace, it
72
+ # will consume all whitespace until the next non-whitespace
73
+ # character.
74
+ #
75
+ # @return [Boolean] if any whitespace was matched.
76
+ def scan_whitespace
77
+ @line += @scanner[1].count("\n") if @scanner.scan(/(\s+)/)
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :line
83
+
84
+ def column
85
+ @scanner.pos - (@scanner.string.rindex(/\n|\r/, @scanner.pos) || 0)
86
+ end
87
+
88
+ # Raises an error.
89
+ #
90
+ # @raise [SyntaxError] always.
91
+ # @return [void]
92
+ def error!
93
+ raise SyntaxError, 'invalid syntax'
94
+ end
95
+
96
+ # Matches using the scanner. If it matches, it returns a truthy
97
+ # value. If it fails to match, it returns nil.
98
+ #
99
+ # @param scan [String, Symbol, Regexp] The match. If it is a
100
+ # string or a symbol, it is turned into a regular expression
101
+ # through interpolation.
102
+ # @return [Boolean, nil]
103
+ def on(scan)
104
+ case scan
105
+ when String, Symbol
106
+ @scanner.scan(/#{Regexp.escape(scan.to_s)}/)
107
+ when Regexp
108
+ @scanner.scan(scan)
109
+ else
110
+ raise ArgumentError, "Unexpected #{scan.class}, expected " \
111
+ 'String, Symbol, or Regexp'
112
+ end
113
+ end
114
+
115
+ # Generates a token with the given name and values.
116
+ #
117
+ # @param name [Symbol] the name of the token.
118
+ # @param values [Object] the values to be part of the token.
119
+ # @return [Token]
120
+ def token(name, *values)
121
+ Token.new(name, values, line, column)
122
+ end
123
+
124
+ # Emits a token, by first generating the token, and then
125
+ # appending it to the token array.
126
+ #
127
+ # @see #token
128
+ # @param (see #token)
129
+ # @return [Array<Token>] The token array.
130
+ def emit(name, *values)
131
+ tokens << token(name, *values)
132
+ end
133
+
134
+ # The group from the last match. It responds to #[] only.
135
+ #
136
+ # @return [#[]]
137
+ def group
138
+ @scanner
139
+ end
140
+
141
+ def_delegators :@scanner, :eos?
142
+ end
143
+ end
@@ -0,0 +1,134 @@
1
+ module Salt
2
+ class Scanner
3
+ module Main
4
+ NUMBER = %{
5
+ (
6
+ [0-9]+
7
+ | 0[xX][0-9a-f]+
8
+ | 0b[01]+
9
+ )
10
+ }
11
+
12
+ DOUBLE = %{([0-9]+\.[0-9]+)}
13
+
14
+ def scan
15
+ until eos?
16
+ scan_keyword || scan_grouping || scan_logimath ||
17
+ scan_control || scan_string || scan_number ||
18
+ scan_identifier || scan_comment || scan_whitespace ||
19
+ error!
20
+ end
21
+ end
22
+
23
+ def scan_keyword
24
+ case
25
+ when on(:function) then emit(:function)
26
+ when on(:func) then emit(:func)
27
+ when on(:class) then emit(:class)
28
+ when on(:struct) then emit(:struct)
29
+ when on(:union) then emit(:union)
30
+ when on(:continue) then emit(:continue)
31
+ when on(:break) then emit(:break)
32
+ when on(:if) then emit(:if)
33
+ when on(:else) then emit(:else)
34
+ when on(:for) then emit(:for)
35
+ when on(:while) then emit(:while)
36
+ when on(:do) then emit(:do)
37
+ when on(:goto) then emit(:goto)
38
+ when on(:return) then emit(:return)
39
+ when on(:typedef) then emit(:typedef)
40
+ when on(:label) then emit(:label)
41
+ end
42
+ end
43
+
44
+ def scan_grouping
45
+ case
46
+ when on('{') then emit(:lbrace)
47
+ when on('}') then emit(:rbrace)
48
+ when on('(') then emit(:lparen)
49
+ when on(')') then emit(:rparen)
50
+ when on('[') then emit(:lbrack)
51
+ when on(']') then emit(:rbrack)
52
+ end
53
+ end
54
+
55
+ def scan_logimath
56
+ case
57
+ when on('++') then emit(:dplus)
58
+ when on('--') then emit(:dminus)
59
+ when on('<<') then emit(:lshift)
60
+ when on('>>') then emit(:rshift)
61
+ when on('==') then emit(:comp)
62
+ when on('!=') then emit(:ncmp)
63
+ when on('&&') then emit(:land)
64
+ when on('||') then emit(:lor)
65
+ when on('=') then emit(:equals)
66
+ when on('+') then emit(:plus)
67
+ when on('-') then emit(:minus)
68
+ when on('/') then emit(:divide)
69
+ when on('%') then emit(:mod)
70
+ when on('&') then emit(:amber)
71
+ when on('^') then emit(:caret)
72
+ when on('*') then emit(:star)
73
+ when on('<') then emit(:lbrk)
74
+ when on('>') then emit(:rbrk)
75
+ when on('!') then emit(:lnot)
76
+ when on('~') then emit(:bnot)
77
+ when on('|') then emit(:bor)
78
+ end
79
+ end
80
+
81
+ def scan_control
82
+ case
83
+ when on('::') then emit(:dcolon)
84
+ when on(':') then emit(:colon)
85
+ when on(';') then emit(:semicolon)
86
+ when on('#') then emit(:hash)
87
+ when on(',') then emit(:comma)
88
+ end
89
+ end
90
+
91
+ def scan_string
92
+ case
93
+ when on(/\" ([^\"] | \\")* \"/)
94
+ emit(:string, group[1])
95
+ when on(/'(.)'/)
96
+ emit(:character, group[1])
97
+ end
98
+ end
99
+
100
+ def scan_number
101
+ case
102
+ when on(/#{NUMBER}(ull|llu)/x)
103
+ emit(:ullnumber, group[1])
104
+ when on(/#{NUMBER}(ul|lu)/x)
105
+ emit(:ulnumber, group[1])
106
+ when on(/#{NUMBER}(su|us)/x)
107
+ emit(:usnumber, group[1])
108
+ when on(/#{NUMBER}ll/x)
109
+ emit(:llnumber, group[1])
110
+ when on(/#{NUMBER}l/x)
111
+ emit(:lnumber, group[1])
112
+ when on(/#{NUMBER}s/x)
113
+ emit(:snumber, group[1])
114
+ when on(/#{NUMBER}u/x)
115
+ emit(:unumber, group[1])
116
+ when on(/#{NUMBER}/x)
117
+ emit(:number, group[1])
118
+ when on(/#{DOUBLE}f/)
119
+ emit(:float, group[1])
120
+ when on(DOUBLE)
121
+ emit(:double, group[1])
122
+ end
123
+ end
124
+
125
+ def scan_identifier
126
+ emit(:identifier, group[1]) if on(/([A-Za-z][A-Za-z0-9_]*)/)
127
+ end
128
+
129
+ def scan_comment
130
+ on(%r{//(.*?)\n})
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,45 @@
1
+ require 'forwardable'
2
+
3
+ module Salt
4
+ class Scanner
5
+ class Token
6
+ attr_reader :type
7
+ attr_reader :values
8
+ attr_reader :line
9
+ attr_reader :column
10
+ extend Forwardable
11
+ include Comparable
12
+
13
+ def_delegators :to_a, :[]
14
+
15
+ def initialize(type, values, line = 0, column = 0)
16
+ @type = type.to_s.upcase.intern
17
+ @values = values
18
+ @line = line
19
+ @column = column
20
+ @array = [@type].concat(@values.clone.freeze).freeze
21
+ freeze
22
+ end
23
+
24
+ def to_a
25
+ @array
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class}(#{self.type}) #{self.values.inspect}>"
30
+ end
31
+
32
+ def <=>(other)
33
+ case other
34
+ when Token
35
+ to_a <=> other.to_a
36
+ when Array
37
+ to_a <=> other
38
+ else
39
+ raise ArgumentError,
40
+ "Don't know how to compare Token with #{other}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Salt
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pry'
4
+
5
+ module Salt
6
+ # A class that is used to help walk the AST generated by Salt (or
7
+ # even any AST that uses arrays to define Nodes and Tokens).
8
+ class Walker
9
+ attr_reader :ast
10
+
11
+ def initialize(ast)
12
+ @ast = ast
13
+ @stack = []
14
+ end
15
+
16
+ def perform
17
+ to([@ast])
18
+ until @stack.empty?
19
+ yield self
20
+ @stack.shift
21
+ end
22
+ end
23
+
24
+ def on(action)
25
+ if action.is_a?(Hash) && action.key?(current_type)
26
+ action[current_type].each do |index|
27
+ to(current[index])
28
+ end
29
+ elsif !action.is_a?(Hash)
30
+ yield current[1..-1] if current_type == action
31
+ end
32
+ end
33
+
34
+ def to(sub)
35
+ return unless sub
36
+ @stack.concat(sub)
37
+ end
38
+
39
+ def current
40
+ @stack.first
41
+ end
42
+
43
+ def current_type
44
+ current[0]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'salt/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'salt'
8
+ spec.version = Salt::VERSION
9
+ spec.authors = ['Jeremy Rodi']
10
+ spec.email = ['redjazz96@gmail.com']
11
+ spec.summary = 'A sane alternative language to C.'
12
+ spec.description = 'A sane alternative language to C. It ' \
13
+ 'compiles directly to C, which then compiles.'
14
+ spec.homepage = 'https://github.com/medcat/salt'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(/^spec\//)
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.6'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.1'
25
+ spec.add_development_dependency 'rubocop'
26
+ spec.add_development_dependency 'rubocop-rspec'
27
+
28
+ spec.add_dependency 'toml-rb', '~> 0.3'
29
+ spec.add_dependency 'commander'
30
+ end
@@ -0,0 +1,26 @@
1
+ RSpec.describe Salt::Language::Scanner do
2
+ let(:input) { %{function main(): int { return 0; } } }
3
+ let(:source) { 'test.sa' }
4
+ subject { described_class.scan(input, source) }
5
+
6
+ it 'generates tokens' do
7
+ expect(subject.any?).to be true
8
+ expect(subject.map(&:class).
9
+ all? { |_| _ == Salt::Language::Scanner::Token }).to be true
10
+ end
11
+
12
+ it 'properly tokenizes' do
13
+ expect(subject.map(&:to_a)).to eq [
14
+ [:FUNCTION], [:IDENTIFIER, 'main'], [:LPAREN], [:RPAREN],
15
+ [:COLON], [:IDENTIFIER, 'int'],
16
+ [:LBRACE], [:RETURN], [:NUMBER, '0'], [:SEMICOLON], [:RBRACE]
17
+ ]
18
+ end
19
+
20
+ it 'adds line and column information' do
21
+ expect(subject.map { |_| [_.line, _.column] }).to eq [
22
+ [1, 0], [1, 9], [1, 13], [1, 14], [1, 15], [1, 17], [1, 21],
23
+ [1, 23], [1, 30], [1, 31], [1, 33]
24
+ ]
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ require 'salt'
4
+ require 'timeout'
5
+ require 'benchmark'
6
+
7
+ RSpec.configure do |config|
8
+ config.expect_with :rspec do |expectations|
9
+ # This option will default to `true` in RSpec 4. It makes the `description`
10
+ # and `failure_message` of custom matchers include text for helper methods
11
+ # defined using `chain`, e.g.:
12
+ # be_bigger_than(2).and_smaller_than(4).description
13
+ # # => "be bigger than 2 and smaller than 4"
14
+ # ...rather than:
15
+ # # => "be bigger than 2"
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ expectations.syntax = :expect
18
+ end
19
+
20
+ config.mock_with :rspec do |mocks|
21
+ # Prevents you from mocking or stubbing a method that does not exist on
22
+ # a real object. This is generally recommended, and will default to
23
+ # `true` in RSpec 4.
24
+ mocks.verify_partial_doubles = true
25
+ end
26
+
27
+ # These two settings work together to allow you to limit a spec run
28
+ # to individual examples or groups you care about by tagging them with
29
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
30
+ # get run.
31
+ config.filter_run :focus
32
+ config.run_all_when_everything_filtered = true
33
+
34
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
35
+ # For more details, see:
36
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
37
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
38
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
39
+ config.disable_monkey_patching!
40
+
41
+ # This setting enables warnings. It's recommended, but in some cases may
42
+ # be too noisy due to issues in dependencies.
43
+ config.warnings = true
44
+
45
+ # Many RSpec users commonly either run the entire suite or an individual
46
+ # file, and it's useful to allow more verbose output when running an
47
+ # individual spec file.
48
+ if config.files_to_run.one?
49
+ # Use the documentation formatter for detailed output,
50
+ # unless a formatter has already been configured
51
+ # (e.g. via a command-line flag).
52
+ config.default_formatter = 'doc'
53
+ end
54
+
55
+ # Print the 10 slowest examples and example groups at the
56
+ # end of the spec run, to help surface which specs are running
57
+ # particularly slow.
58
+ # config.profile_examples = 10
59
+
60
+ # Run specs in random order to surface order dependencies. If you find an
61
+ # order dependency and want to debug it, you can fix the order by providing
62
+ # the seed, which is printed after each run.
63
+ # --seed 1234
64
+ config.order = :random
65
+
66
+ # Seed global randomization in this process using the `--seed` CLI option.
67
+ # Setting this allows you to use `--seed` to deterministically reproduce
68
+ # test failures related to randomization by passing the same `--seed` value
69
+ # as the one that triggered the failure.
70
+ Kernel.srand config.seed
71
+ end