A toy dynamic programming language written in Ruby
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

function_call_spec.rb 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. RSpec.describe AST::FunctionCall do
  2. it 'evaluates built-in functions' do
  3. expect {
  4. AST::FunctionCall.new(
  5. AST::Identifier.new('print'),
  6. [AST::String.new('hello world')]
  7. )
  8. .execute(Environment.new)
  9. }.to output("hello world\n").to_stdout
  10. end
  11. it 'evaluates user-defined functions' do
  12. env = Environment.new
  13. AST::FunctionDefinition.new(
  14. AST::Identifier.new('add_one'),
  15. [AST::Identifier.new('n')],
  16. AST::Block.new(
  17. [
  18. AST::Binary.new(
  19. AST::Operators::ADD,
  20. AST::Identifier.new('n'),
  21. AST::Number.new(1.0)
  22. )
  23. ]
  24. )
  25. )
  26. .execute(env)
  27. expect(
  28. AST::FunctionCall.new(
  29. AST::Identifier.new('add_one'),
  30. [AST::Number.new(5.0)]
  31. )
  32. .execute(env)
  33. ).to eq(6.0)
  34. end
  35. # This corresponds to the program
  36. # function factorial(n) {
  37. # if n == 0 {
  38. # 1;
  39. # } else {
  40. # n * factorial(n - 1);
  41. # }
  42. # }
  43. # factorial(5);
  44. it 'evaluates recursive functions' do
  45. env = Environment.new
  46. AST::FunctionDefinition.new(
  47. AST::Identifier.new('factorial'),
  48. [AST::Identifier.new('n')],
  49. AST::Block.new(
  50. [
  51. AST::Conditional.new(
  52. [
  53. AST::Branch.new(
  54. AST::Binary.new(
  55. AST::Operators::DOUBLE_EQUALS,
  56. AST::Identifier.new('n'),
  57. AST::Number.new(0.0)
  58. ),
  59. AST::Block.new([AST::Number.new(1.0)])
  60. ),
  61. AST::Branch.new(
  62. AST::Boolean.new(true),
  63. AST::Block.new(
  64. [
  65. AST::Binary.new(
  66. AST::Operators::MULTIPLY,
  67. AST::Identifier.new('n'),
  68. AST::FunctionCall.new(
  69. AST::Identifier.new('factorial'),
  70. [
  71. AST::Binary.new(
  72. AST::Operators::SUBTRACT,
  73. AST::Identifier.new('n'),
  74. AST::Number.new(1.0)
  75. )
  76. ]
  77. )
  78. )
  79. ]
  80. )
  81. )
  82. ]
  83. )
  84. ]
  85. )
  86. )
  87. .execute(env)
  88. expect(
  89. AST::FunctionCall.new(
  90. AST::Identifier.new('factorial'),
  91. [AST::Number.new(5.0)]
  92. )
  93. .execute(env)
  94. ).to eq(120.0)
  95. end
  96. # This is equivalent to the program
  97. #
  98. # function add_one(n) {
  99. # n + 1;
  100. # }
  101. # add_one(5);
  102. # print(n);
  103. #
  104. # This test is ensuring that `n` is not still defined outside the scope of
  105. # add_one after the function finishes executing
  106. it 'destroys variables when they go out of scope' do
  107. env = Environment.new
  108. AST::FunctionDefinition.new(
  109. AST::Identifier.new('add_one'),
  110. [AST::Identifier.new('n')],
  111. AST::Block.new(
  112. [
  113. AST::Binary.new(
  114. AST::Operators::ADD,
  115. AST::Identifier.new('n'),
  116. AST::Number.new(1.0)
  117. )
  118. ]
  119. )
  120. )
  121. .execute(env)
  122. AST::FunctionCall.new(AST::Identifier.new('add_one'), [AST::Number.new(5)])
  123. .execute(env)
  124. expect {
  125. AST::FunctionCall.new(
  126. AST::Identifier.new('print'),
  127. [AST::Identifier.new('n')]
  128. )
  129. .execute(env)
  130. }.to raise_error('Undefined variable n')
  131. end
  132. # let add_one = function(n) { n + 1; };
  133. # let do = function(f, n) { f(n); };
  134. # do(5);
  135. it 'executes higher order functions' do
  136. env = Environment.new
  137. AST::VariableDeclaration.new(
  138. AST::Identifier.new('add_one'),
  139. AST::FunctionDefinition.new(
  140. nil,
  141. [AST::Identifier.new('n')],
  142. AST::Block.new(
  143. [
  144. AST::Binary.new(
  145. AST::Operators::ADD,
  146. AST::Identifier.new('n'),
  147. AST::Number.new(1.0)
  148. )
  149. ]
  150. )
  151. )
  152. )
  153. .execute(env)
  154. AST::VariableDeclaration.new(
  155. AST::Identifier.new('do'),
  156. AST::FunctionDefinition.new(
  157. nil,
  158. [AST::Identifier.new('f'), AST::Identifier.new('x')],
  159. AST::Block.new(
  160. [
  161. AST::FunctionCall.new(
  162. AST::Identifier.new('f'),
  163. [AST::Identifier.new('x')]
  164. )
  165. ]
  166. )
  167. )
  168. )
  169. .execute(env)
  170. result =
  171. AST::FunctionCall.new(
  172. AST::Identifier.new('do'),
  173. [AST::Identifier.new('add_one'), AST::Number.new(5.0)]
  174. )
  175. .execute(env)
  176. expect(result).to eq(6)
  177. end
  178. it 'allows calling an anonymous function directly' do
  179. result =
  180. AST::FunctionCall.new(
  181. AST::FunctionDefinition.new(
  182. nil,
  183. [],
  184. AST::Block.new([AST::Number.new(5.0)])
  185. ),
  186. []
  187. )
  188. .execute(Environment.new)
  189. expect(result).to eq(5.0)
  190. end
  191. # function outer() {
  192. # let x = "hello world";
  193. # function () {
  194. # x;
  195. # };
  196. # }
  197. # let y = outer();
  198. # y();
  199. it "keeps a reference to a closure's environment" do
  200. env = Environment.new
  201. AST::FunctionDefinition.new(
  202. AST::Identifier.new('outer'),
  203. [],
  204. AST::Block.new(
  205. [
  206. AST::VariableDeclaration.new(
  207. AST::Identifier.new('x'),
  208. AST::String.new('hello world')
  209. ),
  210. AST::FunctionDefinition.new(
  211. nil,
  212. [],
  213. AST::Block.new([AST::Identifier.new('x')])
  214. )
  215. ]
  216. )
  217. )
  218. .execute(env)
  219. AST::VariableDeclaration.new(
  220. AST::Identifier.new('y'),
  221. AST::FunctionCall.new(AST::Identifier.new('outer'), [])
  222. )
  223. .execute(env)
  224. result = AST::FunctionCall.new(AST::Identifier.new('y'), []).execute(env)
  225. expect(result).to eq('hello world')
  226. end
  227. end