Exemple #1
0
func (p *Parser) literal() (node *tp.Instruction) {
	token := p.pop()
	switch token.Lexeme {
	case STRING:
		node = tp.MakeText(token.Value, token.LineNumber)
	case REGEXP:
		node = tp.MakeFunctionCall("regexp",
			tp.ListInstructions(tp.MakeText(token.Value, token.LineNumber),
				tp.MakeText(token.ExtraValue, token.LineNumber)),
			nil,
			token.LineNumber)
	case POS:
		node = tp.MakePosition(token.Value, token.LineNumber)
	}
	return node
}
Exemple #2
0
func (p *Parser) read() (node *tp.Instruction) {
	p.pop() // pop the "read" keyword
	readLineNo := p.peek().LineNumber
	if p.peek().Lexeme != LPAREN {
		p.error("argument list expected for read")
	}
	p.pop() // pop the lparen
	if p.peek().Lexeme != STRING {
		p.error("`read` requires a literal string argument")
	}
	readPath := p.pop().Value
	readDir := ""
	if p.peek().Lexeme == COMMA {
		p.pop()
		if p.peek().Lexeme != STRING {
			p.error("second argument to `read` must be a literal string")
		}
		readDir = p.pop().Value
		if filepath.IsAbs(readDir) {
			panic(fmt.Sprintf("%s:%d -- second argument to `read` must be a relative path", p.FileName, readLineNo))
		}
	}
	if p.peek().Lexeme != RPAREN {
		p.error("unterminated argument list in read")
	}
	p.pop() // pop the rparen

	// make sure we're not trying to read outside the project folder
	fullReadPath := ""
	if len(readDir) > 0 {
		fullReadPath = filepath.Clean(filepath.Join(p.ProjectPath, readDir, readPath))
	} else {
		fullReadPath = filepath.Clean(filepath.Join(p.ProjectPath, p.ScriptPath, readPath))
	}
	absReadPath, err := filepath.Abs(fullReadPath)
	if err != nil {
		msg := fmt.Sprintf("%s:%d -- `read` could not resolve the full path to %s", p.FileName, readLineNo, readPath)
		panic(msg)
	}
	if !strings.HasPrefix(absReadPath, filepath.Join(p.ProjectPath)) {
		msg := fmt.Sprintf("%s:%d -- `read` cannot open files outside the project folder", p.FileName, readLineNo)
		panic(msg)
	}

	contents, err := ioutil.ReadFile(absReadPath)
	if err != nil { // can't use p.error because it's not a syntax error
		var msgPath string
		if len(readDir) == 0 {
			msgPath = filepath.Join(p.ScriptPath, readPath)
		} else {
			msgPath = filepath.Join(readDir, readPath)
		}
		msg := fmt.Sprintf("%s:%d -- `read` could not open %s", p.FileName, readLineNo, msgPath)
		panic(msg)
	}
	node = tp.MakeText(string(contents), readLineNo)
	return node
}
Exemple #3
0
func (p *Parser) variable(ns string) (node *tp.Instruction) {
	// ns = strings.Split(ns, ",")[0] // only use the first namespace -- can't efficiently search namespaces for global vars
	token := p.pop()
	lexeme, name, lineNo := token.Lexeme, token.Value, token.LineNumber
	sigil := "$"
	if lexeme == LVAR {
		sigil = "%"
	}
	var val *tp.Instruction
	var block []*tp.Instruction
	if p.peek().Lexeme == EQUAL {
		p.pop() // pop the equal sign
		switch p.peek().Lexeme {
		case STRING, REGEXP, POS, READ, ID, TYPE, GVAR, LVAR, LPAREN:
			val = p.expression()
		default:
			p.error("invalid expression in assignment to " + sigil + name)
		}
	}
	if p.peek().Lexeme == LBRACE {
		block = p.block()
	}
	if lexeme == LVAR {
		node = tp.MakeLocalVar(name, val, block, lineNo)
	} else {
		fullVarName := name
		// if the namespace is 'tritium', leave it off -- necessary to avoid breaking all that global capture stuff
		if ns != "tritium" {
			fullVarName = fmt.Sprintf("%s.%s", ns, name)
		}
		args := tp.ListInstructions(tp.MakeText(fullVarName, lineNo))
		if val != nil {
			args = append(args, val)
		}
		node = tp.MakeFunctionCall("var", args, block, lineNo)
	}
	return node
}
Exemple #4
0
func (p *Parser) expression() (node *tp.Instruction) {
	terms := tp.ListInstructions(p.term())
	for p.peek().Lexeme == PLUS {
		p.pop() // pop the plus sign
		switch p.peek().Lexeme {
		case STRING, REGEXP, POS, READ, ID, TYPE, GVAR, LVAR, LPAREN:
			rhs := p.term()
			last := len(terms) - 1
			if terms[last].GetType() == constants.Instruction_TEXT && rhs.GetType() == constants.Instruction_TEXT {
				terms[last] = tp.MakeText(terms[last].GetValue()+rhs.GetValue(), terms[last].GetLineNumber())
			} else {
				terms = append(terms, rhs)
			}
		default:
			p.error("argument to `+` must be a self-contained expression")
		}
	}
	if len(terms) > 1 {
		node = tp.FoldLeft("concat", terms[0], terms[1:len(terms)])
	} else {
		node = terms[0]
	}
	return node
}
Exemple #5
0
func (p *Parser) call(funcName *Token) (node *tp.Instruction) {
	funcNameStr := funcName.Value // grab the function name
	funcLineNo := funcName.LineNumber
	if p.peek().Lexeme != LPAREN {
		p.error("parenthesized argument list expected in call to " + funcNameStr)
	}
	p.pop() // pop the lparen

	ords, kwdnames, kwdvals := p.arguments(funcNameStr) // gather the arguments
	numArgs := len(ords)

	// this will never happen because p.arguments() only returns when it encounters an rparen
	if p.peek().Lexeme != RPAREN {
		p.error("unterminated argument list in call to " + funcNameStr)
	}
	p.pop() // pop the rparen
	var block []*tp.Instruction
	if p.peek().Lexeme == LBRACE {
		block = p.block()
	}

	// Expand keyword args
	if kwdnames != nil && kwdvals != nil {
		kwdToGensym := make(map[string]string, len(kwdnames))
		outer := tp.ListInstructions()
		for i, k := range kwdnames {
			tempname := p.gensym()
			tempvar := tp.MakeFunctionCall("var",
				tp.ListInstructions(tp.MakeText(tempname, funcLineNo),
					kwdvals[i]),
				nil, funcLineNo)
			outer = append(outer, tempvar)
			kwdToGensym[k] = tempname
		}
		inner := tp.ListInstructions()
		for _, k := range kwdnames {
			getter := tp.MakeFunctionCall("var",
				tp.ListInstructions(tp.MakeText(kwdToGensym[k], funcLineNo)),
				nil, funcLineNo)
			setter := tp.MakeFunctionCall("set",
				tp.ListInstructions(tp.MakeText(k, funcLineNo), getter),
				nil, funcLineNo)
			inner = append(inner, setter)
		}
		if block != nil {
			for _, v := range block {
				inner = append(inner, v)
			}
		}
		theCall := tp.MakeFunctionCall(funcNameStr, ords, inner, funcLineNo)
		outer = append(outer, theCall)
		node = tp.MakeBlock(outer, funcLineNo)

	} else if funcNameStr == "concat" && numArgs > 2 {
		// expand variadic concat into nested binary concats
		lhs := tp.FoldLeft("concat", ords[0], ords[1:numArgs-1])
		rhs := ords[numArgs-1]
		node = tp.MakeFunctionCall("concat", tp.ListInstructions(lhs, rhs), block, funcLineNo)
	} else if funcNameStr == "log" && numArgs > 1 {
		// expand variadic log into composition of log and concat
		cats := tp.FoldLeft("concat", ords[0], ords[1:])
		node = tp.MakeFunctionCall("log", tp.ListInstructions(cats), block, funcLineNo)
	} else {
		node = tp.MakeFunctionCall(funcNameStr, ords, block, funcLineNo)
	}
	// if it's not a root file, we can assume that it's a user-called function
	if !p.CompilingMixer /* p.RootFile == false && IncludeSelectorInfo == true */ {
		node.IsUserCalled = proto.Bool(true)
	}
	return node
}
Exemple #6
0
func (p *Parser) statement() (node *tp.Instruction) {
	switch p.peek().Lexeme {
	case IMPORT, OPTIONAL:
		optional := false
		if p.peek().Lexeme == OPTIONAL {
			optional = true
		}
		if p.inFunc {
			panic(fmt.Sprintf("|%s:%d -- imports not allowed inside function definitions", p.FileName, p.peek().LineNumber))
		}
		token := p.pop() // pop the "@import" or "@optional" token (includes importee)
		importPath := token.Value

		layered := false
		appliedLayers := p.AppliedLayers
		if strings.Index(importPath, "@") != -1 {
			if len(p.Layers) == 0 {
				if !optional {
					panic(fmt.Sprintf("%s:%d -- required layer not provided; please make sure you've specified all necessary layers in the start-up options", p.FileName, token.LineNumber))
				} else {
					// make a no-op if the import is optional and no layer has been provided
					node = tp.MakeText("", token.LineNumber)
					return
				}
			}
			if len(appliedLayers) > 0 {
				tmpSlice := make([]string, 2)
				tmpSlice[0] = appliedLayers
				tmpSlice[1] = p.Layers[0]
				appliedLayers = strings.Join(tmpSlice, "/")
			} else {
				appliedLayers = p.Layers[0]
			}
			importPath = strings.Replace(importPath, "@", p.Layers[0], -1)
			layered = true
		}

		scriptLocationInProject := filepath.Clean(filepath.Join(p.ScriptPath, importPath))

		// extract the root script folder from the relative path of the importee
		// (would be easier if filepath.FromSlash worked as advertised)
		dir, base := filepath.Split(p.ScriptPath)
		if len(dir) == 0 {
			dir = base
			base = ""
		}
		if dir[len(dir)-1] == os.PathSeparator {
			dir = dir[0 : len(dir)-1]
		}
		for len(base) > 0 {
			dir, base = filepath.Split(dir)
			if len(dir) == 0 {
				dir = base
				base = ""
			}
			if dir[len(dir)-1] == os.PathSeparator {
				dir = dir[0 : len(dir)-1]
			}
		}
		// make sure that the importee is under the right subfolder
		if !strings.HasPrefix(scriptLocationInProject, dir) {
			msg := fmt.Sprintf("%s:%d -- imported file must exist under the `%s` folder", p.FileName, token.LineNumber, dir)
			panic(msg)
		}

		// now make sure the file exists and give a helpful message if it's a layer file
		exists, exErr := fileutil.Exists(filepath.Join(p.ProjectPath, scriptLocationInProject))
		if !exists || exErr != nil {
			if optional {
				// make a no-op if the import is optional
				node = tp.MakeText("", token.LineNumber)
				return
			} else if layered {
				panic(fmt.Sprintf("%s:%d -- required layer (%s) has no corresponding Tritium file (want %s)", p.FileName, p.LineNumber, appliedLayers, scriptLocationInProject))
			} else {
				panic(fmt.Sprintf("%s:%d -- file to import not found (%s)", p.FileName, p.LineNumber, scriptLocationInProject))
			}
		}

		node = tp.MakeImport(scriptLocationInProject, token.LineNumber)
		if layered {
			node.Namespace = proto.String(appliedLayers) // re-use this slot to specify which layer the import is targeting
		}

	case STRING, REGEXP, POS, READ, ID, TYPE, GVAR, LVAR, LPAREN:
		node = p.expression()
	case NAMESPACE:
		p.error("`@namespace` directive must occur at the top of a file or code block")
	default:
		p.error("statement must consist of import or expression")
	}
	return node
}