Jaina · Gem Version Build Status Coverage Status

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

  • 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)

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::Operator::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.

Authors

Rustam Ibragimov