Monday, February 3, 2014

Refining the parser's API

The current interface for STLParser has some problems.  First, it requires the creation of a new parser object for each file to be parsed.  Second, it's not really obvious upon inspection which method starts the parsing process.

Ideally the code should be usable in a fashion similar to the following:

     STLParser parser = new STLParser()
     STLModel model = parser.parse("filename.stl")
     STLModel model2 = parser.parse("filename2.stl")
To achieve this goal, we'll do things.  First, instead of storing the TokenScanner as a property that is accessed from the parser methods, it will be created as a local variable inside of the parse() method, and passed into each parser method.  This has the added advantage of making the parser threadsafe.

So our new parse method will look something like this:

     public STLModel parse(String filename) {
          TokenScanner scanner = new TokenScanner(filename)
          stl(scanner)
     }

The updated parse methods will look like the following:

        private STLModel stl(TokenScanner scanner) {
                List<STLSolid> solids = []
                //There will always be at least one solid in a valid STL file
                //so we don't need to peek()
                solids << solid(scanner)

                while(scanner.peek(Token.SOLID)) {
                        solids << solid(scanner)
                }
                new STLModel(solids)
        }

        private STLSolid solid(TokenScanner scanner) {
                scanner.advance(Token.SOLID)
                STLSolid solid = new STLSolid()

                if (!scanner.peek(Token.FACET)) {
                        solid.name = scanner.advance(Token.STRING)
                }

                List<STLFacet> facets = []
                while(scanner.peek(Token.FACET)) {
                        STLFacet facet = facet(scanner)
                        facets << facet
                }

                solid.facets = facets
                scanner.advance(Token.ENDSOLID)

                //the string is optional, so peek for name, and make sure it's not the
                //start of a second solid.
                if (!scanner.peek(Token.SOLID) && scanner.peek(Token.STRING)) {
                        def endName = scanner.advance(Token.STRING)

                        //if the name from the start of the solid doesn't match
                        //the name from the end, then part of the file is missing
                        //or corrupt, so throw a ParseException.
                        if (solid.name != endName) throw new ParseException("Invalid name: '${endName}'.  Expected '${solid.name}'".toString(),-1)
                }
                solid
        }

        private STLFacet facet(TokenScanner scanner) {
                scanner.advance(Token.FACET)
                STLNormal normal = normal(scanner)
                STLLoop loop = loop(scanner)
                scanner.advance(Token.ENDFACET)
                new STLFacet(normal: normal, loop: loop)
        }

        private STLNormal normal(TokenScanner scanner) {
                scanner.advance(Token.NORMAL)
                double x = scanner.advance(Token.NUMBER) as double
                double y = scanner.advance(Token.NUMBER) as double
                double z = scanner.advance(Token.NUMBER) as double
                new STLNormal(x: x, y: y, z: z)
        }

        private STLLoop loop(TokenScanner scanner) {
                scanner.advance(Token.OUTER)
                scanner.advance(Token.LOOP)

                STLVertex v1 = vertex(scanner)
                STLVertex v2 = vertex(scanner)
                STLVertex v3 = vertex(scanner)

                scanner.advance(Token.ENDLOOP)
                new STLLoop(vertices: [v1,v2,v3])
        }

        private STLVertex vertex(TokenScanner scanner) {
                scanner.advance(Token.VERTEX)
                double x = scanner.advance(Token.NUMBER) as double
                double y = scanner.advance(Token.NUMBER) as double
                double z = scanner.advance(Token.NUMBER) as double
                new STLVertex(x: x, y: y, z: z)
        }

No comments:

Post a Comment