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 +4 -4
- data/bin/imvnl +30 -9
- data/lib/mvinl/context.rb +54 -0
- data/lib/mvinl/info.rb +2 -2
- data/lib/mvinl/lexer.rb +28 -19
- data/lib/mvinl/parser.rb +16 -7
- data/lib/mvinl.rb +16 -3
- data/rakelib/build.rake +4 -25
- data/spec/complex_vars.mvnl +5 -0
- data/spec/multiline_string.mvnl +5 -0
- data/spec/program_spec.rb +87 -34
- data/spec/spec_helper.rb +2 -0
- data/spec/vars.mvnl +4 -0
- data/syntax/mvinl.tab.rb +322 -208
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c681f294e44a200c6fb8c66aed4ac1c50fce869c1c12f0a64b891b0ee720842
|
4
|
+
data.tar.gz: 94acd6d0220cb2fbfa05c839e8081e19b6d8ef3c3f4bd549dec217653b53fe0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 '
|
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
|
-
|
37
|
-
|
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.
|
44
|
+
if Readline::HISTORY.size > 1 && Readline::HISTORY[-1] == Readline::HISTORY[-2]
|
45
|
+
Readline::HISTORY.pop
|
46
|
+
end
|
42
47
|
|
43
|
-
if
|
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
|
-
#
|
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.
|
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
|
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:
|
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
|
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
|
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
|
100
|
+
if !@context.state[:in_prop]
|
92
101
|
raise UnexpectedTokenError, 'Looking for identifier but found KEYWORD_ARG'
|
93
|
-
elsif
|
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
|
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
|
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
|
114
|
-
|
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?: #{
|
144
|
-
|
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
|
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
|
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
|
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 '
|
4
|
-
require 'mvinl/info'
|
3
|
+
require 'bundler/gem_tasks'
|
5
4
|
|
6
|
-
|
7
|
-
|
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
|
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 '
|
66
|
+
it 'stores function definition with \'+\' OPER and no arguments' do
|
38
67
|
MVinl.eval('def (f (+ 1))')
|
39
|
-
expect(MVinl
|
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
|
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
|
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
|
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
|
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
|
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
|
92
|
+
expect(MVinl.context.functions).to eq({ f: { args: %I[a b], body: %I[+ a b] } })
|
70
93
|
end
|
71
|
-
|
72
|
-
|
73
|
-
result
|
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
|
-
|
78
|
-
result
|
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
|
-
|
83
|
-
|
84
|
-
|
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
data/spec/vars.mvnl
ADDED