salt 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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