diff --git a/spec/examples/rails_job_generator.rb b/spec/examples/rails_job_generator.rb new file mode 100644 index 0000000..b59c52e --- /dev/null +++ b/spec/examples/rails_job_generator.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails/generators/named_base' + +module Rails # :nodoc: + module Generators # :nodoc: + class JobGenerator < Rails::Generators::NamedBase # :nodoc: + class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job' + + class_option :parent, type: :string, default: 'ApplicationJob', desc: 'The parent class for the generated job' + + check_class_collision suffix: 'Job' + + hook_for :test_framework + + def self.default_generator_root + __dir__ + end + + def create_job_file + template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb") + + in_root do + if behavior == :invoke && !File.exist?(application_job_file_name) + template 'application_job.rb', application_job_file_name + end + end + end + + private + + def parent_class_name + options[:parent] + end + + def file_name + @file_name ||= super.sub(/_job\z/i, '') + end + + def application_job_file_name + @application_job_file_name ||= if mountable_engine? + "app/jobs/#{namespaced_path}/application_job.rb" + else + 'app/jobs/application_job.rb' + end + end + end + end +end diff --git a/spec/zodiac/lexer_spec.rb b/spec/zodiac/lexer_spec.rb index abef002..f6f5cd2 100644 --- a/spec/zodiac/lexer_spec.rb +++ b/spec/zodiac/lexer_spec.rb @@ -159,6 +159,31 @@ expect(lexer.lex).to eq(expected_output) end + + # file: + it 'lexs a full ruby program without throwing and returns' do + input = File.read('./spec/examples/rails_job_generator.rb') + lexer = described_class.new(input) + + expect(lexer.lex.size).to be > 0 + end + + it 'lexs comments' do + input = <<~RUBY + # this is a comment + # this is another comment + # this is a third comment + RUBY + lexer = described_class.new(input) + + expected_output = [ + { kind: 'COMMENT', value: '# this is a comment' }, + { kind: 'COMMENT', value: '# this is another comment' }, + { kind: 'COMMENT', value: '# this is a third comment' } + ] + + expect(lexer.lex).to eq(expected_output) + end end end end diff --git a/src/zodiac/lexer.rb b/src/zodiac/lexer.rb index 106a6e0..b341975 100644 --- a/src/zodiac/lexer.rb +++ b/src/zodiac/lexer.rb @@ -51,6 +51,8 @@ def lex_next end @tokens << { kind: 'SYMBOL', value: word } end + elsif @cur == '#' + lex_comment elsif symbol?(@cur) && !@raw_string[@cur_index + 2].nil? && @raw_string[@cur_index..@cur_index + 2] == '<=>' @tokens << { kind: 'SYMBOL', value: '<=>' } @cur_index += 3 @@ -150,5 +152,19 @@ def lex_identifier @tokens << { kind: 'IDENTIFIER', value: word } end + + def lex_comment + word = '#' + @cur_index += 1 + @cur = @raw_string[@cur_index] + + while @cur != "\n" + word += @cur + @cur_index += 1 + @cur = @raw_string[@cur_index] + end + + @tokens << { kind: 'COMMENT', value: word } + end end end diff --git a/src/zodiac/parser.rb b/src/zodiac/parser.rb index 98b2f2a..440a2f9 100644 --- a/src/zodiac/parser.rb +++ b/src/zodiac/parser.rb @@ -1,192 +1,214 @@ # frozen_string_literal: true -# Here is the syntax of Ruby in pseudo BNF. For more detail, see parse.y in Ruby distribution. - -# PROGRAM : COMPSTMT - -# COMPSTMT : STMT (TERM EXPR)* [TERM] - -# STMT : CALL do [`|' [BLOCK_VAR] `|'] COMPSTMT end -# | undef FNAME -# | alias FNAME FNAME -# | STMT if EXPR -# | STMT while EXPR -# | STMT unless EXPR -# | STMT until EXPR -# | `BEGIN' `{' COMPSTMT `}' -# | `END' `{' COMPSTMT `}' -# | LHS `=' COMMAND [do [`|' [BLOCK_VAR] `|'] COMPSTMT end] -# | EXPR - -# EXPR : MLHS `=' MRHS -# | return CALL_ARGS -# | yield CALL_ARGS -# | EXPR and EXPR -# | EXPR or EXPR -# | not EXPR -# | COMMAND -# | `!' COMMAND -# | ARG - -# CALL : FUNCTION -# | COMMAND - -# COMMAND : OPERATION CALL_ARGS -# | PRIMARY `.' OPERATION CALL_ARGS -# | PRIMARY `::' OPERATION CALL_ARGS -# | super CALL_ARGS - -# FUNCTION : OPERATION [`(' [CALL_ARGS] `)'] -# | PRIMARY `.' OPERATION `(' [CALL_ARGS] `)' -# | PRIMARY `::' OPERATION `(' [CALL_ARGS] `)' -# | PRIMARY `.' OPERATION -# | PRIMARY `::' OPERATION -# | super `(' [CALL_ARGS] `)' -# | super - -# ARG : LHS `=' ARG -# | LHS OP_ASGN ARG -# | ARG `..' ARG -# | ARG `...' ARG -# | ARG `+' ARG -# | ARG `-' ARG -# | ARG `*' ARG -# | ARG `/' ARG -# | ARG `%' ARG -# | ARG `**' ARG -# | `+' ARG -# | `-' ARG -# | ARG `|' ARG -# | ARG `^' ARG -# | ARG `&' ARG -# | ARG `<=>' ARG -# | ARG `>' ARG -# | ARG `>=' ARG -# | ARG `<' ARG -# | ARG `<=' ARG -# | ARG `==' ARG -# | ARG `===' ARG -# | ARG `!=' ARG -# | ARG `=~' ARG -# | ARG `!~' ARG -# | `!' ARG -# | `~' ARG -# | ARG `<<' ARG -# | ARG `>>' ARG -# | ARG `&&' ARG -# | ARG `||' ARG -# | defined? ARG -# | PRIMARY - -# PRIMARY : `(' COMPSTMT `)' -# | LITERAL -# | VARIABLE -# | PRIMARY `::' IDENTIFIER -# | `::' IDENTIFIER -# | PRIMARY `[' [ARGS] `]' -# | `[' [ARGS [`,']] `]' -# | `{' [(ARGS|ASSOCS) [`,']] `}' -# | return [`(' [CALL_ARGS] `)'] -# | yield [`(' [CALL_ARGS] `)'] -# | defined? `(' ARG `)' -# | FUNCTION -# | FUNCTION `{' [`|' [BLOCK_VAR] `|'] COMPSTMT `}' -# | if EXPR THEN -# COMPSTMT -# (elsif EXPR THEN COMPSTMT)* -# [else COMPSTMT] -# end -# | unless EXPR THEN -# COMPSTMT -# [else COMPSTMT] -# end -# | while EXPR DO COMPSTMT end -# | until EXPR DO COMPSTMT end -# | case COMPSTMT -# (when WHEN_ARGS THEN COMPSTMT)+ -# [else COMPSTMT] -# end -# | for BLOCK_VAR in EXPR DO -# COMPSTMT -# end -# | begin -# COMPSTMT -# [rescue [ARGS] DO COMPSTMT]+ -# [else COMPSTMT] -# [ensure COMPSTMT] -# end -# | class IDENTIFIER [`<' IDENTIFIER] -# COMPSTMT -# end -# | module IDENTIFIER -# COMPSTMT -# end -# | def FNAME ARGDECL -# COMPSTMT -# end -# | def SINGLETON (`.'|`::') FNAME ARGDECL -# COMPSTMT -# end - -# WHEN_ARGS : ARGS [`,' `*' ARG] -# | `*' ARG - -# THEN : TERM -# | then -# | TERM then - -# DO : TERM -# | do -# | TERM do - -# BLOCK_VAR : LHS -# | MLHS - -# MLHS : MLHS_ITEM `,' [MLHS_ITEM (`,' MLHS_ITEM)*] [`*' [LHS]] -# | `*' LHS - -# MLHS_ITEM : LHS -# | '(' MLHS ')' - -# LHS : VARIABLE -# | PRIMARY `[' [ARGS] `]' -# | PRIMARY `.' IDENTIFIER - -# MRHS : ARGS [`,' `*' ARG] -# | `*' ARG - -# CALL_ARGS : ARGS -# | ARGS [`,' ASSOCS] [`,' `*' ARG] [`,' `&' ARG] -# | ASSOCS [`,' `*' ARG] [`,' `&' ARG] -# | `*' ARG [`,' `&' ARG] -# | `&' ARG -# | COMMAND - -# ARGS : ARG (`,' ARG)* - -# ARGDECL : `(' ARGLIST `)' -# | ARGLIST TERM - -# ARGLIST : IDENTIFIER(`,'IDENTIFIER)*[`,'`*'[IDENTIFIER]][`,'`&'IDENTIFIER] -# | `*'IDENTIFIER[`,'`&'IDENTIFIER] -# | [`&'IDENTIFIER] - -# SINGLETON : VARIABLE -# | `(' EXPR `)' - -# ASSOCS : ASSOC (`,' ASSOC)* - -# ASSOC : ARG `=>' ARG - -# VARIABLE : VARNAME -# | nil -# | self - -# LITERAL : numeric -# | SYMBOL -# | STRING -# | STRING2 -# | HERE_DOC -# | REGEXP - -# TERM : `;' -# | `\n' + +module Zodiac + # Base parsing class for the Zodiac language. + + # Here is the syntax of Ruby in pseudo BNF. For more detail, see parse.y in Ruby distribution. + + # STMT : CALL do [`|' [BLOCK_VAR] `|'] COMPSTMT end + # | undef FNAME + # | alias FNAME FNAME + # | STMT if EXPR + # | STMT while EXPR + # | STMT unless EXPR + # | STMT until EXPR + # | `BEGIN' `{' COMPSTMT `}' + # | `END' `{' COMPSTMT `}' + # | LHS `=' COMMAND [do [`|' [BLOCK_VAR] `|'] COMPSTMT end] + # | EXPR + + # EXPR : MLHS `=' MRHS + # | return CALL_ARGS + # | yield CALL_ARGS + # | EXPR and EXPR + # | EXPR or EXPR + # | not EXPR + # | COMMAND + # | `!' COMMAND + # | ARG + + # CALL : FUNCTION + # | COMMAND + + # COMMAND : OPERATION CALL_ARGS + # | PRIMARY `.' OPERATION CALL_ARGS + # | PRIMARY `::' OPERATION CALL_ARGS + # | super CALL_ARGS + + # FUNCTION : OPERATION [`(' [CALL_ARGS] `)'] + # | PRIMARY `.' OPERATION `(' [CALL_ARGS] `)' + # | PRIMARY `::' OPERATION `(' [CALL_ARGS] `)' + # | PRIMARY `.' OPERATION + # | PRIMARY `::' OPERATION + # | super `(' [CALL_ARGS] `)' + # | super + + # ARG : LHS `=' ARG + # | LHS OP_ASGN ARG + # | ARG `..' ARG + # | ARG `...' ARG + # | ARG `+' ARG + # | ARG `-' ARG + # | ARG `*' ARG + # | ARG `/' ARG + # | ARG `%' ARG + # | ARG `**' ARG + # | `+' ARG + # | `-' ARG + # | ARG `|' ARG + # | ARG `^' ARG + # | ARG `&' ARG + # | ARG `<=>' ARG + # | ARG `>' ARG + # | ARG `>=' ARG + # | ARG `<' ARG + # | ARG `<=' ARG + # | ARG `==' ARG + # | ARG `===' ARG + # | ARG `!=' ARG + # | ARG `=~' ARG + # | ARG `!~' ARG + # | `!' ARG + # | `~' ARG + # | ARG `<<' ARG + # | ARG `>>' ARG + # | ARG `&&' ARG + # | ARG `||' ARG + # | defined? ARG + # | PRIMARY + + # PRIMARY : `(' COMPSTMT `)' + # | LITERAL + # | VARIABLE + # | PRIMARY `::' IDENTIFIER + # | `::' IDENTIFIER + # | PRIMARY `[' [ARGS] `]' + # | `[' [ARGS [`,']] `]' + # | `{' [(ARGS|ASSOCS) [`,']] `}' + # | return [`(' [CALL_ARGS] `)'] + # | yield [`(' [CALL_ARGS] `)'] + # | defined? `(' ARG `)' + # | FUNCTION + # | FUNCTION `{' [`|' [BLOCK_VAR] `|'] COMPSTMT `}' + # | if EXPR THEN + # COMPSTMT + # (elsif EXPR THEN COMPSTMT)* + # [else COMPSTMT] + # end + # | unless EXPR THEN + # COMPSTMT + # [else COMPSTMT] + # end + # | while EXPR DO COMPSTMT end + # | until EXPR DO COMPSTMT end + # | case COMPSTMT + # (when WHEN_ARGS THEN COMPSTMT)+ + # [else COMPSTMT] + # end + # | for BLOCK_VAR in EXPR DO + # COMPSTMT + # end + # | begin + # COMPSTMT + # [rescue [ARGS] DO COMPSTMT]+ + # [else COMPSTMT] + # [ensure COMPSTMT] + # end + # | class IDENTIFIER [`<' IDENTIFIER] + # COMPSTMT + # end + # | module IDENTIFIER + # COMPSTMT + # end + # | def FNAME ARGDECL + # COMPSTMT + # end + # | def SINGLETON (`.'|`::') FNAME ARGDECL + # COMPSTMT + # end + + # WHEN_ARGS : ARGS [`,' `*' ARG] + # | `*' ARG + + # THEN : TERM + # | then + # | TERM then + + # DO : TERM + # | do + # | TERM do + + # BLOCK_VAR : LHS + # | MLHS + + # MLHS : MLHS_ITEM `,' [MLHS_ITEM (`,' MLHS_ITEM)*] [`*' [LHS]] + # | `*' LHS + + # MLHS_ITEM : LHS + # | '(' MLHS ')' + + # LHS : VARIABLE + # | PRIMARY `[' [ARGS] `]' + # | PRIMARY `.' IDENTIFIER + + # MRHS : ARGS [`,' `*' ARG] + # | `*' ARG + + # CALL_ARGS : ARGS + # | ARGS [`,' ASSOCS] [`,' `*' ARG] [`,' `&' ARG] + # | ASSOCS [`,' `*' ARG] [`,' `&' ARG] + # | `*' ARG [`,' `&' ARG] + # | `&' ARG + # | COMMAND + + # ARGS : ARG (`,' ARG)* + + # ARGDECL : `(' ARGLIST `)' + # | ARGLIST TERM + + # ARGLIST : IDENTIFIER(`,'IDENTIFIER)*[`,'`*'[IDENTIFIER]][`,'`&'IDENTIFIER] + # | `*'IDENTIFIER[`,'`&'IDENTIFIER] + # | [`&'IDENTIFIER] + + # SINGLETON : VARIABLE + # | `(' EXPR `)' + + # ASSOCS : ASSOC (`,' ASSOC)* + + # ASSOC : ARG `=>' ARG + + # VARIABLE : VARNAME + # | nil + # | self + + # LITERAL : numeric + # | SYMBOL + # | STRING + # | STRING2 + # | HERE_DOC + # | REGEXP + + # TERM : `;' + # | `\n' + class Parser + def initialize(raw_string) + @raw_string = raw_string + @cur_index = 0 + @tokens = [] + end + + def parse + end + + private + + # PROGRAM : COMPSTMT + def parse_program + { kind: 'PROGRAM', value: parse_compstmt } + end + + # COMPSTMT : STMT (TERM EXPR)* [TERM] + def parse_compstmt + { kind: 'COMPSTMT', value: nil } + end +end