mvinl 0.1.4 → 0.1.6

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: 2e1464b5085d8cd24dc9b43f22063e675a8f6d249a2c3d716e29367726032e16
4
- data.tar.gz: 0f8ee1fa140bf083afda23c1004e02bf3a8b3e0fcccd55f19d39135305b6b2dd
3
+ metadata.gz: 7c681f294e44a200c6fb8c66aed4ac1c50fce869c1c12f0a64b891b0ee720842
4
+ data.tar.gz: 94acd6d0220cb2fbfa05c839e8081e19b6d8ef3c3f4bd549dec217653b53fe0a
5
5
  SHA512:
6
- metadata.gz: 449b3f884f41165b9892fa70e9a776dbd7208261ad1d237189d252fcec7f09fe6688873324b8045b9a85b1fdf7e707591057f4e1e7bf899fa48170f8e2cbf9a4
7
- data.tar.gz: 0a09d81f07dcbc1c9253048526b3e60606b7a48c657f040c4a76e7d639d104b14148d5e2e640574429f4d1181eb85974543f74ec2b87077c29f3befe07aa2d76
6
+ metadata.gz: 2d2df258513050c3c8b982865df1e60e2d84aa253b09070f65ae0775d9a07f90231480130d5e9b645b0ee761460d66acf097a35e878894f7b30d3eda62ab23ed
7
+ data.tar.gz: 5eaa3496bef0fc23371cf449e12264873fa6455bff81f88c70c454a903b09b2860252feec4e07515d3eb37a637f2fdad7ffabfd1ad4dd5758e19844641cbf868
data/bin/imvnl CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen-string-literal: true
3
3
 
4
- =begin engine.rb
4
+ =begin imvnl
5
5
  Copyright (c) 2018, 2024, Daniel Sierpiński All rights reserved.
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -24,23 +24,28 @@ THE SOFTWARE.
24
24
  =end
25
25
 
26
26
  require 'readline'
27
- require 'pry'
27
+ require 'mvinl/context'
28
28
  require 'mvinl/lexer'
29
29
  require 'mvinl/parser'
30
30
  require 'mvinl/info'
31
31
 
32
- PSL = "mvnl(#{MVinl::Version})".freeze
33
- PS1 = "#{PSL}>".freeze
34
-
35
32
  module MVinl::REPL
36
- LEXER = MVinl::Lexer.new('')
37
- PARSER = MVinl::Parser.new(LEXER)
33
+ PSL = "mvnl(#{MVinl::Version})".freeze
34
+ PS1 = "#{PSL}>".freeze
35
+ HISTORY_FILE = File.expand_path('.imvnl_history')
36
+ CONTEXT = MVinl::Context.new
37
+ LEXER = MVinl::Lexer.new(CONTEXT, '')
38
+ PARSER = MVinl::Parser.new(LEXER, CONTEXT)
38
39
 
39
40
  def self.run
41
+ load_history
42
+
40
43
  while (input = Readline.readline("#{PS1} ", true))
41
- Readline::HISTORY.pop if input.strip.empty?
44
+ if Readline::HISTORY.size > 1 && Readline::HISTORY[-1] == Readline::HISTORY[-2]
45
+ Readline::HISTORY.pop
46
+ end
42
47
 
43
- if input == '^' || input == 'exit'
48
+ if ['^', 'exit'].include? input
44
49
  PARSER.finalize!
45
50
  break if PARSER.parsing_done?
46
51
  else
@@ -59,6 +64,22 @@ module MVinl::REPL
59
64
  warn e.message
60
65
  end
61
66
  end
67
+
68
+ save_history
69
+ end
70
+
71
+ private_class_method def self.load_history
72
+ return unless File.exist? HISTORY_FILE
73
+
74
+ File.foreach(HISTORY_FILE) do |line|
75
+ Readline::HISTORY << line.chomp
76
+ end
77
+ end
78
+
79
+ private_class_method def self.save_history
80
+ File.open(HISTORY_FILE, 'w') do |file|
81
+ Readline::HISTORY.each { |entry| file.puts(entry) }
82
+ end
62
83
  end
63
84
  end
64
85
 
@@ -0,0 +1,54 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'pry'
4
+
5
+ module MVinl
6
+ # Lexer and parser shared context
7
+ class Context
8
+ attr_accessor :variables, :functions, :state
9
+
10
+ RESERVED = %I[def]
11
+ CONSTANTS = {}
12
+
13
+ def initialize
14
+ reset
15
+ end
16
+
17
+ def reset
18
+ @variables = {}
19
+ @functions = {}
20
+ @state = {
21
+ in_prop: false,
22
+ in_var: false,
23
+ in_keyword_arg: false,
24
+ keyword_arg_depth: 0,
25
+ lines: 0,
26
+ depth: 0
27
+ }
28
+ end
29
+
30
+ def define_function(name, args, body)
31
+ @functions[name] = { args: args, body: body }
32
+ end
33
+
34
+ def define_constant(name, value)
35
+ if RESERVED.include? name
36
+ nil
37
+ elsif CONSTANTS[name]
38
+ false
39
+ else
40
+ @state[:in_var] = false
41
+ CONSTANTS[name] = value
42
+ end
43
+ end
44
+
45
+ def define_variable(name, value)
46
+ if RESERVED.include? name
47
+ nil
48
+ else
49
+ @state[:in_var] = false
50
+ @variables[name] = value
51
+ end
52
+ end
53
+ end
54
+ end
data/lib/mvinl/info.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- # engine.rb
3
+ # info.rb
4
4
  # Copyright (c) 2024, Daniel Sierpiński All rights reserved.
5
5
  #
6
6
  # See Copyright Notice in mvnil.rb
7
7
 
8
8
  module MVinl
9
- VERSION = '0.1.4'
9
+ VERSION = '0.1.6'
10
10
  Version = VERSION
11
11
  Copyright = 'Copyright (c) 2024, Daniel Sierpiński'
12
12
  end
data/lib/mvinl/lexer.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- =begin engine.rb
3
+ =begin lexer.rb
4
4
  Copyright (c) 2024, Daniel Sierpiński All rights reserved.
5
5
 
6
6
  See Copyright Notice in mvnil.rb
@@ -16,11 +16,8 @@ module MVinl
16
16
  attr_reader :eos
17
17
 
18
18
  ID_REGEX = /[a-zA-Z_][a-zA-Z0-9_]*/
19
-
20
- RESERVED = %w[def as style].freeze
21
-
22
19
  TOKENS = {
23
- KEYWORD: /#{RESERVED[0]}|#{RESERVED[1]}|#{RESERVED[2]}/,
20
+ KEYWORD: Regexp.union(Context::RESERVED),
24
21
  OPEN_PAREN: /\(/,
25
22
  CLOSE_PAREN: /\)/,
26
23
  NEW_LINE: /(?:[ \t]*(?:\r?\n)[ \t]*)+/,
@@ -28,6 +25,8 @@ module MVinl
28
25
  KEYWORD_ARG: /(#{ID_REGEX}):/,
29
26
  ID: ID_REGEX,
30
27
  GROUP: /@(#{ID_REGEX})/,
28
+ CONSTANT: /!([A-Z][A-Z0-9_]*)/,
29
+ VARIABLE: /!(#{ID_REGEX})/,
31
30
  FLOAT: /[+-]?\d+\.\d+/,
32
31
  NUMBER: /[+-]?\d+/,
33
32
  MULTILINE_STRING: /"((?:\\.|[^"\\])*)"\s*\\\s*/,
@@ -38,7 +37,8 @@ module MVinl
38
37
  END_TAG: /\./
39
38
  }.freeze
40
39
 
41
- def initialize(input)
40
+ def initialize(context, input = '')
41
+ @context = context
42
42
  @ss = StringScanner.new(input)
43
43
  @args_n = 0
44
44
  @in_group = false
@@ -52,6 +52,14 @@ module MVinl
52
52
  def next_token
53
53
  return process_eos if @ss.eos?
54
54
 
55
+ # Check if variable name been used
56
+ MVinl::Context::CONSTANTS.each_key do |const_name|
57
+ return [:CONSTANT_CALL, const_name] if @ss.scan(/\A#{Regexp.escape const_name.to_s}\b/)
58
+ end
59
+ @context.variables.each_key do |var_name|
60
+ return [:VARIABLE_CALL, var_name] if @ss.scan(/\A#{Regexp.escape var_name.to_s}\b/)
61
+ end
62
+
55
63
  TOKENS.each do |type, regex|
56
64
  if @ss.scan regex
57
65
  @last_type = type
@@ -60,6 +68,7 @@ module MVinl
60
68
  end
61
69
  case @last_type
62
70
  when :NEW_LINE
71
+ @context.state[:lines] += 1
63
72
  return next_token if continuation_line?
64
73
 
65
74
  [:END_TAG, "\n"]
@@ -72,13 +81,13 @@ module MVinl
72
81
  next_token
73
82
  when :OPEN_PAREN then [:OPEN_PAREN, '(']
74
83
  when :CLOSE_PAREN
75
- unless Parser::STATE[:depth].positive?
84
+ unless @context.state[:depth].positive?
76
85
  raise UnexpectedTokenError, 'CLOSE_PARAM found with no matching OPEN_PARAM'
77
86
  end
78
87
 
79
88
  [:CLOSE_PAREN, ')']
80
89
  when :OPER
81
- unless Parser::STATE[:depth].positive?
90
+ unless @context.state[:depth].positive?
82
91
  raise UnexpectedTokenError, 'OPER found with no matching OPEN_PARAM'
83
92
  end
84
93
 
@@ -88,30 +97,30 @@ module MVinl
88
97
 
89
98
  [:KEYWORD, @ss.matched]
90
99
  when :KEYWORD_ARG
91
- if !Parser::STATE[:in_prop]
100
+ if !@context.state[:in_prop]
92
101
  raise UnexpectedTokenError, 'Looking for identifier but found KEYWORD_ARG'
93
- elsif Parser::STATE[:in_keyword_arg]
102
+ elsif @context.state[:in_keyword_arg]
94
103
  raise UnexpectedTokenError, 'Looking for a keyword argument value but found KEYWORD_ARG'
95
104
  end
96
105
 
97
106
  [:KEYWORD_ARG, @ss[1]]
98
107
  when :GROUP
99
108
  # Group gets canceled whenever encountered another group id or a matching end tag
100
- if Parser::STATE[:in_keyword_arg]
109
+ if @context.state[:in_keyword_arg]
101
110
  raise UnexpectedTokenError, 'Looking for a keyword argument value but found GROUP'
102
111
  end
103
112
 
104
113
  @in_group = true
105
114
  [:GROUP, @ss[1]]
115
+ when :CONSTANT then [:CONSTANT, @ss[1]]
116
+ when :VARIABLE then [:VARIABLE, @ss[1]]
106
117
  when :ID then [:ID, @ss.matched]
107
- when :MULTILINE_STRING
108
- [:MULTILINE_STRING, @ss[1]]
109
- when :NUMBER, :FLOAT, :STRING, :SYMBOL
118
+ when :NUMBER, :FLOAT, :STRING, :SYMBOL, :MULTILINE_STRING
110
119
  # Values can't be used outside an property or a lambda
111
- if !Parser::STATE[:in_prop] && !Parser::STATE[:depth].positive?
120
+ if !@context.state[:in_prop] && !@context.state[:depth].positive? && !@context.state[:in_var]
112
121
  raise UnexpectedTokenError, "Looking for ID or OPEN_PAREN but found #{@last_type}"
113
- elsif !Parser::STATE[:in_keyword_arg] && Parser::STATE[:keyword_arg_depth].positive? &&
114
- !Parser::STATE[:depth].positive?
122
+ elsif !@context.state[:in_keyword_arg] && @context.state[:keyword_arg_depth].positive? &&
123
+ !@context.state[:depth].positive? && !@context.state[:in_var]
115
124
  raise UnexpectedTokenError, "Looking for END_TAG or KEYWORD_ARG but found #{@last_type}"
116
125
  end
117
126
 
@@ -140,8 +149,8 @@ module MVinl
140
149
 
141
150
  # Auto end properties
142
151
  lookahead = @ss.check(/\A(?:#{TOKENS[:GROUP]}|#{TOKENS[:ID]}$|#{TOKENS[:END_TAG]}|#{TOKENS[:NEW_LINE]})/)
143
- warn "Continue? in_prop?: #{Parser::STATE[:in_prop].inspect}, lookahead: #{lookahead.inspect}"
144
- Parser::STATE[:in_prop] ? !lookahead : true
152
+ warn "Continue? in_prop?: #{@context.state[:in_prop].inspect}, lookahead: #{lookahead.inspect}"
153
+ @context.state[:in_prop] ? !lookahead : true
145
154
  end
146
155
 
147
156
  # Move the cursor to the next new line tag
data/lib/mvinl/parser.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- =begin engine.rb
3
+ =begin parser.rb
4
4
  Copyright (c) 2024, Daniel Sierpiński All rights reserved.
5
5
 
6
6
  See Copyright Notice in mvnil.rb
@@ -8,15 +8,28 @@ See Copyright Notice in mvnil.rb
8
8
 
9
9
  require 'mvinl.tab'
10
10
 
11
+ # Generated parser class
11
12
  class MVinl::Parser < MVinl::Program
12
- def initialize(lexer, debug: false)
13
- @yydebug = debug
13
+ def initialize(lexer, context, debug: false)
14
14
  @lexer = lexer
15
+ @context = context
16
+ @yydebug = debug
15
17
  @tokens = []
16
18
  @done = false
17
19
  super()
18
20
  end
19
21
 
22
+ def parse
23
+ do_parse
24
+ rescue Racc::ParseError => e
25
+ puts "Parsing error at #{@context.state[:lines]}: #{e.message}"
26
+ nil
27
+ end
28
+
29
+ def feed(input)
30
+ @lexer.feed input
31
+ end
32
+
20
33
  def next_token
21
34
  if @tokens.empty?
22
35
  @lexer.next_token
@@ -36,8 +49,4 @@ class MVinl::Parser < MVinl::Program
36
49
  def parsing_done?
37
50
  @done && @tokens.empty?
38
51
  end
39
-
40
- def parse
41
- do_parse
42
- end
43
52
  end
data/lib/mvinl.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- =begin engine.rb
3
+ =begin mvinl.rb
4
4
  Copyright (c) 2018, 2024, Daniel Sierpiński All rights reserved.
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -22,16 +22,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
  THE SOFTWARE.
23
23
  =end
24
24
 
25
+ require 'mvinl/context'
25
26
  require 'mvinl/parser'
26
27
  require 'mvinl/lexer'
27
28
 
29
+ # Library entry point
28
30
  module MVinl
31
+ @context = Context.new
32
+ @parser = Parser.new(Lexer.new(@context, ''), @context)
33
+
29
34
  def self.eval(input)
30
- parser = Parser.new(Lexer.new(input))
31
- parser.parse
35
+ @parser.feed input
36
+ @parser.parse
32
37
  end
33
38
 
34
39
  def self.eval_from_file(path)
35
40
  self.eval File.read(path)
36
41
  end
42
+
43
+ def self.context
44
+ @context
45
+ end
46
+
47
+ def self.reset
48
+ @context.reset
49
+ end
37
50
  end
data/rakelib/build.rake CHANGED
@@ -1,32 +1,11 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'rubygems/package'
4
- require 'mvinl/info'
3
+ require 'bundler/gem_tasks'
5
4
 
6
- require 'pry'
7
- require 'shellwords'
8
-
9
- GRAMMAR_FILE = 'syntax/mvinl.y'
10
- COMPILED_FILE = 'syntax/mvinl.tab.rb'
11
-
12
- gemspec = Gem::Specification.load 'mvinl.gemspec'
13
- gemfile = "#{gemspec.name}-#{gemspec.version}.gem"
14
-
15
- task push: [:build] do
16
- #binding.pry
17
- system('gem', 'push', '-v', Shellwords.escape(gemspec.version.to_s), Shellwords.escape(gemspec.name))
18
- end
19
-
20
- task install: [:build] do
21
- Gem.install(gemfile)
22
- puts "Installed #{gemfile}"
23
- end
24
-
25
- task build: [:compile] do
26
- Gem::Package.build(gemspec)
27
- puts "Build #{gemfile}"
28
- end
5
+ desc 'Push MVinl gem; use this task in stead of \'release\''
6
+ task push: %I[compile build release]
29
7
 
8
+ desc 'Compile the grammar file'
30
9
  task :compile do
31
10
  system('racc', GRAMMAR_FILE, '-o', COMPILED_FILE)
32
11
  end
@@ -0,0 +1,5 @@
1
+ !a 5
2
+ def (foo (* a 2))
3
+ !a 10
4
+
5
+ x (foo)
@@ -0,0 +1,5 @@
1
+ !a "Hello," \
2
+ "MVinl" \
3
+ "!"
4
+
5
+ x a
data/spec/program_spec.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require_relative 'spec_helper'
4
- require 'mvinl'
5
4
 
6
5
  describe MVinl, '#eval' do
7
6
  context 'no input' do
@@ -10,78 +9,132 @@ describe MVinl, '#eval' do
10
9
  expect(result).to eq({})
11
10
  end
12
11
  end
12
+
13
13
  context 'groups' do
14
14
  it 'retruns a single group' do
15
15
  result = MVinl.eval('@x')
16
16
  expect(result).to eq({ x: {} })
17
17
  end
18
-
19
18
  it 'returns two groups' do
20
19
  result = MVinl.eval('@x @y')
21
20
  expect(result).to eq({ x: {}, y: {} })
22
21
  end
23
22
  end
23
+
24
24
  context 'properties' do
25
25
  it 'return a single property' do
26
26
  result = MVinl.eval('@x a')
27
27
  expect(result).to eq({ x: { a: [[], {}] } })
28
28
  end
29
-
30
29
  it 'returns two properties' do
31
30
  result = MVinl.eval('@x a b')
32
31
  expect(result).to eq({ x: { a: [[], {}], b: [[], {}] } })
33
32
  end
34
33
  end
35
34
 
35
+ context 'variables' do
36
+ it 'stores a single variable' do
37
+ MVinl.eval('!n 0')
38
+ expect(MVinl.context.variables[:n]).to be_truthy
39
+ end
40
+ it 'stores two variables' do
41
+ MVinl.eval('!n 3 !m 6')
42
+ expect(MVinl.context.variables[:n] && MVinl.context.variables[:m]).to be_truthy
43
+ end
44
+ it 'stores a single variable with it\'s value' do
45
+ MVinl.eval('!n 5')
46
+ expect(MVinl.context.variables[:n]).to eq 5
47
+ end
48
+ it 'evaluates a single variable' do
49
+ result = MVinl.eval('!n 5 x n')
50
+ expect(result).to eq({ x: [[5], {}] })
51
+ end
52
+ end
53
+
54
+ context 'constants' do
55
+ it 'stores a single constant' do
56
+ MVinl.eval('!N 50')
57
+ expect(MVinl::Context::CONSTANTS[:N]).to be_truthy
58
+ end
59
+ it 'evaluates a single constant' do
60
+ result = MVinl.eval('x N')
61
+ expect(result).to eq({ x: [[50], {}] })
62
+ end
63
+ end
64
+
36
65
  context 'functions' do
37
- it 'Stores function definition with \'+\' OPER and no arguments' do
66
+ it 'stores function definition with \'+\' OPER and no arguments' do
38
67
  MVinl.eval('def (f (+ 1))')
39
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: [], body: [:+, 1] } })
68
+ expect(MVinl.context.functions).to eq({ f: { args: [], body: [:+, 1] } })
40
69
  end
41
-
42
- it 'Stores function definition with \'-\' OPER and no arguments' do
70
+ it 'stores function definition with \'-\' OPER and no arguments' do
43
71
  MVinl.eval('def (f (- 1))')
44
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: [], body: [:-, 1] } })
72
+ expect(MVinl.context.functions).to eq({ f: { args: [], body: [:-, 1] } })
45
73
  end
46
-
47
- it 'Stores function definition with \'*\' OPER and no arguments' do
74
+ it 'stores function definition with \'*\' OPER and no arguments' do
48
75
  MVinl.eval('def (f (* 1))')
49
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: [], body: [:*, 1] } })
76
+ expect(MVinl.context.functions).to eq({ f: { args: [], body: [:*, 1] } })
50
77
  end
51
-
52
- it 'Stores function definition with \'/\' OPER and no arguments' do
78
+ it 'stores function definition with \'/\' OPER and no arguments' do
53
79
  MVinl.eval('def (f (/ 1))')
54
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: [], body: [:/, 1] } })
80
+ expect(MVinl.context.functions).to eq({ f: { args: [], body: [:/, 1] } })
55
81
  end
56
-
57
- it 'Stores function definition with \'%\' OPER and no arguments' do
82
+ it 'stores function definition with \'%\' OPER and no arguments' do
58
83
  MVinl.eval('def (f (% 1))')
59
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: [], body: [:%, 1] } })
84
+ expect(MVinl.context.functions).to eq({ f: { args: [], body: [:%, 1] } })
60
85
  end
61
-
62
- it 'Stores function definition with a single argument' do
86
+ it 'stores function definition with a single argument' do
63
87
  MVinl.eval('def (f a (+ a a))')
64
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: %I[a], body: %I[+ a a] } })
88
+ expect(MVinl.context.functions).to eq({ f: { args: %I[a], body: %I[+ a a] } })
65
89
  end
66
-
67
- it 'Stores function definition with two arguments' do
90
+ it 'stores function definition with two arguments' do
68
91
  MVinl.eval('def (f a b (+ a b))')
69
- expect(MVinl::Parser::FUNCTIONS).to eq({ f: { args: %I[a b], body: %I[+ a b] } })
92
+ expect(MVinl.context.functions).to eq({ f: { args: %I[a b], body: %I[+ a b] } })
70
93
  end
71
-
72
- it 'Evaluate anonimous function' do
73
- result = MVinl.eval('@x x (+ 2 2)')
74
- expect(result).to eq({ x: { x: [[4], {}] } })
94
+ it 'evaluates anonimous function' do
95
+ result = MVinl.eval('x (+ 2 2)')
96
+ expect(result).to eq({ x: [[4], {}] })
75
97
  end
76
-
77
- it 'Evaluate function' do
78
- result = MVinl.eval('def (foo (+ 5)) @x x (foo)')
79
- expect(result).to eq({ x: { x: [[5], {}] } })
98
+ it 'evaluates function' do
99
+ result = MVinl.eval('def (foo (+ 5)) x (foo)')
100
+ expect(result).to eq({ x: [[5], {}] })
80
101
  end
102
+ it 'evaluates function calling another function' do
103
+ result = MVinl.eval('def (foo (+ 5)) def (bar (foo)) x (bar)')
104
+ expect(result).to eq({ x: [[5], {}] })
105
+ end
106
+ it 'evaluates function inside a variable' do
107
+ MVinl.eval('def (foo (+ 7)) !n (foo)')
108
+ expect(MVinl.context.variables[:n]).to eq 7
109
+ end
110
+ end
111
+ end
81
112
 
82
- it 'Evaluate function calling another function' do
83
- result = MVinl.eval('def (foo (+ 5)) def (bar (foo)) @x x (bar)')
84
- expect(result).to eq({ x: { x: [[5], {}] } })
113
+ describe MVinl, '#eval_from_file' do
114
+ context 'stack example' do
115
+ it 'evaluates a basic example' do
116
+ MVinl.reset
117
+ result = MVinl.eval_from_file('spec/stack.mvnl')
118
+ expect(result).to eq({ menu: { Menu: [[], { New_Game: :state_next, Exit: :abord }] },
119
+ game: { Mouse: [[], {}], Board: [[3, 3], {}],
120
+ Button: [['󰮱 Hello Vinl!'], {
121
+ line_height: 25, padding: 8
122
+ }] } })
123
+ end
124
+ it 'evaluates an example with variables' do
125
+ MVinl.reset
126
+ result = MVinl.eval_from_file('spec/vars.mvnl')
127
+ expect(result).to eq({ x: [[5], { fun: 10 }] })
128
+ end
129
+ it 'function respects variable change' do
130
+ MVinl.reset
131
+ result = MVinl.eval_from_file('spec/complex_vars.mvnl')
132
+ expect(result).to eq({ x: [[20], {}] })
133
+ end
134
+ it 'evaluates multiline string' do
135
+ MVinl.reset
136
+ result = MVinl.eval_from_file('spec/multiline_string.mvnl')
137
+ expect(result).to eq({ x: [['Hello, MVinl !'], {}] })
85
138
  end
86
139
  end
87
140
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  $LOAD_PATH.unshift File.expand_path('lib', __dir__)
4
+
5
+ require 'mvinl'
data/spec/vars.mvnl ADDED
@@ -0,0 +1,4 @@
1
+ !a 5
2
+ def (foo (* 2 a))
3
+
4
+ x a fun: (foo)