Skip to content

Commit

Permalink
Support lists and nested lists in SanLang
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanIvanoff committed Dec 18, 2023
1 parent e83c190 commit e5f0345
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 11 deletions.
23 changes: 14 additions & 9 deletions lib/sanbase/san_lang/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule Sanbase.SanLang.Interpreter do
def eval({:env_var, _, _} = env_var, env), do: eval_env_var(env_var, env)
def eval({:identifier, _, _} = identifier, env), do: eval_identifier(identifier, env)
def eval({:lambda_fn, _args, _body} = lambda, env), do: eval_lambda_fn(lambda, env)
def eval({:list, _} = list, env), do: eval_list(list, env)

def eval({{boolean_op, _}, _, _} = boolean_expr, env) when boolean_op in [:and, :or],
do: eval_boolean_expr(boolean_expr, env)
Expand All @@ -35,6 +36,15 @@ defmodule Sanbase.SanLang.Interpreter do
Map.get(env_var, key)
end

# Comparison
def eval({{:comparison_expr, {op, _}}, lhs, rhs}, env) when op in ~w(== != < > <= >=)a,
do: apply(Kernel, op, [eval(lhs, env), eval(rhs, env)])

# Named Function Calls
def eval({:function_call, {:identifier, _, function_name}, args}, env) do
eval_function_call(function_name, args, env)
end

def eval({:access_expr, env_var_or_identifier, key}, env) do
# The acessed type is an env var or an identifier
map = eval(env_var_or_identifier, env)
Expand All @@ -43,6 +53,10 @@ defmodule Sanbase.SanLang.Interpreter do
Map.get(map, key)
end

def eval_list({:list, list_elements}, env) do
Enum.map(list_elements, fn x -> eval(x, env) end)
end

# Boolean expressions
def eval_boolean_expr({{op, _}, lhs, rhs}, env) when op in [:and, :or] do
lhs = eval(lhs, env)
Expand All @@ -60,15 +74,6 @@ defmodule Sanbase.SanLang.Interpreter do
end
end

# Comparison
def eval({{:comparison_expr, {op, _}}, lhs, rhs}, env) when op in ~w(== != < > <= >=)a,
do: apply(Kernel, op, [eval(lhs, env), eval(rhs, env)])

# Named Function Calls
def eval({:function_call, {:identifier, _, function_name}, args}, env) do
eval_function_call(function_name, args, env)
end

@supported_functions SanLang.Kernel.__info__(:functions)
|> Enum.map(fn {name, _arity} -> to_string(name) end)
defp eval_function_call(function_name, args, env)
Expand Down
5 changes: 3 additions & 2 deletions lib/sanbase/san_lang/san_lang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ defmodule Sanbase.SanLang do
- pow(2, 10) => 1024
- div(10, 5) => 2
- Named functions with lambda function as arguments:
- map(@projects, fn p -> x["name"] end) => ["Bitcoin", "Ethereum"]
- map([1,2,3], fn x -> x + 10 end) => [11, 12, 13]
- filter([1,2,3], fn x -> x > 1 end) => [2, 3]
- Comparisons and boolean expressions: ==, !=, >, <, >=, <=, and, or
- 1 + 2 * 3 + 10 > 10 => true
"""
Expand Down Expand Up @@ -65,7 +66,7 @@ defmodule Sanbase.SanLang do
end

@doc ~s"""
Same as eval/2, but raises on error
Same as eval/2, but raises on error.
"""
@spec eval(String.t(), Environment.t()) :: any() | no_return
def eval!(input, env \\ Environment.new()) when is_binary(input) do
Expand Down
8 changes: 8 additions & 0 deletions src/san_lang_parser.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Nonterminals
grammar
expr
value
list list_elements
dual_arithmetic_op mult_arithmetic_op
boolean_literal and_op or_op
comparison_rel_op comparison_comp_op
Expand Down Expand Up @@ -61,6 +62,7 @@ value -> access_expr : '$1'.
value -> function_call : '$1'.
value -> identifier : '$1'.
value -> boolean_literal : '$1'.
value -> list : '$1'.

%% booleans
boolean_literal -> 'true' : '$1'.
Expand Down Expand Up @@ -90,6 +92,12 @@ comparison_rel_op -> '<=' : {comparison_expr, '$1'}.
comparison_rel_op -> '>' : {comparison_expr, '$1'}.
comparison_rel_op -> '>=' : {comparison_expr, '$1'}.

%% Lists
list -> '[' ']' : {list, []}.
list -> '[' list_elements ']' : {list, '$2'}.
list_elements -> value ',' list_elements : ['$1' | '$3'].
list_elements -> value : ['$1'].

%% Lambda function
lambda_fn -> 'fn' lambda_args '->' expr 'end' : {lambda_fn, '$2', '$4'}.
lambda_args -> identifier ',' lambda_args : ['$1' | '$3'].
Expand Down
8 changes: 8 additions & 0 deletions test/sanbase/san_lang/san_lang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ defmodule Sanbase.SanLangTest do
assert SanLang.eval!(~s|"a string"|) == "a string"
end

test "lists" do
assert SanLang.eval!("[]") == []
assert SanLang.eval!("[1, 2, 3]") == [1, 2, 3]
assert SanLang.eval!("[1, 2, 3.14]") == [1, 2, 3.14]
assert SanLang.eval!("[1, 2, 3.14, \"a string\"]") == [1, 2, 3.14, "a string"]
assert SanLang.eval!("[1,2,[3,4,[[[5]]]]]") == [1, 2, [3, 4, [[[5]]]]]
end

test "env vars are pulled from the environment" do
env = SanLang.Environment.new()
env = SanLang.Environment.put_env_bindings(env, %{"a" => 1, "b" => "some string value"})
Expand Down

0 comments on commit e5f0345

Please sign in to comment.