svnno****@sourc*****
svnno****@sourc*****
2010年 1月 14日 (木) 14:25:05 JST
Revision: 90 http://sourceforge.jp/projects/ngms/svn/view?view=rev&revision=90 Author: osiire Date: 2010-01-14 14:25:05 +0900 (Thu, 14 Jan 2010) Log Message: ----------- [NMShell] add command line parser Modified Paths: -------------- trunk/source/NMShell/src/info/ngms/commands/ls.scala trunk/source/NMShell/src/info/ngms/nmshell/Main.scala trunk/source/NMShell/src/info/ngms/nmshell/NMCommand.scala trunk/source/NMShell/src/info/ngms/nmshell/NMCommandStream.scala trunk/source/NMShell/src/info/ngms/nmshell/NMCommandThread.scala trunk/source/NMShell/src/info/ngms/nmshell/NMShellCommandParser.scala trunk/source/NMShell/src/info/ngms/nmshell/NMShellConfigFile.scala trunk/source/NMShell/src/info/ngms/nmshell/NMShellEnvironment.scala trunk/source/NMShell/src/info/ngms/nmshell/NMShellPipe.scala trunk/source/NMTree/src/info/ngms/nmtree/NMFileSystemTree.scala trunk/source/NMTree/src/info/ngms/nmtree/NMPath.scala Added Paths: ----------- trunk/source/NMShell/src/info/ngms/commands/cd.scala trunk/source/NMShell/test/CommandParserTest.scala Added: trunk/source/NMShell/src/info/ngms/commands/cd.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/commands/cd.scala (rev 0) +++ trunk/source/NMShell/src/info/ngms/commands/cd.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -0,0 +1,51 @@ +/* + * Next Generation Management System Project + * Copyright(c) 2009, NGMS Project Team All Rights Reserved. + */ +package info.ngms.commands + +import info.ngms.nmshell.NMCommand +import info.ngms.nmshell.NMCommandContext +import info.ngms.nmshell.NMCommandStream +import info.ngms.nmshell.NMCommandStreamKind +import info.ngms.nmshell.NMCommandParameterInfo +import info.ngms.nmshell.NMShellEnvironment +import info.ngms.nmshell.NMRawStream +import info.ngms.nmshell.RawStream +import info.ngms.nmtree.NMPath + +class cd extends NMCommand { + val name = "cd" + + private class Options { + var dst : Option[NMPath] = None + } + private val options : Options = new Options + + def parseOption( args : Array[String] ) : Unit = { + if(args.length > 0) { + val p = new NMPath(args(0)) + options.dst = Some(p) + } + } + + def parameters : List[ NMCommandParameterInfo ] = { + Nil + } + + def doWork( env : NMCommandContext ) : Unit = { + options.dst match { + case Some(path) => { + if(path.exists) + NMShellEnvironment.currentPath = path + else + print(env, "no such file or directory\n") + } + case _ => + () + } + } + + def inputStreamKind = RawStream() + def outputStreamKind = RawStream() +} Modified: trunk/source/NMShell/src/info/ngms/commands/ls.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/commands/ls.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/commands/ls.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -11,6 +11,7 @@ import info.ngms.nmshell.NMCommandParameterInfo import info.ngms.nmshell.NMRawStream import info.ngms.nmshell.RawStream +import info.ngms.nmshell.NMShellEnvironment import info.ngms.nmtree.NMTreeElements class ls extends NMCommand { @@ -24,16 +25,8 @@ } def doWork( env : NMCommandContext ) : Unit = { - env.stdout match { - case NMRawStream(stream) => { - for(c <- "this is ls command\n") { - stream.write(c) - } - stream.close - } - case _ => - println("not support stream type") - } + val current = NMShellEnvironment.currentPath + current.children.foreach( p => print(env, p.basename + "\n")) } def inputStreamKind = RawStream() Modified: trunk/source/NMShell/src/info/ngms/nmshell/Main.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/Main.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/Main.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -4,19 +4,27 @@ */ package info.ngms.nmshell +import java.io.OutputStreamWriter import info.ngms.nmtree.NMTree import info.ngms.nmtree.NMPath import info.ngms.nmtree.NMTreeImplementations object Main { + + private def parseArgs( args : Array[String] ) : Unit = { + + } + def main(args : Array[String]) : Unit = { + parseArgs(args) NMTree.init( NMShellEnvironment ) val root = new NMPath(NMPath.root) val config : NMShellConfigFile = new NMShellConfigFile(NMShellConfigFile.defaultPath) config.rootType match { - case NMTreeImplementations.RowFileSystem => + case NMTreeImplementations.RowFileSystem => { val fs = new info.ngms.nmtree.NMFileSystemTree( config.rootPath, root ) NMTree.mount( root, fs ) + } } while(true) mainLoop() @@ -26,30 +34,52 @@ print(NMShellEnvironment.currentPath.toString + " $") } + private class CommandStruct { + var parsed : Command = null + var instance : NMCommand = null + var context : NMCommandContext = null + } + def mainLoop() : Unit = { showPrompt() val line = readLine() try { - val cmds = NMShellCommandParser.parse(NMShellCommandParser.line, line).get - cmds match { - case Nil => + val cmd = NMShellCommandParser.parse(NMShellCommandParser.line, line).get + cmd match { + case Exit => + System.exit(0) + case Statements(Nil) => () - case _ => - NMShellCommandHistory.add(line) - val cmdInstances = cmds.map( n => SearchCommandInstance(n._1, n._2) ) - val contexts = cmdInstances.map( cmd => new NMCommandContext(cmd.name) ) - val cmdp = cmdInstances.zip(contexts) - connectCommandInstances(cmdp) - NMShellEnvironment.startCommandJoiner(cmdInstances.length) - cmdp.foreach(spawnCommand) - NMShellEnvironment.join() + case Statements(sts) => + //NMShellCommandHistory.add(sts) + sts.foreach( st => { + val cts = + st.cmds.map( parsed => { + val ct = new CommandStruct; + ct.parsed = parsed + ct.instance = SearchCommandInstance(parsed.name) + ct.context = new NMCommandContext(parsed.name) + ct + }) + // 以下3行は順番に注意 + cts.foreach(parseCommandArgs) + connectCommandInstances(cts) + cts.foreach(redirect) + if(!st.background) { + NMShellEnvironment.startCommandJoiner(cts.length) + cts.foreach(spawnCommand) + NMShellEnvironment.join() + } else { + cts.foreach(spawnCommand) + } + }) } } catch { case _ => () } } - def SearchCommandInstance( name : String, args : List[String] ) : NMCommand = { + def SearchCommandInstance( name : String ) : NMCommand = { try { Class.forName("info.ngms.commands." + name).newInstance().asInstanceOf[NMCommand] } catch { @@ -60,20 +90,62 @@ } } - private def connectCommandInstances( cmds : List[(NMCommand, NMCommandContext)] ) { + private def parseCommandArgs(ct : CommandStruct ) : Unit = { + ct.instance.parseOption(ct.parsed.args.toArray) + } + + private def redirect( ct : CommandStruct ) : Unit = { + // input redirect + ct.parsed.input_path match { + case Some(path) if path.exists => + () //未実装 + case _ => + () + } + // output redirect + ct.parsed.out_redirects.foreach( redirect => { + redirect match { + case Dup(frm, dst) => () // 未実装 + case Redirect(NumDescriptor(n), rtype, path) => + setStream(ct.context, n, + NMRawStream(new Stream[Int] { + val stream = NMTree.createStream(path) + val writer = new OutputStreamWriter(stream.output) + def read = None + def write(x : Int) = writer.write(x) + def flush = writer.flush + def close = { writer.close; stream.close } + })) + case _ => () + } + }) + } + + private def setStream( instance : NMCommandContext, n : Int, stream : NMCommandStream ) : Unit = { + n match { + case 1 => + instance.stdout = stream + case 2 => + instance.stderr = stream + case _ => + () + } + } + + private def connectCommandInstances( cts : List[CommandStruct] ) { var stdin = NMShellEnvironment.stdin var stdout = NMShellEnvironment.stdout var stderr = NMShellEnvironment.stderr - cmds.head._2.stdin = stdin - cmds.head._2.stderr = stderr - cmds.last._2.stdout = stdout - cmds.zip(cmds.tail).foreach { + cts.head.context.stdin = stdin + cts.head.context.stderr = stderr + cts.last.context.stdout = stdout + cts.zip(cts.tail).foreach { x => - val cmd1 = x._1._1 - val ctx1 = x._1._2 - val cmd2 = x._2._1 - val ctx2 = x._2._2 + val cmd1 = x._1.instance + val ctx1 = x._1.context + val cmd2 = x._2.instance + val ctx2 = x._2.context if(cmd1.outputStreamKind == cmd2.inputStreamKind){ ctx1.stdout = createStream(cmd1.outputStreamKind) @@ -99,8 +171,8 @@ } } - def spawnCommand( cmdp : (NMCommand, NMCommandContext) ) : Unit = { - val runnable : Runnable = new NMCommandThread( cmdp._1, cmdp._2 ) + def spawnCommand( ct : CommandStruct ) : Unit = { + val runnable : Runnable = new NMCommandThread( ct.instance, ct.context ) (new Thread( runnable )).start } } Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMCommand.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMCommand.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMCommand.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -25,4 +25,25 @@ * @return 出力ストリームの種類 */ def outputStreamKind : NMCommandStreamKind + + def print_stream( stream : NMCommandStream, msg : String ) : Unit = { + stream match { + case NMRawStream(stream) => { + for(c <- msg) { + stream.write(c) + } + stream.flush + } + case _ => + () + } + } + + def print( env : NMCommandContext, msg : String ) : Unit = { + print_stream(env.stdout, msg) + } + + def print_err( env : NMCommandContext, msg : String ) : Unit = { + print_stream(env.stderr, msg) + } } Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMCommandStream.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMCommandStream.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMCommandStream.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -44,6 +44,11 @@ def read() : Option[T] /** + * ストリームをフラッシュする + */ + def flush() : Unit + + /** * ストリームを閉じる。 */ def close : Unit Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMCommandThread.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMCommandThread.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMCommandThread.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -10,7 +10,9 @@ NMShellEnvironment.addCommandContext(Thread.currentThread.getId(), context) cmd.doWork( context ) } catch { - case _ => () + case e => + print(e.getMessage) + e.printStackTrace } finally { NMShellEnvironment.removeCommandContext(Thread.currentThread.getId()) } Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMShellCommandParser.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMShellCommandParser.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMShellCommandParser.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -6,13 +6,127 @@ import scala.util.parsing.combinator.Parsers import scala.util.parsing.combinator.RegexParsers +import info.ngms.nmtree.NMPath +private[nmshell] abstract class Line +private[nmshell] case class Statements( sts : List[Statement]) extends Line +private[nmshell] case object Exit extends Line + +private[nmshell] class Statement (val cmds : List[Command]) { + var background : Boolean = false +} + +private[nmshell] abstract class RedirectType +private[nmshell] case object NormalRedirect extends RedirectType +private[nmshell] case object AppendRedirect extends RedirectType + +private[nmshell] abstract class OutRedirect +private[nmshell] case class Dup( val frm : Descriptor, val dst : Descriptor ) extends OutRedirect +private[nmshell] case class Redirect( val frm : Descriptor, val rtype : RedirectType, val path : NMPath ) extends OutRedirect + +private[nmshell] abstract class Descriptor +private[nmshell] case object Close extends Descriptor +private[nmshell] case class NumDescriptor(n : Int) extends Descriptor + +private[nmshell] abstract class OutCase +private[nmshell] case class OutPath( path : NMPath ) extends OutCase +private[nmshell] case class OutDescriptor( dest : Descriptor ) extends OutCase + +class Command( val name : String, val args : List[String]) { + var out_redirects : List[OutRedirect] = Nil + var input_path : Option[NMPath] = None +} + object NMShellCommandParser extends RegexParsers { - def line = repsep( cmd, pipe ) <~ rep(space) + def line = statements | exit + + def exit = ":exit" <~ optional_spaces ^^ { case _ => Exit } + + def statements = rep1sep(statement, statement_sep) <~ optional_spaces ^^ { + case sts => Statements(sts) + } + def statement_sep = ";" <~ rep(";") + + def statement = statement_body ~ opt(background) <~ optional_spaces ^^ { + case st ~ Some(_) => { st.background = true; st } + case st ~ None => st + } + def background = "&" + + def statement_body = head_exp ~ opt(piped_tail) ^^ { + case h ~ Some(exps) => new Statement( h :: exps ) + case h ~ None => new Statement( List(h) ) + } + + def head_exp = (exp ~ opt(in_redirect_exp)) ^^ { + case exp ~ Some(path) => { exp.input_path = Some(path); exp } + case exp ~ None => exp + } + + def in_redirect_exp = "<" ~ spaces ~> path + def piped_tail = pipe ~> rep1sep( exp, pipe ) def pipe = "|" - def cmd = cmdName ~ rep(space) ~ repsep(arg, spaces) <~ rep(space) ^^ { case cmd ~ sp ~ args => (cmd, args) } - def cmdName = "[^ ]+".r - def arg = "[^| ]+".r - def space = " " + + def exp : Parser[Command] = (((optional_spaces ~> command) <~ optional_spaces) ~ out_redirects) <~ optional_spaces ^^ { + case cmd ~ rs => { + cmd.out_redirects = rs + cmd + } + } + + def dd(x : Option[Descriptor]) = x.getOrElse(NumDescriptor(1)) + + def out_redirects = repsep( out_redirect, spaces ) <~ optional_spaces + + def out_redirect = ((opt(file_descriptor) ~ out_redirect_symbol ~ out_case1) | (opt(file_descriptor) ~ out_redirect_symbol ~ out_case2)) ^^ { + case frm ~ _ ~ OutDescriptor(dst) => Dup( dd(frm), dst ) + case frm ~ rtype ~ OutPath(path) => Redirect( dd(frm), rtype, path ) + } + + def path = cmd_string ^^ { case p => new NMPath(p) } + + def out_case1 : Parser[OutCase] = "&" ~> opt(file_descriptor) ^^ { + case None => OutDescriptor(NumDescriptor(1)) + case Some(d : Descriptor) => OutDescriptor(d) + } + def out_case2 : Parser[OutCase] = optional_spaces ~> path ^^ { case path => OutPath(path) } + + def out_redirect_symbol = (">" | ">>") ^^ { + case ">" => NormalRedirect + case ">>" => AppendRedirect + } + + def file_descriptor = (number | "-") ^^ { + case "-" => Close + case num => NumDescriptor(Integer.parseInt(num)) + } + def number = "[1-9]+[0-9]*".r + + def command = command_name ~ repsep(cmd_string, spaces) ^^ { + case name ~ args => new Command(name, args) + } + def command_name = "[^ :<>|&`\"]+".r + + def cmd_string = ("\"" ~> double_quote_escaped_string) <~ "\"" | space_escaped_string + + def double_quote_escaped_string = rep(double_quote_escaped_char) ^^ { + case l => l.foldLeft ("") { (a,b) => a + b } + } + + def double_quote_escaped_char = non_double_quote_char | double_double_quote + def non_double_quote_char = "[^\"<>|&`]".r + def double_double_quote = "\"\"" ^^ { case _ => "\"" } + + def space_escaped_string = rep(space_escaped_char) ^^ { + case l => l.foldLeft ("") { (a,b) => a + b } + } + + def space_escaped_char = non_space_char | escape_space_char + def non_space_char = "[^ <>|&`]".r + def escape_space_char = """\ """ + + def expand_command = "`" ~> command <~ "`" + def spaces = " +".r + def optional_spaces = opt(spaces) } Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMShellConfigFile.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMShellConfigFile.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMShellConfigFile.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -6,6 +6,7 @@ object NMShellConfigFile { val defaultPath = "share/ngms/ngms.conf" + val userPath = "~/.ngms/ngms.conf" } /** @@ -13,6 +14,7 @@ */ class NMShellConfigFile ( path : String ) { // 未実装 - val rootPath = "/" + val rootPath = "./share/ngms/data" + val initialUser = "root" val rootType = info.ngms.nmtree.NMTreeImplementations.RowFileSystem } \ No newline at end of file Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMShellEnvironment.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMShellEnvironment.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMShellEnvironment.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -6,6 +6,7 @@ import info.ngms.nmtree.NMTree import info.ngms.nmtree.NMPath +import info.ngms.nmtree.NMUser import info.ngms.nmtree.NMTreeElements import info.ngms.nmtree.NMStreamElem import info.ngms.nmtree.NMTreeElements @@ -35,7 +36,23 @@ pc.set(pc.take + ( id -> c )) } + def closeStream( stream : NMCommandStream ) : Unit = { + stream match { + case NMRawStream(stream) => stream.close + case _ => () + } + } + def removeCommandContext( id : Long ) : Unit = { + val context = getCommandContext(id) + context match { + case Some(context) => { + closeStream(context.stdin) + closeStream(context.stdout) + closeStream(context.stderr) + } + case _ => () + } pc.set(pc.take - id ) commandEndNotify.write(()) } @@ -61,37 +78,45 @@ * 標準入力 */ val stdin : NMCommandStream = { - NMRawStream(new Stream[Int]{ - def read = Some(System.in.read) - def write(x : Int) = () - def close = () - }) + NMRawStream(new Stream[Int]{ + def read = Some(System.in.read) + def write(x : Int) = () + def flush = () + def close = () + }) } /** * 標準出力 */ val stdout : NMCommandStream = { - NMRawStream(new Stream[Int]{ - def read = None - def write(x : Int) = System.out.write(x) - def close = () - }) + NMRawStream(new Stream[Int]{ + def read = None + def write(x : Int) = System.out.write(x) + def flush = () + def close = () + }) } /** * 標準エラー */ val stderr : NMCommandStream = { - NMRawStream(new Stream[Int]{ - def read = None - def write(x : Int) = System.err.write(x) - def close = () - }) - } + NMRawStream(new Stream[Int]{ + def read = None + def write(x : Int) = System.err.write(x) + def flush = () + def close = () + }) + } /** - * + * 現在のパス */ var currentPath : NMPath = new NMPath(NMPath.root) + + /** + * 現在のユーザー + */ + var currentUser : NMUser = NMUser.root } Modified: trunk/source/NMShell/src/info/ngms/nmshell/NMShellPipe.scala =================================================================== --- trunk/source/NMShell/src/info/ngms/nmshell/NMShellPipe.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMShell/src/info/ngms/nmshell/NMShellPipe.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -26,6 +26,8 @@ } } + def flush() = () + def close { mbox send End } Added: trunk/source/NMShell/test/CommandParserTest.scala =================================================================== --- trunk/source/NMShell/test/CommandParserTest.scala (rev 0) +++ trunk/source/NMShell/test/CommandParserTest.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -0,0 +1,76 @@ +package info.ngms.nmshell.test + +import org.scalatest.FunSuite +import org.scalatest.BeforeAndAfterEach +import info.ngms.nmshell._ +import info.ngms.nmshell.NMShellCommandParser._ + +class CommandParserTest extends FunSuite with BeforeAndAfterEach { + + test("name") { + val input = "cd /config" + val line = NMShellCommandParser.parse(NMShellCommandParser.line, input).get + line match { + case Exit => assert(false) + case Statements(sts) => { + assert(sts.length == 1) + assert(sts.head.cmds.head.name === "cd") + assert(sts.head.cmds.head.args.head === "/config") + } + } + } + + test("pipe") { + val input = " foo -la | bar -args " + val line = NMShellCommandParser.parse(NMShellCommandParser.line, input).get + line match { + case Exit => assert(false) + case Statements(st :: Nil) => { + st.cmds match { + case cmd1 :: cmd2 :: Nil => { + assert(cmd1.name === "foo") + assert(cmd2.name === "bar") + assert(cmd2.args.head === "-args") + } + case _ => assert(false) + } + } + case _ => assert(false) + } + } + + test("exit") { + val input = ":exit" + val line = NMShellCommandParser.parse(NMShellCommandParser.line, input).get + line match { + case Exit => () + case _ => assert(false) + } + } + + test("redirect1") { + val input = " foo -la > /path/to" + val line = NMShellCommandParser.parse(NMShellCommandParser.line, input).get + line match { + case Statements(st :: Nil) => { + st.cmds match { + case cmd1 :: Nil => { + assert(cmd1.name === "foo") + assert(cmd1.out_redirects.length === 1) + cmd1.out_redirects.head match { + case Redirect(NumDescriptor(n), NormalRedirect, path) => { + assert(n === 1) + assert(path.canonical === "/path/to") + } + case _ => assert(false) + } + } + case _ => assert(false) + } + } + case _ => assert(false) + } + } +} + + Modified: trunk/source/NMTree/src/info/ngms/nmtree/NMFileSystemTree.scala =================================================================== --- trunk/source/NMTree/src/info/ngms/nmtree/NMFileSystemTree.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMTree/src/info/ngms/nmtree/NMFileSystemTree.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -33,7 +33,9 @@ file.createNewFile() new { val input : InputStream = new FileInputStream( file ) - val output : OutputStream = new FileOutputStream( file ) + val output : OutputStream = { + new FileOutputStream( file ) + } def close : Unit = input.close output.close Modified: trunk/source/NMTree/src/info/ngms/nmtree/NMPath.scala =================================================================== --- trunk/source/NMTree/src/info/ngms/nmtree/NMPath.scala 2010-01-13 02:02:50 UTC (rev 89) +++ trunk/source/NMTree/src/info/ngms/nmtree/NMPath.scala 2010-01-14 05:25:05 UTC (rev 90) @@ -133,21 +133,24 @@ } private object PathParser extends RegexParsers { - def emptyName( e : Element ) = { - e match { - case Name(n) if (n.length == 0) => true - case _ => false - } - } - def seps = separator <~ rep(separator) + def emptyName( e : Element ) = { + e match { + case Name(n) if (n.length == 0) => true + case _ => false + } + } - def path = opt(seps) ~ repsep( elem, seps ) ~ opt(seps) ^^ - { case head ~ elems ~ _ => new InnerPath( head, elems.filter( e => !emptyName(e)))} - def elem = "[^/]+".r ^^ { - case "." => DOT - case ".." => UP - case n => Name(n) - } + def seps = separator <~ rep(separator) + + def elem = "[^/]+".r ^^ { + case "." => DOT + case ".." => UP + case n => Name(n) + } + + def path = opt(seps) ~ repsep( elem, seps ) ~ opt(seps) ^^ { + case head ~ elems ~ _ => new InnerPath( head, elems.filter( e => !emptyName(e))) + } } private def makeInnerPath( path : String ) : InnerPath = {