Skip to content
Browse files

Began work on the syntax parser. It currently parses numbers, strings…

…, and tables. (It doesn't -do- much with them yet, though.)
  • Loading branch information...
1 parent 59a575f commit 345cdc91a5ae841a0715bf8046ae02249b100892 @Twisol committed
Showing with 271 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +3 −0 Gemfile
  3. +10 −0 Gemfile.lock
  4. +11 −0 bin/lupin.rb
  5. +6 −0 lib/lupin.rb
  6. +7 −0 lib/lupin/ast.rb
  7. +59 −0 lib/lupin/ast/literals.rb
  8. +13 −0 lib/lupin/ast/table.rb
  9. +5 −0 lib/lupin/parser.rb
  10. +73 −0 lib/lupin/parser/lua.citrus
  11. +80 −0 lib/lupin/parser/parser.citrus
  12. +3 −0 lib/lupin/version.rb
View
1 .gitignore
@@ -0,0 +1 @@
+*.rbc
View
3 Gemfile
@@ -0,0 +1,3 @@
+source :gemcutter
+
+gem 'citrus', '~> 2.2.2'
View
10 Gemfile.lock
@@ -0,0 +1,10 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ citrus (2.2.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ citrus (~> 2.2.2)
View
11 bin/lupin.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+require "rubygems"
+gem "bundler"
+require "bundler"
+Bundler.load
+
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
+require "lupin"
+
+# Eventually, a REPL will probably go here.
View
6 lib/lupin.rb
@@ -0,0 +1,6 @@
+module Lupin
+ require "lupin/version"
+
+ require "lupin/ast"
+ require "lupin/parser"
+end
View
7 lib/lupin/ast.rb
@@ -0,0 +1,7 @@
+module Lupin
+ module AST
+ require "lupin/ast/literals"
+ require "lupin/ast/table"
+ end
+end
+
View
59 lib/lupin/ast/literals.rb
@@ -0,0 +1,59 @@
+module Lupin::AST
+ class Literal
+ attr_reader :value
+
+ def initialize (val)
+ @value = val
+ end
+ end
+
+ class String < Literal
+ def initialize (str)
+ str = str.gsub /\\(\d{1,3}|\D)/m do
+ case $1
+ when 'a' then "\a"
+ when 'b' then "\b"
+ when 'f' then "\f"
+ when 'n' then "\n"
+ when 'r' then "\r"
+ when 't' then "\t"
+ when 'v' then "\v"
+ when "\"" then "\""
+ when "\'" then "\'"
+ when "\\" then "\\"
+ when /\n|\r/ then "\n"
+ when /\d{1,3}/ then seq.to_i.chr
+ end
+ end
+
+ super(str)
+ end
+ end
+
+ class LongString < Literal
+ end
+
+ class Number < Literal
+ def initialize (base, exponent)
+ super(base * (10 ** exponent))
+ end
+ end
+
+ class True < Literal
+ def initialize
+ super(true)
+ end
+ end
+
+ class False < Literal
+ def initialize
+ super(false)
+ end
+ end
+
+ class Nil < Literal
+ def initialize
+ super(nil)
+ end
+ end
+end
View
13 lib/lupin/ast/table.rb
@@ -0,0 +1,13 @@
+module Lupin::AST
+ class Table
+ def initialize (fieldlist)
+ @fields = fieldlist
+ end
+ end
+
+ class Field
+ def initialize (key, val)
+ @key, @val = key, val
+ end
+ end
+end
View
5 lib/lupin/parser.rb
@@ -0,0 +1,5 @@
+require "citrus"
+
+module Lupin
+ Citrus.load(File.join(File.dirname(__FILE__), 'parser', 'parser'))
+end
View
73 lib/lupin/parser/lua.citrus
@@ -0,0 +1,73 @@
+grammar Lupin::Parser
+ rule lua
+ chunk
+ end
+
+ rule chunk
+ list:stat* { matches.map(&:value) }
+ end
+
+ rule stat
+ (statement:
+ ( exp
+ )
+ WS? ';'?
+ ) {
+ statement.value
+ }
+ end
+
+ rule exp
+ ( 'nil' { Lupin::AST::Nil.new }
+ | 'false' { Lupin::AST::False.new }
+ | 'true' { Lupin::AST::True.new }
+ | number
+ | string
+ )
+ end
+
+ rule number
+ (base:/\d*\.\d+|\d+\.?/ { to_f }
+ e:exponent? { matches.length > 0 ? exponent.value : 0 }
+ ) { Lupin::AST::Number.new(base.value, e.value) }
+ end
+
+ rule exponent
+ ([Ee] e:/[+-]?\d+/) { e.to_i }
+ end
+
+ rule string
+ ( ("\"" s:/(\\(?:['"abfnrtv\n\r]|\d{1,3})|[^"\n])*/ "\"") { Lupin::AST::String.new(s.value) }
+ | ("\'" s:/(\\(?:['"abfnrtv\n\r]|\d{1,3})|[^'\n])*/ "\'") { Lupin::AST::String.new(s.value) }
+ | /\[(=*)\[.*?\]\1\]/m { Lupin::AST::LongString.new(match(/\[(=*)\[(.*?)\]\1\]/m)[2]) }
+ )
+ end
+
+ rule WS
+ /[ \n\t]+/ { nil }
+ end
+end
+
+grammar Lupin::Addition
+ rule addition
+ a:number WS? '+' WS? b:number {
+ a.value + b.value
+ }
+ end
+
+ rule number
+ (sign /\d*\.\d+|\d+\.?/ exp?) { sign.value * to_f }
+ end
+
+ rule exp
+ ([Ee] sign:sign digits:/\d+/) { digits.value.to_i * sign.value }
+ end
+
+ rule sign
+ (sign:[-+]? WS*) { sign.value == '-' ? -1 : 1 }
+ end
+
+ rule WS
+ [ \n\t]
+ end
+end
View
80 lib/lupin/parser/parser.citrus
@@ -0,0 +1,80 @@
+grammar Lupin::Parser
+ rule lua
+ chunk
+ end
+
+ rule chunk
+ list:stat* { matches.map(&:value) }
+ end
+
+ rule stat
+ (statement:
+ exp
+ WS? ';'?
+ ) {
+ statement.value
+ }
+ end
+
+ rule exp
+ ( table
+ | number
+ | string
+ | 'nil' { Lupin::AST::Nil.new }
+ | 'false' { Lupin::AST::False.new }
+ | 'true' { Lupin::AST::True.new }
+ )
+ end
+
+ rule table
+ ('{' WS? list:fieldlist WS? '}') {
+ Lupin::AST::Table.new(list.value)
+ }
+ end
+
+ rule fieldlist
+ (f:field
+ list:(WS? fieldsep WS? f:field)*
+ WS? fieldsep?
+ ) {
+ [self].concat(list.matches).map {|m| m.f.value}
+ }
+ end
+
+ rule field
+ ( ('[' WS? k:exp WS? ']' WS? '=' WS? v:exp)
+ { Lupin::AST::Field.new(k.value, v.value) }
+ | (exp:exp '')
+ { Lupin::AST::Field.new(nil, exp.value) }
+ )
+ end
+
+ rule fieldsep
+ ';' | ','
+ end
+
+ rule identifier
+ /[A-Za-z_][A-Za-z0-9]*/ { Lupin::AST::String.new(to_s) }
+ end
+
+ rule number
+ (base:/\d*\.\d+|\d+\.?/ { to_f }
+ e:exponent? { matches.length > 0 ? exponent.value : 0 }
+ ) { Lupin::AST::Number.new(base.value, e.value) }
+ end
+
+ rule exponent
+ ([Ee] e:/[+-]?\d+/) { e.to_i }
+ end
+
+ rule string
+ ( ("\"" s:/(\\(?:['"abfnrtv\n\r]|\d{1,3})|[^"\n])*/ "\"") { Lupin::AST::String.new(s.value) }
+ | ("\'" s:/(\\(?:['"abfnrtv\n\r]|\d{1,3})|[^'\n])*/ "\'") { Lupin::AST::String.new(s.value) }
+ | /\[(=*)\[.*?\]\1\]/m { Lupin::AST::LongString.new(match(/\[(=*)\[(.*?)\]\1\]/m)[2]) }
+ )
+ end
+
+ rule WS
+ /[ \n\t]+/ { nil }
+ end
+end
View
3 lib/lupin/version.rb
@@ -0,0 +1,3 @@
+module Lupin
+ VERSION = [0, 0, 1].map!(&:to_s).join('.')
+end

0 comments on commit 345cdc9

Please sign in to comment.
Something went wrong with that request. Please try again.