initial commit
This commit is contained in:
commit
46af3c6fa9
18 changed files with 1363 additions and 0 deletions
382
lib/yamd.rb
Normal file
382
lib/yamd.rb
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
# YAMD
|
||||
# Copyright (C) 2025 kp2pml30
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module YAMD
|
||||
class Renderer
|
||||
attr_reader :buf
|
||||
|
||||
def initialize()
|
||||
@buf = String.new
|
||||
end
|
||||
|
||||
def finalize
|
||||
@buf.freeze
|
||||
end
|
||||
end
|
||||
|
||||
class Output
|
||||
def initialize()
|
||||
@buf = String.new
|
||||
@buf << "Proc.new { |__renderer|\n"
|
||||
|
||||
@ind = 1
|
||||
end
|
||||
|
||||
def add_static(s)
|
||||
return if s.empty?
|
||||
|
||||
@buf << ("\t" * @ind) << "__renderer.str " << s.dump << "\n"
|
||||
end
|
||||
|
||||
def add_expr(e)
|
||||
@buf << ("\t" * @ind) << "__renderer.str((#{e}).to_s)"
|
||||
end
|
||||
|
||||
def add_code_nl(c)
|
||||
c = c.strip
|
||||
|
||||
if c.start_with?("}")
|
||||
@ind -= 1
|
||||
end
|
||||
|
||||
@buf << ("\t" * @ind) << c << "\n"
|
||||
|
||||
if c.end_with?("{")
|
||||
@ind += 1
|
||||
end
|
||||
end
|
||||
|
||||
def finalize()
|
||||
@buf << "}"
|
||||
@buf.freeze
|
||||
|
||||
@buf
|
||||
end
|
||||
end
|
||||
|
||||
class State
|
||||
attr_reader :output
|
||||
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@lineStart = false
|
||||
@lineNo = 0
|
||||
nextLine
|
||||
|
||||
@output = Output.new
|
||||
end
|
||||
|
||||
def errCtx
|
||||
{
|
||||
line: @lineNo
|
||||
}
|
||||
end
|
||||
|
||||
def eol?()
|
||||
@line.empty?
|
||||
end
|
||||
|
||||
def eof?()
|
||||
eol? and @io.eof?
|
||||
end
|
||||
|
||||
def lineStart?()
|
||||
@lineStart
|
||||
end
|
||||
|
||||
def nextLine()
|
||||
@lineNo += 1
|
||||
|
||||
@lineStart = true
|
||||
@line = @io.gets()
|
||||
if @line.nil?
|
||||
@line = String.new
|
||||
end
|
||||
|
||||
if @line.end_with?("\n")
|
||||
@line.chop!
|
||||
end
|
||||
|
||||
if @line.end_with?("\r")
|
||||
@line.chop!
|
||||
end
|
||||
|
||||
@line.freeze
|
||||
|
||||
line
|
||||
end
|
||||
|
||||
def line()
|
||||
@line
|
||||
end
|
||||
|
||||
def consume(cnt)
|
||||
@lineStart = false
|
||||
@line = @line[cnt..]
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
attr_reader :indent, :indent_str
|
||||
|
||||
def initialize(indent)
|
||||
@indent = indent
|
||||
@indent_str = "\t" * indent
|
||||
self.freeze
|
||||
end
|
||||
|
||||
def indented()
|
||||
Context.new(@indent + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def self.parseRaw(state, ctx)
|
||||
raw = String.new
|
||||
loop {
|
||||
if state.eof?
|
||||
break
|
||||
end
|
||||
if not state.line.start_with? ctx.indent_str
|
||||
# whitespace line == empty line
|
||||
if state.line =~ /^\s*$/
|
||||
raw << "\n"
|
||||
state.nextLine
|
||||
next
|
||||
end
|
||||
# skip comment
|
||||
if state.line =~ /^\s*#(?:|\s.*)$/
|
||||
state.nextLine
|
||||
next
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
state.consume(ctx.indent) # skip indent
|
||||
raw << state.line
|
||||
raw << "\n"
|
||||
state.nextLine
|
||||
}
|
||||
|
||||
raw.rstrip!
|
||||
raw.freeze
|
||||
end
|
||||
|
||||
def loopIndentedContent(state, ctx)
|
||||
loop {
|
||||
if not state.lineStart?
|
||||
yield state.line
|
||||
state.nextLine
|
||||
|
||||
next
|
||||
end
|
||||
if state.line =~ /^#(?:|\s.*)$/
|
||||
state.nextLine
|
||||
#yield ' '
|
||||
next
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.parseContent(state, ctx)
|
||||
strBuf = String.new
|
||||
|
||||
flushStrBuf = Proc.new {
|
||||
if strBuf != ''
|
||||
if strBuf.end_with?(' ')
|
||||
strBuf.chop!
|
||||
end
|
||||
state.output.add_static(strBuf)
|
||||
strBuf = String.new
|
||||
end
|
||||
}
|
||||
|
||||
addWs = Proc.new {
|
||||
if not strBuf.end_with? ' '
|
||||
strBuf << ' '
|
||||
end
|
||||
}
|
||||
|
||||
prevParagraph = false
|
||||
|
||||
while not state.eof?
|
||||
if state.lineStart?
|
||||
if state.line =~ /^\s*$/
|
||||
if not prevParagraph
|
||||
prevParagraph = true
|
||||
flushStrBuf.()
|
||||
|
||||
state.output.add_code_nl("__renderer.new_line")
|
||||
end
|
||||
state.nextLine
|
||||
next
|
||||
end
|
||||
|
||||
if state.line =~ /^\s*#(?:|\s.*)$/
|
||||
addWs.()
|
||||
state.nextLine
|
||||
next
|
||||
end
|
||||
|
||||
prevParagraph = false
|
||||
|
||||
if not state.line.start_with?(ctx.indent_str)
|
||||
flushStrBuf.()
|
||||
return
|
||||
end
|
||||
|
||||
state.consume(ctx.indent)
|
||||
if state.line.start_with?("\t")
|
||||
raise "unexpected indent at #{state.errCtx}"
|
||||
end
|
||||
|
||||
if state.line.start_with?("#!")
|
||||
state.consume(2)
|
||||
|
||||
state.output.add_code_nl("__renderer." + state.line.strip + " {")
|
||||
state.nextLine
|
||||
parseContent(state, ctx.indented)
|
||||
state.output.add_code_nl("}")
|
||||
|
||||
next
|
||||
elsif state.line.start_with?('#$')
|
||||
state.consume(2)
|
||||
|
||||
line = state.line.strip
|
||||
state.nextLine
|
||||
state.output.add_code_nl("__renderer." + line.strip + " {")
|
||||
state.output.add_code_nl("next " + parseRaw(state, ctx.indented).dump)
|
||||
state.output.add_code_nl("}")
|
||||
|
||||
next
|
||||
elsif state.line.start_with?("#%")
|
||||
flushStrBuf.()
|
||||
|
||||
state.consume(2)
|
||||
|
||||
state.output.add_code_nl(state.line)
|
||||
|
||||
state.nextLine
|
||||
next
|
||||
elsif state.line.start_with?("#. ")
|
||||
flushStrBuf.()
|
||||
state.consume(3)
|
||||
|
||||
state.output.add_code_nl("__renderer.list_item {")
|
||||
parseContent state, ctx.indented
|
||||
state.output.add_code_nl("}")
|
||||
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
prevParagraph = false
|
||||
|
||||
if state.eol?
|
||||
addWs.()
|
||||
state.nextLine
|
||||
elsif state.line.start_with?('##')
|
||||
strBuf << '#'
|
||||
state.consume 2
|
||||
elsif state.line[0] == '#'
|
||||
state.output.add_static(strBuf)
|
||||
strBuf = String.new
|
||||
|
||||
self.handle_hash state, ctx
|
||||
else
|
||||
strBuf << state.line[0]
|
||||
state.consume 1
|
||||
end
|
||||
end
|
||||
|
||||
flushStrBuf.()
|
||||
end
|
||||
|
||||
def self.handle_hash(state, ctx)
|
||||
if state.line.start_with?('#`')
|
||||
state.consume(2)
|
||||
code = String.new
|
||||
while state.line.size > 0 and state.line[0] != '`'
|
||||
code << state.line[0]
|
||||
state.consume 1
|
||||
end
|
||||
if not state.line.start_with?('`')
|
||||
raise "unterminated \#` at #{state.errCtx}"
|
||||
end
|
||||
state.consume 1
|
||||
state.output.add_code_nl("__renderer.inline_code(#{code.dump})")
|
||||
elsif state.line.start_with?('#{')
|
||||
state.consume 2
|
||||
expr = read_balanced(state, ctx)
|
||||
if not state.line.start_with?('}')
|
||||
raise "unterminated \#{} at #{state.errCtx}"
|
||||
end
|
||||
state.consume 1
|
||||
|
||||
state.output.add_expr expr
|
||||
elsif state.line.start_with?('#(')
|
||||
state.consume 2
|
||||
expr = read_balanced(state, ctx)
|
||||
if not state.line.start_with?(')')
|
||||
raise "unterminated \#() at #{state.errCtx}"
|
||||
end
|
||||
state.consume 1
|
||||
|
||||
state.output.add_code_nl "__renderer.inline_math #{expr.dump}"
|
||||
else
|
||||
raise "unexpected hash at #{state.errCtx}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.read_balanced(state, ctx)
|
||||
ret = String.new
|
||||
|
||||
stack = []
|
||||
|
||||
flipper = {
|
||||
"(" => ")",
|
||||
"{" => "}",
|
||||
}
|
||||
|
||||
revFlipper = {}
|
||||
flipper.each { |k, v|
|
||||
revFlipper[v] = k
|
||||
}
|
||||
|
||||
while state.line.size > 0 and state.line[0] != '`'
|
||||
chr = state.line[0]
|
||||
|
||||
fromFlipper = flipper[chr]
|
||||
fromRevFlipper = revFlipper[chr]
|
||||
|
||||
if not fromFlipper.nil?
|
||||
stack.push fromFlipper
|
||||
end
|
||||
if not fromRevFlipper.nil?
|
||||
return ret.freeze if stack.empty?
|
||||
|
||||
have = stack.pop
|
||||
if have != chr
|
||||
raise "unbalanced #{have}, got #{chr}"
|
||||
end
|
||||
end
|
||||
|
||||
ret << chr
|
||||
state.consume 1
|
||||
end
|
||||
|
||||
ret.freeze
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue