Simple programming language builder inspired by interpreter pattern. You can build your own languages with custom operands and operators for any project purposes.
Installation
gem 'jaina'
$ bundle install
# --- or ---
$ gem install 'jaina'
require 'jaina'
Usage
- Registered operators
- Register your own operator
- Register your own operand
- Context API
- Parse your code (build AST)
- Evaluate your code
- Custom operator/operand arguments
- List registered operands and operators
- Full example
Registered operators
AND
OR
NOT
-
(
,)
(grouping operators)
Register your own operator
# step 1: define new operator
class But < Jaina::NonTerminalExpr
token 'BUT' # use it in your program :)
associativity_direction :left # associativity (left or right)
acts_as_binary_term # binar or unary
precedence_level 4 # for example: AND > OR, NOT > AND, and etc...
end
# step 2: regsiter your operator
Jaina.register_expression(But)
Register your own operand
# step 1: define new operand
class A < Jaina::TerminalExpr
token 'A'
# NOTE: context is a custom data holder that passed from expression to expression
def evaluate(context)
# your custom evaluation code
end
end
# step 2: regsiter your operand
Jaina.register_expression(A)
# step X: redefine existing operand (with the same token)
class NewA < Jaina::TerminalExpr
token 'A'
end
Jaina.redefine_expression(NewA)
Context API
class A < Jaina::TerminalExpr
# ... some code
def evaluate(context)
... your code ...
# ... context ???
... your code ...
end
end
# NOTE: context api
context.keys # => []
context.set(:a, 1) # => 1
context.get(:a) # => 1
context.keys # => [:a]
context.get(:b) # => Jaina::Parser::AST::Contex::UndefinedContextKeyError
Parse your code (build AST)
# NOTE: without arguments
Jaina.parse('A AND B AND (C OR D) OR A AND (C OR E)')
# => #<Jaina::Parser::AST:0x00007fd6f424a2e8>
# NOTE: with arguments
Jaina.parse('A[1,2] AND B[3,4]')
# => #<Jaina::Parser::AST:0x00007fd6f424a2e9>
Evaluate your code
ast = Jaina.parse('A AND B[5,test] AND (C OR D) OR A AND (C OR E)')
ast.evaluate
# --- or ---
Jaina.evaluate('A AND B[5,test] AND (C OR D) OR A AND (C OR E)')
# --- you can set initial context of your program ---
Jaina.evaluate('A AND B[5,test]', login: 'admin', logged_in: true)
Custom operator/operand arguments
# NOTE: use []
Jaina.parse('A[1,true] AND B[false,"false"]')
# NOTE:
# all your arguments will be typecasted to
# the concrete type inferred from the argument literal
Jaina.parse('A[1,true,false,"false"]') # 1, true, false "false"
# NOTE: access to the argument list
class A < Jaina::TerminalExpr
token 'A'
def evaluate(context)
# A[1,true,false,"false"]
arguments[0] # => 1
arguments[1] # => true
arguments[2] # => false
arguments[3] # => "false"
end
end
List and fetch registered operands and operators
A = Class.new(Jaina::TerminalExpr) { token 'A' }
B = Class.new(Jaina::TerminalExpr) { token 'B' }
C = Class.new(Jaina::TerminalExpr) { token 'C' }
Jaina.register_expression(A)
Jaina.register_expression(B)
Jaina.register_expression(C)
Jaina.expressions
# => ["AND", "OR", "NOT", "(", ")", "A", "B", "C"]
Jaina.fetch_expression("AND") # => Jaina::Parser::Expression::Unit::And
Jaina.fetch_expression("A") # => A
Jaina.fetch_expression("KEK")
# => raises Jaina::Parser::Expression::Registry::UnregisteredExpressionError
Full example
# step 1: create new operand
class AddNumber < Jaina::TerminalExpr
token 'ADD'
def evaluate(context)
context.set(:current_value, context.get(:current_value) + 10)
end
end
# step 2: create another new operand
class CheckNumber < Jaina::TerminalExpr
token 'CHECK'
def evaluate(context)
context.get(:current_value) < 0
end
end
# step 4: and another new :)
class InitState < Jaina::TerminalExpr
token 'INIT'
def evaluate(context)
initial_value = arguments[0] || 0
context.set(:current_value, initial_value)
end
end
# step 5: register new oeprands
Jaina.register_expression(AddNumber)
Jaina.register_expression(CheckNumber)
Jaina.register_expression(InitState)
# step 6: run your program
# NOTE: with initial context
Jaina.evaluate('CHECK AND ADD', current_value: -1) # => 9
Jaina.evaluate('CHECK AND ADD', current_value: 2) # => false
# NOTE: without initial context
Jaina.evaluate('INIT AND ADD') # => 10
Jaina.evaluate('INIT AND (CHECK OR ADD)') # => 10
# NOTE: with arguments
Jaina.evaluate('INIT[100] AND ADD') => # 112
Contributing
- Fork it ( https://github.com/0exp/jaina/fork )
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create new Pull Request
License
Released under MIT License.