<commit_before>package core

import (
	"errors"
	"fmt"
	"github.com/ofux/deluge/dsl/ast"
	"github.com/ofux/deluge/dsl/evaluator"
	"github.com/ofux/deluge/dsl/object"
	log "github.com/sirupsen/logrus"
	"sync"
	"time"
)

type DelugeStatus int

const (
	DelugeVirgin DelugeStatus = iota
	DelugeInProgress
	DelugeDoneSuccess
	DelugeDoneError
	DelugeInterrupted
)

type Deluge struct {
	ID             string
	Name           string
	GlobalDuration time.Duration
	Scenarios      map[string]*Scenario

	Status    DelugeStatus
	Mutex     *sync.Mutex
	interrupt chan struct{}
}

type delugeBuilder struct {
	name            string
	globalDuration  time.Duration
	scenarioCores   map[string]*scenarioCore
	scenarioConfigs map[string]*scenarioConfig
}

type scenarioCore struct {
	name         string
	script       ast.Node
	scriptParams []*ast.Identifier
}

type scenarioConfig struct {
	concurrent        int
	iterationDuration time.Duration
	args              *object.Hash
}

func NewDeluge(ID string, script *ast.Program) (*Deluge, error) {
	builder := &delugeBuilder{
		scenarioCores:   make(map[string]*scenarioCore),
		scenarioConfigs: make(map[string]*scenarioConfig),
	}
	ev := evaluator.NewEvaluator()
	if err := ev.AddBuiltin("deluge", builder.dslCreateDeluge); err != nil {
		log.Fatal(err.Error())
	}
	if err := ev.AddBuiltin("scenario", builder.dslCreateScenario); err != nil {
		log.Fatal(err.Error())
	}

	evaluated := ev.Eval(script, object.NewEnvironment())
	if evaluated != nil && evaluated.Type() == object.ERROR_OBJ {
		return nil, errors.New(evaluated.Inspect())
	}

	dlg := &Deluge{
		ID:             ID,
		Name:           builder.name,
		GlobalDuration: builder.globalDuration,
		Scenarios:      make(map[string]*Scenario),
		Status:         DelugeVirgin,
		Mutex:          &sync.Mutex{},
		interrupt:      make(chan struct{}),
	}
	for id, sConf := range builder.scenarioConfigs {
		if sCore, ok := builder.scenarioCores[id]; ok {
			dlg.Scenarios[id] = newScenario(
				sCore.name,
				sConf.concurrent,
				sConf.iterationDuration,
				sCore.script,
				sCore.scriptParams,
				sConf.args,
				log.New().WithField("deluge", dlg.Name),
			)
		} else {
			return nil, errors.New(fmt.Sprintf("Scenario '%s' is configured but not defined.", id))
		}
	}
	return dlg, nil
}

// Run runs the deluge asynchronously. It returns a channel that will be closed once the execution is finished.
func (d *Deluge) Run() <-chan struct{} {
	done := make(chan struct{})
	go func() {
		defer close(done)
		d.run()
	}()
	return done
}

func (d *Deluge) run() {
	log.Infof("Executing %d scenario(s)", len(d.Scenarios))
	start := time.Now()

	d.Mutex.Lock()
	if d.Status != DelugeVirgin {
		log.Warnf("Cannot run a deluge %s with status %d", d.ID, d.Status)
		return
	}
	d.Status = DelugeInProgress
	d.Mutex.Unlock()

	var waitg sync.WaitGroup
	for _, scenario := range d.Scenarios {
		waitg.Add(1)
		go func(scenario *Scenario) {
			defer waitg.Done()
			scenario.run(d.GlobalDuration, d.interrupt)
		}(scenario)
	}
	waitg.Wait()

	d.Mutex.Lock()
	d.end()
	d.Mutex.Unlock()

	log.Infof("Deluge executed %d scenario(s) in %s", len(d.Scenarios), time.Now().Sub(start).String())
}

func (d *Deluge) end() {
	if d.Status == DelugeInProgress {
		d.Status = DelugeDoneSuccess
		for _, scenario := range d.Scenarios {
			if scenario.Status == ScenarioDoneError {
				d.Status = DelugeDoneError
				break
			}
		}
	}
}

func (d *Deluge) Interrupt() {
	d.Mutex.Lock()
	if d.Status == DelugeVirgin || d.Status == DelugeInProgress {
		d.Status = DelugeInterrupted
		d.Mutex.Unlock()
		close(d.interrupt)
	} else {
		d.Mutex.Unlock()
	}
}

func (d *delugeBuilder) dslCreateDeluge(node ast.Node, args ...object.Object) object.Object {
	if len(args) != 3 {
		return evaluator.NewError(node, "Expected %d arguments at %s\n", 3, ast.PrintLocation(node))
	}

	name, ok := args[0].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 1st argument to be a string at %s\n", ast.PrintLocation(node))
	}
	d.name = name.Value

	durationStr, ok := args[1].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 2nd argument to be a string at %s\n", ast.PrintLocation(node))
	}
	duration, err := time.ParseDuration(durationStr.Value)
	if err != nil {
		return evaluator.NewError(node, "Expected 2nd argument to be a valid duration at %s. Error: %s\n", ast.PrintLocation(node), err.Error())
	}
	d.globalDuration = duration

	conf, ok := args[2].(*object.Hash)
	if !ok {
		return evaluator.NewError(node, "Expected 3rd argument to be an object at %s\n", ast.PrintLocation(node))
	}

	for scenarioId, v := range conf.Pairs {
		scenarioConf, ok := v.(*object.Hash)
		if !ok {
			return evaluator.NewError(node, "Expected scenario configuration to be an object at %s\n", ast.PrintLocation(node))
		}

		concurrentClientsHashPair, ok := scenarioConf.Get("concurrent")
		if !ok {
			return evaluator.NewError(node, "Expected 'concurrent' value in configuration at %s\n", ast.PrintLocation(node))
		}
		concurrentClients, ok := concurrentClientsHashPair.(*object.Integer)
		if !ok {
			return evaluator.NewError(node, "Expected 'concurrent' value to be an integer in configuration at %s\n", ast.PrintLocation(node))
		}

		delayHashPair, ok := scenarioConf.Get("delay")
		if !ok {
			return evaluator.NewError(node, "Expected 'delay' value in configuration at %s\n", ast.PrintLocation(node))
		}
		delayHashStr, ok := delayHashPair.(*object.String)
		if !ok {
			return evaluator.NewError(node, "Expected 'delay' value to be a valid duration in configuration at %s\n", ast.PrintLocation(node))
		}
		delayHash, err := time.ParseDuration(delayHashStr.Value)
		if err != nil {
			return evaluator.NewError(node, "Expected 'delay' value to be a valid duration in configuration at %s\n", ast.PrintLocation(node))
		}

		var argsHash *object.Hash
		if argsHashPair, ok := scenarioConf.Get("args"); ok {
			argsHash, ok = argsHashPair.(*object.Hash)
			if !ok {
				return evaluator.NewError(node, "Expected 'args' to be an object at %s\n", ast.PrintLocation(node))
			}
			argsHash.IsImmutable = true
		} else {
			argsHash = &object.Hash{
				Pairs:       map[object.HashKey]object.Object{},
				IsImmutable: true,
			}
		}

		_, ok = d.scenarioConfigs[string(scenarioId)]
		if ok {
			return evaluator.NewError(node, "Scenario '%v' is already configured", scenarioId)
		}

		d.scenarioConfigs[string(scenarioId)] = &scenarioConfig{
			concurrent:        int(concurrentClients.Value),
			iterationDuration: delayHash,
			args:              argsHash,
		}
	}

	return evaluator.NULL
}

func (d *delugeBuilder) dslCreateScenario(node ast.Node, args ...object.Object) object.Object {
	if len(args) != 3 {
		return evaluator.NewError(node, "Expected %d arguments at %s\n", 3, ast.PrintLocation(node))
	}

	scenarioId, ok := args[0].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 1st argument to be a string at %s\n", ast.PrintLocation(node))
	}

	name, ok := args[1].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 2nd argument to be a string at %s\n", ast.PrintLocation(node))
	}

	coreFunc, ok := args[2].(*object.Function)
	if !ok {
		return evaluator.NewError(node, "Expected 3rd argument to be a function at %s\n", ast.PrintLocation(node))
	}

	_, ok = d.scenarioCores[scenarioId.Value]
	if ok {
		return evaluator.NewError(node, "Scenario '%s' is already defined", scenarioId.Value)
	}

	d.scenarioCores[scenarioId.Value] = &scenarioCore{
		name:         name.Value,
		script:       coreFunc.Body,
		scriptParams: coreFunc.Parameters,
	}

	return evaluator.NULL
}
<commit_msg>Small variables renaming<commit_after>package core

import (
	"errors"
	"fmt"
	"github.com/ofux/deluge/dsl/ast"
	"github.com/ofux/deluge/dsl/evaluator"
	"github.com/ofux/deluge/dsl/object"
	log "github.com/sirupsen/logrus"
	"sync"
	"time"
)

type DelugeStatus int

const (
	DelugeVirgin DelugeStatus = iota
	DelugeInProgress
	DelugeDoneSuccess
	DelugeDoneError
	DelugeInterrupted
)

type Deluge struct {
	ID             string
	Name           string
	GlobalDuration time.Duration
	Scenarios      map[string]*Scenario

	Status    DelugeStatus
	Mutex     *sync.Mutex
	interrupt chan struct{}
}

type delugeBuilder struct {
	name            string
	globalDuration  time.Duration
	scenarioCores   map[string]*scenarioCore
	scenarioConfigs map[string]*scenarioConfig
}

type scenarioCore struct {
	name         string
	script       ast.Node
	scriptParams []*ast.Identifier
}

type scenarioConfig struct {
	concurrent        int
	iterationDuration time.Duration
	args              *object.Hash
}

func NewDeluge(ID string, script *ast.Program) (*Deluge, error) {
	builder := &delugeBuilder{
		scenarioCores:   make(map[string]*scenarioCore),
		scenarioConfigs: make(map[string]*scenarioConfig),
	}
	ev := evaluator.NewEvaluator()
	if err := ev.AddBuiltin("deluge", builder.dslCreateDeluge); err != nil {
		log.Fatal(err.Error())
	}
	if err := ev.AddBuiltin("scenario", builder.dslCreateScenario); err != nil {
		log.Fatal(err.Error())
	}

	evaluated := ev.Eval(script, object.NewEnvironment())
	if evaluated != nil && evaluated.Type() == object.ERROR_OBJ {
		return nil, errors.New(evaluated.Inspect())
	}

	dlg := &Deluge{
		ID:             ID,
		Name:           builder.name,
		GlobalDuration: builder.globalDuration,
		Scenarios:      make(map[string]*Scenario),
		Status:         DelugeVirgin,
		Mutex:          &sync.Mutex{},
		interrupt:      make(chan struct{}),
	}
	for id, sConf := range builder.scenarioConfigs {
		if sCore, ok := builder.scenarioCores[id]; ok {
			dlg.Scenarios[id] = newScenario(
				sCore.name,
				sConf.concurrent,
				sConf.iterationDuration,
				sCore.script,
				sCore.scriptParams,
				sConf.args,
				log.New().WithField("deluge", dlg.Name),
			)
		} else {
			return nil, errors.New(fmt.Sprintf("Scenario '%s' is configured but not defined.", id))
		}
	}
	return dlg, nil
}

// Run runs the deluge asynchronously. It returns a channel that will be closed once the execution is finished.
func (d *Deluge) Run() <-chan struct{} {
	done := make(chan struct{})
	go func() {
		defer close(done)
		d.run()
	}()
	return done
}

func (d *Deluge) run() {
	log.Infof("Executing %d scenario(s)", len(d.Scenarios))
	start := time.Now()

	d.Mutex.Lock()
	if d.Status != DelugeVirgin {
		log.Warnf("Cannot run a deluge %s with status %d", d.ID, d.Status)
		return
	}
	d.Status = DelugeInProgress
	d.Mutex.Unlock()

	var waitg sync.WaitGroup
	for _, scenario := range d.Scenarios {
		waitg.Add(1)
		go func(scenario *Scenario) {
			defer waitg.Done()
			scenario.run(d.GlobalDuration, d.interrupt)
		}(scenario)
	}
	waitg.Wait()

	d.Mutex.Lock()
	d.end()
	d.Mutex.Unlock()

	log.Infof("Deluge executed %d scenario(s) in %s", len(d.Scenarios), time.Now().Sub(start).String())
}

func (d *Deluge) end() {
	if d.Status == DelugeInProgress {
		d.Status = DelugeDoneSuccess
		for _, scenario := range d.Scenarios {
			if scenario.Status == ScenarioDoneError {
				d.Status = DelugeDoneError
				break
			}
		}
	}
}

func (d *Deluge) Interrupt() {
	d.Mutex.Lock()
	if d.Status == DelugeVirgin || d.Status == DelugeInProgress {
		d.Status = DelugeInterrupted
		d.Mutex.Unlock()
		close(d.interrupt)
	} else {
		d.Mutex.Unlock()
	}
}

func (d *delugeBuilder) dslCreateDeluge(node ast.Node, args ...object.Object) object.Object {
	if len(args) != 3 {
		return evaluator.NewError(node, "Expected %d arguments at %s\n", 3, ast.PrintLocation(node))
	}

	name, ok := args[0].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 1st argument to be a string at %s\n", ast.PrintLocation(node))
	}
	d.name = name.Value

	durationStr, ok := args[1].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 2nd argument to be a string at %s\n", ast.PrintLocation(node))
	}
	duration, err := time.ParseDuration(durationStr.Value)
	if err != nil {
		return evaluator.NewError(node, "Expected 2nd argument to be a valid duration at %s. Error: %s\n", ast.PrintLocation(node), err.Error())
	}
	d.globalDuration = duration

	conf, ok := args[2].(*object.Hash)
	if !ok {
		return evaluator.NewError(node, "Expected 3rd argument to be an object at %s\n", ast.PrintLocation(node))
	}

	for scenarioId, v := range conf.Pairs {
		scenarioConf, ok := v.(*object.Hash)
		if !ok {
			return evaluator.NewError(node, "Expected scenario configuration to be an object at %s\n", ast.PrintLocation(node))
		}

		concurrentClientsHashValue, ok := scenarioConf.Get("concurrent")
		if !ok {
			return evaluator.NewError(node, "Expected 'concurrent' value in configuration at %s\n", ast.PrintLocation(node))
		}
		concurrentClients, ok := concurrentClientsHashValue.(*object.Integer)
		if !ok {
			return evaluator.NewError(node, "Expected 'concurrent' value to be an integer in configuration at %s\n", ast.PrintLocation(node))
		}

		delayHashValue, ok := scenarioConf.Get("delay")
		if !ok {
			return evaluator.NewError(node, "Expected 'delay' value in configuration at %s\n", ast.PrintLocation(node))
		}
		delayHashStr, ok := delayHashValue.(*object.String)
		if !ok {
			return evaluator.NewError(node, "Expected 'delay' value to be a valid duration in configuration at %s\n", ast.PrintLocation(node))
		}
		delayHash, err := time.ParseDuration(delayHashStr.Value)
		if err != nil {
			return evaluator.NewError(node, "Expected 'delay' value to be a valid duration in configuration at %s\n", ast.PrintLocation(node))
		}

		var argsHash *object.Hash
		if argsHashValue, ok := scenarioConf.Get("args"); ok {
			argsHash, ok = argsHashValue.(*object.Hash)
			if !ok {
				return evaluator.NewError(node, "Expected 'args' to be an object at %s\n", ast.PrintLocation(node))
			}
			argsHash.IsImmutable = true
		} else {
			argsHash = &object.Hash{
				Pairs:       map[object.HashKey]object.Object{},
				IsImmutable: true,
			}
		}

		_, ok = d.scenarioConfigs[string(scenarioId)]
		if ok {
			return evaluator.NewError(node, "Scenario '%v' is already configured", scenarioId)
		}

		d.scenarioConfigs[string(scenarioId)] = &scenarioConfig{
			concurrent:        int(concurrentClients.Value),
			iterationDuration: delayHash,
			args:              argsHash,
		}
	}

	return evaluator.NULL
}

func (d *delugeBuilder) dslCreateScenario(node ast.Node, args ...object.Object) object.Object {
	if len(args) != 3 {
		return evaluator.NewError(node, "Expected %d arguments at %s\n", 3, ast.PrintLocation(node))
	}

	scenarioId, ok := args[0].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 1st argument to be a string at %s\n", ast.PrintLocation(node))
	}

	name, ok := args[1].(*object.String)
	if !ok {
		return evaluator.NewError(node, "Expected 2nd argument to be a string at %s\n", ast.PrintLocation(node))
	}

	coreFunc, ok := args[2].(*object.Function)
	if !ok {
		return evaluator.NewError(node, "Expected 3rd argument to be a function at %s\n", ast.PrintLocation(node))
	}

	_, ok = d.scenarioCores[scenarioId.Value]
	if ok {
		return evaluator.NewError(node, "Scenario '%s' is already defined", scenarioId.Value)
	}

	d.scenarioCores[scenarioId.Value] = &scenarioCore{
		name:         name.Value,
		script:       coreFunc.Body,
		scriptParams: coreFunc.Parameters,
	}

	return evaluator.NULL
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package core

import (
	"context"
	"strings"
	"sync"
	"time"

	"github.com/loadimpact/k6/core/local"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/metrics"
	"github.com/loadimpact/k6/stats"
	log "github.com/sirupsen/logrus"
	"gopkg.in/guregu/null.v3"
)

const (
	TickRate        = 1 * time.Millisecond
	MetricsRate     = 1 * time.Second
	CollectRate     = 50 * time.Millisecond
	ThresholdsRate  = 2 * time.Second
	ShutdownTimeout = 10 * time.Second

	BackoffAmount = 50 * time.Millisecond
	BackoffMax    = 10 * time.Second
)

// The Engine is the beating heart of K6.
type Engine struct {
	runLock sync.Mutex

	Executor     lib.Executor
	Options      lib.Options
	Collectors   []lib.Collector
	NoThresholds bool

	logger *log.Logger

	Metrics     map[string]*stats.Metric
	MetricsLock sync.Mutex

	Samples chan stats.SampleContainer

	// Assigned to metrics upon first received sample.
	thresholds map[string]stats.Thresholds
	submetrics map[string][]*stats.Submetric

	// Are thresholds tainted?
	thresholdsTainted bool
}

func NewEngine(ex lib.Executor, o lib.Options) (*Engine, error) {
	if ex == nil {
		ex = local.New(nil)
	}

	e := &Engine{
		Executor: ex,
		Options:  o,
		Metrics:  make(map[string]*stats.Metric),
		Samples:  make(chan stats.SampleContainer, o.MetricSamplesBufferSize.Int64),
	}
	e.SetLogger(log.StandardLogger())

	if err := ex.SetVUsMax(o.VUsMax.Int64); err != nil {
		return nil, err
	}
	if err := ex.SetVUs(o.VUs.Int64); err != nil {
		return nil, err
	}
	ex.SetPaused(o.Paused.Bool)
	ex.SetStages(o.Stages)
	ex.SetEndTime(o.Duration)
	ex.SetEndIterations(o.Iterations)

	e.thresholds = o.Thresholds
	e.submetrics = make(map[string][]*stats.Submetric)
	for name := range e.thresholds {
		if !strings.Contains(name, "{") {
			continue
		}

		parent, sm := stats.NewSubmetric(name)
		e.submetrics[parent] = append(e.submetrics[parent], sm)
	}

	return e, nil
}

func (e *Engine) setRunStatus(status int) {
	if len(e.Collectors) == 0 {
		return
	}

	for _, c := range e.Collectors {
		c.SetRunStatus(status)
	}
}

func (e *Engine) Run(ctx context.Context) error {
	e.runLock.Lock()
	defer e.runLock.Unlock()

	e.logger.Debug("Engine: Starting with parameters...")
	for i, st := range e.Executor.GetStages() {
		fields := make(log.Fields)
		if st.Target.Valid {
			fields["tgt"] = st.Target.Int64
		}
		if st.Duration.Valid {
			fields["d"] = st.Duration.Duration
		}
		e.logger.WithFields(fields).Debugf(" - stage #%d", i)
	}

	fields := make(log.Fields)
	if endTime := e.Executor.GetEndTime(); endTime.Valid {
		fields["time"] = endTime.Duration
	}
	if endIter := e.Executor.GetEndIterations(); endIter.Valid {
		fields["iter"] = endIter.Int64
	}
	e.logger.WithFields(fields).Debug(" - end conditions (if any)")

	collectorwg := sync.WaitGroup{}
	collectorctx, collectorcancel := context.WithCancel(context.Background())
	if len(e.Collectors) > 0 {
		for _, collector := range e.Collectors {
			collectorwg.Add(1)
			go func(collector lib.Collector) {
				collector.Run(collectorctx)
				collectorwg.Done()
			}(collector)
		}
	}

	subctx, subcancel := context.WithCancel(context.Background())
	subwg := sync.WaitGroup{}

	// Run metrics emission.
	subwg.Add(1)
	go func() {
		e.runMetricsEmission(subctx)
		e.logger.Debug("Engine: Emission terminated")
		subwg.Done()
	}()

	// Run thresholds.
	if !e.NoThresholds {
		subwg.Add(1)
		go func() {
			e.runThresholds(subctx, subcancel)
			e.logger.Debug("Engine: Thresholds terminated")
			subwg.Done()
		}()
	}

	// Run the executor.
	errC := make(chan error)
	subwg.Add(1)
	go func() {
		errC <- e.Executor.Run(subctx, e.Samples)
		e.logger.Debug("Engine: Executor terminated")
		subwg.Done()
	}()

	defer func() {
		// Shut down subsystems.
		subcancel()

		// Process samples until the subsystems have shut down.
		// Filter out samples produced past the end of a test.
		go func() {
			if errC != nil {
				<-errC
				errC = nil
			}
			subwg.Wait()
			close(e.Samples)
		}()

		sampleContainers := []stats.SampleContainer{}
		for sc := range e.Samples {
			sampleContainers = append(sampleContainers, sc)
		}
		e.processSamples(sampleContainers)

		// Emit final metrics.
		e.emitMetrics()

		// Process final thresholds.
		if !e.NoThresholds {
			e.processThresholds(nil)
		}

		// Finally, shut down collector.
		collectorcancel()
		collectorwg.Wait()
	}()

	ticker := time.NewTicker(CollectRate)
	sampleContainers := []stats.SampleContainer{}
	for {
		select {
		case <-ticker.C:
			if len(sampleContainers) > 0 {
				e.processSamples(sampleContainers)
				sampleContainers = []stats.SampleContainer{}
			}
		case sc := <-e.Samples:
			sampleContainers = append(sampleContainers, sc)
		case err := <-errC:
			errC = nil
			if err != nil {
				e.logger.WithError(err).Debug("run: executor returned an error")
				e.setRunStatus(lib.RunStatusAbortedSystem)
				return err
			}
			e.logger.Debug("run: executor terminated")
			return nil
		case <-ctx.Done():
			e.logger.Debug("run: context expired; exiting...")
			e.setRunStatus(lib.RunStatusAbortedUser)
			return nil
		}
	}
}

func (e *Engine) IsTainted() bool {
	return e.thresholdsTainted
}

func (e *Engine) SetLogger(l *log.Logger) {
	e.logger = l
	e.Executor.SetLogger(l)
}

func (e *Engine) GetLogger() *log.Logger {
	return e.logger
}

func (e *Engine) runMetricsEmission(ctx context.Context) {
	ticker := time.NewTicker(MetricsRate)
	for {
		select {
		case <-ticker.C:
			e.emitMetrics()
		case <-ctx.Done():
			return
		}
	}
}

func (e *Engine) emitMetrics() {
	t := time.Now()

	e.processSamples([]stats.SampleContainer{stats.ConnectedSamples{
		Samples: []stats.Sample{
			{
				Time:   t,
				Metric: metrics.VUs,
				Value:  float64(e.Executor.GetVUs()),
				Tags:   e.Options.RunTags,
			}, {
				Time:   t,
				Metric: metrics.VUsMax,
				Value:  float64(e.Executor.GetVUsMax()),
				Tags:   e.Options.RunTags,
			},
		},
		Tags: e.Options.RunTags,
		Time: t,
	}})
}

func (e *Engine) runThresholds(ctx context.Context, abort func()) {
	ticker := time.NewTicker(ThresholdsRate)
	for {
		select {
		case <-ticker.C:
			e.processThresholds(abort)
		case <-ctx.Done():
			return
		}
	}
}

func (e *Engine) processThresholds(abort func()) {
	e.MetricsLock.Lock()
	defer e.MetricsLock.Unlock()

	t := e.Executor.GetTime()
	abortOnFail := false

	e.thresholdsTainted = false
	for _, m := range e.Metrics {
		if len(m.Thresholds.Thresholds) == 0 {
			continue
		}
		m.Tainted = null.BoolFrom(false)

		e.logger.WithField("m", m.Name).Debug("running thresholds")
		succ, err := m.Thresholds.Run(m.Sink, t)
		if err != nil {
			e.logger.WithField("m", m.Name).WithError(err).Error("Threshold error")
			continue
		}
		if !succ {
			e.logger.WithField("m", m.Name).Debug("Thresholds failed")
			m.Tainted = null.BoolFrom(true)
			e.thresholdsTainted = true
			if !abortOnFail && m.Thresholds.Abort {
				abortOnFail = true
			}
		}
	}

	if abortOnFail && abort != nil {
		//TODO: When sending this status we get a 422 Unprocessable Entity
		e.setRunStatus(lib.RunStatusAbortedThreshold)
		abort()
	}
}

func (e *Engine) processSamples(sampleCointainers []stats.SampleContainer) {
	if len(sampleCointainers) == 0 {
		return
	}

	e.MetricsLock.Lock()
	defer e.MetricsLock.Unlock()

	for _, sampleCointainer := range sampleCointainers {
		samples := sampleCointainer.GetSamples()

		if len(samples) == 0 {
			continue
		}

		for _, sample := range samples {
			m, ok := e.Metrics[sample.Metric.Name]
			if !ok {
				m = stats.New(sample.Metric.Name, sample.Metric.Type, sample.Metric.Contains)
				m.Thresholds = e.thresholds[m.Name]
				m.Submetrics = e.submetrics[m.Name]
				e.Metrics[m.Name] = m
			}
			m.Sink.Add(sample)

			for _, sm := range m.Submetrics {
				if !sample.Tags.Contains(sm.Tags) {
					continue
				}

				if sm.Metric == nil {
					sm.Metric = stats.New(sm.Name, sample.Metric.Type, sample.Metric.Contains)
					sm.Metric.Sub = *sm
					sm.Metric.Thresholds = e.thresholds[sm.Name]
					e.Metrics[sm.Name] = sm.Metric
				}
				sm.Metric.Sink.Add(sample)
			}
		}
	}
	if len(e.Collectors) > 0 {
		for _, collector := range e.Collectors {
			collector.Collect(sampleCointainers)
		}
	}
}
<commit_msg>Fix a bug that caused the last sample slice to be discarded<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package core

import (
	"context"
	"strings"
	"sync"
	"time"

	"github.com/loadimpact/k6/core/local"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/metrics"
	"github.com/loadimpact/k6/stats"
	log "github.com/sirupsen/logrus"
	"gopkg.in/guregu/null.v3"
)

const (
	TickRate        = 1 * time.Millisecond
	MetricsRate     = 1 * time.Second
	CollectRate     = 50 * time.Millisecond
	ThresholdsRate  = 2 * time.Second
	ShutdownTimeout = 10 * time.Second

	BackoffAmount = 50 * time.Millisecond
	BackoffMax    = 10 * time.Second
)

// The Engine is the beating heart of K6.
type Engine struct {
	runLock sync.Mutex

	Executor     lib.Executor
	Options      lib.Options
	Collectors   []lib.Collector
	NoThresholds bool

	logger *log.Logger

	Metrics     map[string]*stats.Metric
	MetricsLock sync.Mutex

	Samples chan stats.SampleContainer

	// Assigned to metrics upon first received sample.
	thresholds map[string]stats.Thresholds
	submetrics map[string][]*stats.Submetric

	// Are thresholds tainted?
	thresholdsTainted bool
}

func NewEngine(ex lib.Executor, o lib.Options) (*Engine, error) {
	if ex == nil {
		ex = local.New(nil)
	}

	e := &Engine{
		Executor: ex,
		Options:  o,
		Metrics:  make(map[string]*stats.Metric),
		Samples:  make(chan stats.SampleContainer, o.MetricSamplesBufferSize.Int64),
	}
	e.SetLogger(log.StandardLogger())

	if err := ex.SetVUsMax(o.VUsMax.Int64); err != nil {
		return nil, err
	}
	if err := ex.SetVUs(o.VUs.Int64); err != nil {
		return nil, err
	}
	ex.SetPaused(o.Paused.Bool)
	ex.SetStages(o.Stages)
	ex.SetEndTime(o.Duration)
	ex.SetEndIterations(o.Iterations)

	e.thresholds = o.Thresholds
	e.submetrics = make(map[string][]*stats.Submetric)
	for name := range e.thresholds {
		if !strings.Contains(name, "{") {
			continue
		}

		parent, sm := stats.NewSubmetric(name)
		e.submetrics[parent] = append(e.submetrics[parent], sm)
	}

	return e, nil
}

func (e *Engine) setRunStatus(status int) {
	if len(e.Collectors) == 0 {
		return
	}

	for _, c := range e.Collectors {
		c.SetRunStatus(status)
	}
}

func (e *Engine) Run(ctx context.Context) error {
	e.runLock.Lock()
	defer e.runLock.Unlock()

	e.logger.Debug("Engine: Starting with parameters...")
	for i, st := range e.Executor.GetStages() {
		fields := make(log.Fields)
		if st.Target.Valid {
			fields["tgt"] = st.Target.Int64
		}
		if st.Duration.Valid {
			fields["d"] = st.Duration.Duration
		}
		e.logger.WithFields(fields).Debugf(" - stage #%d", i)
	}

	fields := make(log.Fields)
	if endTime := e.Executor.GetEndTime(); endTime.Valid {
		fields["time"] = endTime.Duration
	}
	if endIter := e.Executor.GetEndIterations(); endIter.Valid {
		fields["iter"] = endIter.Int64
	}
	e.logger.WithFields(fields).Debug(" - end conditions (if any)")

	collectorwg := sync.WaitGroup{}
	collectorctx, collectorcancel := context.WithCancel(context.Background())
	if len(e.Collectors) > 0 {
		for _, collector := range e.Collectors {
			collectorwg.Add(1)
			go func(collector lib.Collector) {
				collector.Run(collectorctx)
				collectorwg.Done()
			}(collector)
		}
	}

	subctx, subcancel := context.WithCancel(context.Background())
	subwg := sync.WaitGroup{}

	// Run metrics emission.
	subwg.Add(1)
	go func() {
		e.runMetricsEmission(subctx)
		e.logger.Debug("Engine: Emission terminated")
		subwg.Done()
	}()

	// Run thresholds.
	if !e.NoThresholds {
		subwg.Add(1)
		go func() {
			e.runThresholds(subctx, subcancel)
			e.logger.Debug("Engine: Thresholds terminated")
			subwg.Done()
		}()
	}

	// Run the executor.
	errC := make(chan error)
	subwg.Add(1)
	go func() {
		errC <- e.Executor.Run(subctx, e.Samples)
		e.logger.Debug("Engine: Executor terminated")
		subwg.Done()
	}()

	sampleContainers := []stats.SampleContainer{}
	defer func() {
		// Shut down subsystems.
		subcancel()

		// Process samples until the subsystems have shut down.
		// Filter out samples produced past the end of a test.
		go func() {
			if errC != nil {
				<-errC
				errC = nil
			}
			subwg.Wait()
			close(e.Samples)
		}()

		for sc := range e.Samples {
			sampleContainers = append(sampleContainers, sc)
		}
		if len(sampleContainers) > 0 {
			e.processSamples(sampleContainers)
		}

		// Emit final metrics.
		e.emitMetrics()

		// Process final thresholds.
		if !e.NoThresholds {
			e.processThresholds(nil)
		}

		// Finally, shut down collector.
		collectorcancel()
		collectorwg.Wait()
	}()

	ticker := time.NewTicker(CollectRate)
	for {
		select {
		case <-ticker.C:
			if len(sampleContainers) > 0 {
				e.processSamples(sampleContainers)
				sampleContainers = []stats.SampleContainer{}
			}
		case sc := <-e.Samples:
			sampleContainers = append(sampleContainers, sc)
		case err := <-errC:
			errC = nil
			if err != nil {
				e.logger.WithError(err).Debug("run: executor returned an error")
				e.setRunStatus(lib.RunStatusAbortedSystem)
				return err
			}
			e.logger.Debug("run: executor terminated")
			return nil
		case <-ctx.Done():
			e.logger.Debug("run: context expired; exiting...")
			e.setRunStatus(lib.RunStatusAbortedUser)
			return nil
		}
	}
}

func (e *Engine) IsTainted() bool {
	return e.thresholdsTainted
}

func (e *Engine) SetLogger(l *log.Logger) {
	e.logger = l
	e.Executor.SetLogger(l)
}

func (e *Engine) GetLogger() *log.Logger {
	return e.logger
}

func (e *Engine) runMetricsEmission(ctx context.Context) {
	ticker := time.NewTicker(MetricsRate)
	for {
		select {
		case <-ticker.C:
			e.emitMetrics()
		case <-ctx.Done():
			return
		}
	}
}

func (e *Engine) emitMetrics() {
	t := time.Now()

	e.processSamples([]stats.SampleContainer{stats.ConnectedSamples{
		Samples: []stats.Sample{
			{
				Time:   t,
				Metric: metrics.VUs,
				Value:  float64(e.Executor.GetVUs()),
				Tags:   e.Options.RunTags,
			}, {
				Time:   t,
				Metric: metrics.VUsMax,
				Value:  float64(e.Executor.GetVUsMax()),
				Tags:   e.Options.RunTags,
			},
		},
		Tags: e.Options.RunTags,
		Time: t,
	}})
}

func (e *Engine) runThresholds(ctx context.Context, abort func()) {
	ticker := time.NewTicker(ThresholdsRate)
	for {
		select {
		case <-ticker.C:
			e.processThresholds(abort)
		case <-ctx.Done():
			return
		}
	}
}

func (e *Engine) processThresholds(abort func()) {
	e.MetricsLock.Lock()
	defer e.MetricsLock.Unlock()

	t := e.Executor.GetTime()
	abortOnFail := false

	e.thresholdsTainted = false
	for _, m := range e.Metrics {
		if len(m.Thresholds.Thresholds) == 0 {
			continue
		}
		m.Tainted = null.BoolFrom(false)

		e.logger.WithField("m", m.Name).Debug("running thresholds")
		succ, err := m.Thresholds.Run(m.Sink, t)
		if err != nil {
			e.logger.WithField("m", m.Name).WithError(err).Error("Threshold error")
			continue
		}
		if !succ {
			e.logger.WithField("m", m.Name).Debug("Thresholds failed")
			m.Tainted = null.BoolFrom(true)
			e.thresholdsTainted = true
			if !abortOnFail && m.Thresholds.Abort {
				abortOnFail = true
			}
		}
	}

	if abortOnFail && abort != nil {
		//TODO: When sending this status we get a 422 Unprocessable Entity
		e.setRunStatus(lib.RunStatusAbortedThreshold)
		abort()
	}
}

func (e *Engine) processSamples(sampleCointainers []stats.SampleContainer) {
	if len(sampleCointainers) == 0 {
		return
	}

	e.MetricsLock.Lock()
	defer e.MetricsLock.Unlock()

	for _, sampleCointainer := range sampleCointainers {
		samples := sampleCointainer.GetSamples()

		if len(samples) == 0 {
			continue
		}

		for _, sample := range samples {
			m, ok := e.Metrics[sample.Metric.Name]
			if !ok {
				m = stats.New(sample.Metric.Name, sample.Metric.Type, sample.Metric.Contains)
				m.Thresholds = e.thresholds[m.Name]
				m.Submetrics = e.submetrics[m.Name]
				e.Metrics[m.Name] = m
			}
			m.Sink.Add(sample)

			for _, sm := range m.Submetrics {
				if !sample.Tags.Contains(sm.Tags) {
					continue
				}

				if sm.Metric == nil {
					sm.Metric = stats.New(sm.Name, sample.Metric.Type, sample.Metric.Contains)
					sm.Metric.Sub = *sm
					sm.Metric.Thresholds = e.thresholds[sm.Name]
					e.Metrics[sm.Name] = sm.Metric
				}
				sm.Metric.Sink.Add(sample)
			}
		}
	}
	if len(e.Collectors) > 0 {
		for _, collector := range e.Collectors {
			collector.Collect(sampleCointainers)
		}
	}
}
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"io"
	"regexp"
	"sort"
	"unicode/utf8"
)

func seqFirst(seq Seq, w io.Writer, indent int) (Seq, int) {
	if !seq.IsEmpty() {
		indent = formatObject(seq.First(), indent, w)
		seq = seq.Rest()
	}
	return seq, indent
}

// TODO: maybe merge it with seqFirstAfterBreak
// or extract common part into a separate function
func seqFirstAfterSpace(seq Seq, w io.Writer, indent int, insideDefRecord bool) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		fmt.Fprint(w, " ")
		obj = seq.First()
		// Seq handling here is needed to properly format methods
		// inside defrecord
		if s, ok := obj.(Seq); ok && !obj.Equals(NIL) {
			if info := obj.GetInfo(); info != nil {
				fmt.Fprint(w, info.prefix)
				indent += utf8.RuneCountInString(info.prefix)
			}
			indent = formatSeqEx(s, w, indent+1, insideDefRecord)
		} else {
			indent = formatObject(obj, indent+1, w)
		}
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func writeNewLines(w io.Writer, prevObj Object, obj Object) int {
	cnt := newLineCount(prevObj, obj)
	for i := 0; i < cnt; i++ {
		fmt.Fprint(w, "\n")
	}
	return cnt
}

func seqFirstAfterBreak(prevObj Object, seq Seq, w io.Writer, indent int, insideDefRecord bool) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		obj = seq.First()
		writeNewLines(w, prevObj, obj)
		writeIndent(w, indent)
		// Seq handling here is needed to properly format methods
		// inside defrecord
		if s, ok := obj.(Seq); ok && !obj.Equals(NIL) {
			if info := obj.GetInfo(); info != nil {
				fmt.Fprint(w, info.prefix)
				indent += utf8.RuneCountInString(info.prefix)
			}
			indent = formatSeqEx(s, w, indent, insideDefRecord)
		} else {
			indent = formatObject(obj, indent, w)
		}
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func seqFirstAfterForcedBreak(seq Seq, w io.Writer, indent int) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		obj = seq.First()
		fmt.Fprint(w, "\n")
		writeIndent(w, indent)
		indent = formatObject(obj, indent, w)
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func formatBindings(v *Vector, w io.Writer, indent int) int {
	return v.Format(w, indent)
}

func formatVectorVertically(v *Vector, w io.Writer, indent int) int {
	fmt.Fprint(w, "[")
	newIndent := indent + 1
	for i := 0; i < v.count; i++ {
		newIndent = formatObject(v.at(i), indent+1, w)
		if i+1 < v.count {
			fmt.Fprint(w, "\n")
			writeIndent(w, indent+1)
		}
	}
	if v.count > 0 {
		if isComment(v.at(v.count - 1)) {
			fmt.Fprint(w, "\n")
			writeIndent(w, indent+1)
			newIndent = indent + 1
		}
	}
	fmt.Fprint(w, "]")
	return newIndent + 1
}

var defRegex *regexp.Regexp = regexp.MustCompile("^def.*$")
var ifRegex *regexp.Regexp = regexp.MustCompile("^if(-.+)?$")
var whenRegex *regexp.Regexp = regexp.MustCompile("^when(-.+)?$")
var doIndentRegex *regexp.Regexp = regexp.MustCompile("^(do|try|finally|go|alt!|alt!!)$")
var bodyIndentRegexes []*regexp.Regexp = []*regexp.Regexp{
	regexp.MustCompile("^(bound-fn|if|if-not|case|cond|cond->|cond->>|as->|condp|when|while|when-not|when-first|do|future|thread)$"),
	regexp.MustCompile("^(comment|doto|locking|proxy|with-[^\\s]*|reify)$"),
	regexp.MustCompile("^(defprotocol|extend|extend-protocol|extend-type|catch|let|letfn|binding|loop|for|go-loop)$"),
	regexp.MustCompile("^(doseq|dotimes|when-let|if-let|defstruct|struct-map|defmethod|testing|are|deftest|context|use-fixtures)$"),
	regexp.MustCompile("^(POST|GET|PUT|DELETE)"),
	regexp.MustCompile("^(handler-case|handle|dotrace|deftrace|match)$"),
}

func isOneAndBodyExpr(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		return defRegex.MatchString(*s.name) ||
			ifRegex.MatchString(*s.name) ||
			whenRegex.MatchString(*s.name)
	default:
		return false
	}
}

func isDoIndent(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		return doIndentRegex.MatchString(*s.name)
	default:
		return false
	}
}

func isBodyIndent(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		for _, re := range bodyIndentRegexes {
			if re.MatchString(*s.name) {
				return true
			}
		}
		return false
	default:
		return false
	}
}

func isNewLine(obj, nextObj Object) bool {
	info, nextInfo := obj.GetInfo(), nextObj.GetInfo()
	return !(info == nil || nextInfo == nil || info.endLine == nextInfo.startLine)
}

func newLineCount(obj, nextObj Object) int {
	info, nextInfo := obj.GetInfo(), nextObj.GetInfo()
	if info == nil || nextInfo == nil {
		return 0
	}
	return nextInfo.startLine - info.endLine
}

func formatSeq(seq Seq, w io.Writer, indent int) int {
	return formatSeqEx(seq, w, indent, false)
}

type RequireSort []Object

func (rs RequireSort) Len() int      { return len(rs) }
func (rs RequireSort) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
func (rs RequireSort) Less(i, j int) bool {
	a := rs[i]
	if s, ok := a.(Seqable); ok {
		a = s.Seq().First()
	}
	b := rs[j]
	if s, ok := b.(Seqable); ok {
		b = s.Seq().First()
	}
	return a.ToString(false) < b.ToString(false)
}

func sortRequire(seq Seq) Seq {
	s := RequireSort(ToSlice(seq))
	sort.Sort(s)
	return &ArraySeq{arr: s}
}

func formatSeqEx(seq Seq, w io.Writer, indent int, formatAsDef bool) int {
	i := indent + 1
	restIndent := indent + 2
	fmt.Fprint(w, "(")
	obj := seq.First()
	prevObj := obj
	seq, i = seqFirst(seq, w, i)
	isDefRecord := false
	if obj.Equals(SYMBOLS.defrecord) ||
		obj.Equals(SYMBOLS.defprotocol) ||
		obj.Equals(SYMBOLS.extendProtocol) ||
		obj.Equals(SYMBOLS.reify) ||
		obj.Equals(SYMBOLS.extendType) {
		isDefRecord = true
	}
	if obj.Equals(SYMBOLS.ns) || isOneAndBodyExpr(obj) {
		seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
	} else if obj.Equals(KEYWORDS.require) || obj.Equals(KEYWORDS._import) {
		seq = sortRequire(seq)
		seq, obj, _ = seqFirstAfterSpace(seq, w, i, isDefRecord)
		for !seq.IsEmpty() {
			seq, obj, _ = seqFirstAfterForcedBreak(seq, w, i+1)
		}
	} else if obj.Equals(SYMBOLS.catch) {
		if !seq.IsEmpty() {
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
		}
	} else if obj.Equals(SYMBOLS.fn) {
		if !seq.IsEmpty() {
			switch seq.First().(type) {
			case *Vector:
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			case Symbol:
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			default:
				if !isNewLine(obj, seq.First()) {
					restIndent = i + 1
				}
			}
		}
	} else if obj.Equals(SYMBOLS.let) || obj.Equals(SYMBOLS.loop) {
		if v, ok := seq.First().(*Vector); ok {
			fmt.Fprint(w, " ")
			i = formatBindings(v, w, i+1)
			prevObj = seq.First()
			seq = seq.Rest()
		}
	} else if obj.Equals(SYMBOLS.letfn) {
		if v, ok := seq.First().(*Vector); ok {
			fmt.Fprint(w, " ")
			i = formatVectorVertically(v, w, i+1)
			prevObj = seq.First()
			seq = seq.Rest()
		}
	} else if isDoIndent(obj) {
		if !seq.IsEmpty() && !isNewLine(obj, seq.First()) {
			restIndent = i + 1
		}
	} else if formatAsDef {
	} else if isBodyIndent(obj) {
		restIndent = indent + 2
	} else {
		// Indent function call arguments.
		restIndent = indent + 1
		if !seq.IsEmpty() && !isNewLine(obj, seq.First()) {
			restIndent = i + 1
		}
	}

	for !seq.IsEmpty() {
		nextObj := seq.First()
		if isNewLine(obj, nextObj) {
			seq, prevObj, i = seqFirstAfterBreak(prevObj, seq, w, restIndent, isDefRecord)
		} else {
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
		}
		obj = nextObj
	}

	if isComment(obj) {
		fmt.Fprint(w, "\n")
		writeIndent(w, restIndent)
		i = restIndent
	}

	fmt.Fprint(w, ")")
	return i + 1
}
<commit_msg>Formatter: indent fdef correctly.<commit_after>package core

import (
	"fmt"
	"io"
	"regexp"
	"sort"
	"unicode/utf8"
)

func seqFirst(seq Seq, w io.Writer, indent int) (Seq, int) {
	if !seq.IsEmpty() {
		indent = formatObject(seq.First(), indent, w)
		seq = seq.Rest()
	}
	return seq, indent
}

// TODO: maybe merge it with seqFirstAfterBreak
// or extract common part into a separate function
func seqFirstAfterSpace(seq Seq, w io.Writer, indent int, insideDefRecord bool) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		fmt.Fprint(w, " ")
		obj = seq.First()
		// Seq handling here is needed to properly format methods
		// inside defrecord
		if s, ok := obj.(Seq); ok && !obj.Equals(NIL) {
			if info := obj.GetInfo(); info != nil {
				fmt.Fprint(w, info.prefix)
				indent += utf8.RuneCountInString(info.prefix)
			}
			indent = formatSeqEx(s, w, indent+1, insideDefRecord)
		} else {
			indent = formatObject(obj, indent+1, w)
		}
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func writeNewLines(w io.Writer, prevObj Object, obj Object) int {
	cnt := newLineCount(prevObj, obj)
	for i := 0; i < cnt; i++ {
		fmt.Fprint(w, "\n")
	}
	return cnt
}

func seqFirstAfterBreak(prevObj Object, seq Seq, w io.Writer, indent int, insideDefRecord bool) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		obj = seq.First()
		writeNewLines(w, prevObj, obj)
		writeIndent(w, indent)
		// Seq handling here is needed to properly format methods
		// inside defrecord
		if s, ok := obj.(Seq); ok && !obj.Equals(NIL) {
			if info := obj.GetInfo(); info != nil {
				fmt.Fprint(w, info.prefix)
				indent += utf8.RuneCountInString(info.prefix)
			}
			indent = formatSeqEx(s, w, indent, insideDefRecord)
		} else {
			indent = formatObject(obj, indent, w)
		}
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func seqFirstAfterForcedBreak(seq Seq, w io.Writer, indent int) (Seq, Object, int) {
	var obj Object
	if !seq.IsEmpty() {
		obj = seq.First()
		fmt.Fprint(w, "\n")
		writeIndent(w, indent)
		indent = formatObject(obj, indent, w)
		seq = seq.Rest()
	}
	return seq, obj, indent
}

func formatBindings(v *Vector, w io.Writer, indent int) int {
	return v.Format(w, indent)
}

func formatVectorVertically(v *Vector, w io.Writer, indent int) int {
	fmt.Fprint(w, "[")
	newIndent := indent + 1
	for i := 0; i < v.count; i++ {
		newIndent = formatObject(v.at(i), indent+1, w)
		if i+1 < v.count {
			fmt.Fprint(w, "\n")
			writeIndent(w, indent+1)
		}
	}
	if v.count > 0 {
		if isComment(v.at(v.count - 1)) {
			fmt.Fprint(w, "\n")
			writeIndent(w, indent+1)
			newIndent = indent + 1
		}
	}
	fmt.Fprint(w, "]")
	return newIndent + 1
}

var defRegex *regexp.Regexp = regexp.MustCompile("^def.*$")
var ifRegex *regexp.Regexp = regexp.MustCompile("^if(-.+)?$")
var whenRegex *regexp.Regexp = regexp.MustCompile("^when(-.+)?$")
var doIndentRegex *regexp.Regexp = regexp.MustCompile("^(do|try|finally|go|alt!|alt!!)$")
var bodyIndentRegexes []*regexp.Regexp = []*regexp.Regexp{
	regexp.MustCompile("^(bound-fn|if|if-not|case|cond|cond->|cond->>|as->|condp|when|while|when-not|when-first|do|future|thread)$"),
	regexp.MustCompile("^(comment|doto|locking|proxy|with-[^\\s]*|reify|fdef)$"),
	regexp.MustCompile("^(defprotocol|extend|extend-protocol|extend-type|catch|let|letfn|binding|loop|for|go-loop)$"),
	regexp.MustCompile("^(doseq|dotimes|when-let|if-let|defstruct|struct-map|defmethod|testing|are|deftest|context|use-fixtures)$"),
	regexp.MustCompile("^(POST|GET|PUT|DELETE)"),
	regexp.MustCompile("^(handler-case|handle|dotrace|deftrace|match)$"),
}

func isOneAndBodyExpr(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		return defRegex.MatchString(*s.name) ||
			ifRegex.MatchString(*s.name) ||
			whenRegex.MatchString(*s.name)
	default:
		return false
	}
}

func isDoIndent(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		return doIndentRegex.MatchString(*s.name)
	default:
		return false
	}
}

func isBodyIndent(obj Object) bool {
	switch s := obj.(type) {
	case Symbol:
		for _, re := range bodyIndentRegexes {
			if re.MatchString(*s.name) {
				return true
			}
		}
		return false
	default:
		return false
	}
}

func isNewLine(obj, nextObj Object) bool {
	info, nextInfo := obj.GetInfo(), nextObj.GetInfo()
	return !(info == nil || nextInfo == nil || info.endLine == nextInfo.startLine)
}

func newLineCount(obj, nextObj Object) int {
	info, nextInfo := obj.GetInfo(), nextObj.GetInfo()
	if info == nil || nextInfo == nil {
		return 0
	}
	return nextInfo.startLine - info.endLine
}

func formatSeq(seq Seq, w io.Writer, indent int) int {
	return formatSeqEx(seq, w, indent, false)
}

type RequireSort []Object

func (rs RequireSort) Len() int      { return len(rs) }
func (rs RequireSort) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
func (rs RequireSort) Less(i, j int) bool {
	a := rs[i]
	if s, ok := a.(Seqable); ok {
		a = s.Seq().First()
	}
	b := rs[j]
	if s, ok := b.(Seqable); ok {
		b = s.Seq().First()
	}
	return a.ToString(false) < b.ToString(false)
}

func sortRequire(seq Seq) Seq {
	s := RequireSort(ToSlice(seq))
	sort.Sort(s)
	return &ArraySeq{arr: s}
}

func formatSeqEx(seq Seq, w io.Writer, indent int, formatAsDef bool) int {
	i := indent + 1
	restIndent := indent + 2
	fmt.Fprint(w, "(")
	obj := seq.First()
	prevObj := obj
	seq, i = seqFirst(seq, w, i)
	isDefRecord := false
	if obj.Equals(SYMBOLS.defrecord) ||
		obj.Equals(SYMBOLS.defprotocol) ||
		obj.Equals(SYMBOLS.extendProtocol) ||
		obj.Equals(SYMBOLS.reify) ||
		obj.Equals(SYMBOLS.extendType) {
		isDefRecord = true
	}
	if obj.Equals(SYMBOLS.ns) || isOneAndBodyExpr(obj) {
		seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
	} else if obj.Equals(KEYWORDS.require) || obj.Equals(KEYWORDS._import) {
		seq = sortRequire(seq)
		seq, obj, _ = seqFirstAfterSpace(seq, w, i, isDefRecord)
		for !seq.IsEmpty() {
			seq, obj, _ = seqFirstAfterForcedBreak(seq, w, i+1)
		}
	} else if obj.Equals(SYMBOLS.catch) {
		if !seq.IsEmpty() {
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
		}
	} else if obj.Equals(SYMBOLS.fn) {
		if !seq.IsEmpty() {
			switch seq.First().(type) {
			case *Vector:
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			case Symbol:
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
				seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
			default:
				if !isNewLine(obj, seq.First()) {
					restIndent = i + 1
				}
			}
		}
	} else if obj.Equals(SYMBOLS.let) || obj.Equals(SYMBOLS.loop) {
		if v, ok := seq.First().(*Vector); ok {
			fmt.Fprint(w, " ")
			i = formatBindings(v, w, i+1)
			prevObj = seq.First()
			seq = seq.Rest()
		}
	} else if obj.Equals(SYMBOLS.letfn) {
		if v, ok := seq.First().(*Vector); ok {
			fmt.Fprint(w, " ")
			i = formatVectorVertically(v, w, i+1)
			prevObj = seq.First()
			seq = seq.Rest()
		}
	} else if isDoIndent(obj) {
		if !seq.IsEmpty() && !isNewLine(obj, seq.First()) {
			restIndent = i + 1
		}
	} else if formatAsDef {
	} else if isBodyIndent(obj) {
		restIndent = indent + 2
	} else {
		// Indent function call arguments.
		restIndent = indent + 1
		if !seq.IsEmpty() && !isNewLine(obj, seq.First()) {
			restIndent = i + 1
		}
	}

	for !seq.IsEmpty() {
		nextObj := seq.First()
		if isNewLine(obj, nextObj) {
			seq, prevObj, i = seqFirstAfterBreak(prevObj, seq, w, restIndent, isDefRecord)
		} else {
			seq, prevObj, i = seqFirstAfterSpace(seq, w, i, isDefRecord)
		}
		obj = nextObj
	}

	if isComment(obj) {
		fmt.Fprint(w, "\n")
		writeIndent(w, restIndent)
		i = restIndent
	}

	fmt.Fprint(w, ")")
	return i + 1
}
<|endoftext|>


<commit_before>// (c) Copyright 2015 JONNALAGADDA Srinivas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package flow

import (
	"fmt"
	"log"
	"sync"
	"time"
)

// Document represents a task in a workflow, whose life cycle it
// tracks.
//
// Documents are central to the workflow engine and its operations.
// Each document represents a task whose life cycle it tracks.  In the
// process, it accumulates various details, and tracks the times of
// its modifications.  The life cycle typically involves several state
// transitions, whose details are also tracked.
//
// Most applications should embed `Document` in their document
// structures rather than use this directly.
type Document struct {
	id     uint64 // globally-unique
	dtype  DocType
	title  string
	text   string // primary content
	author *User  // creator of the document
	ctime  time.Time

	mutex    sync.RWMutex
	state    *DocState   // current state
	events   []*DocEvent // state transitions so far, tracked in order
	revision uint16
	tags     []string // user-defined tags associated with this document
}

// NewDocument creates and initialises a document.
//
// The document created through this method has a life cycle that is
// associated with it through a particular workflow.
func NewDocument(id uint64, dtype DocType, title string, author *User, instate *DocState) (*Document, error) {
	if id == 0 || string(dtype) == "" || title == "" || author == nil {
		return nil, fmt.Errorf("invalid initialisation data -- id: %d, dtype: %s, title: %s, author: %s", id, dtype, title, author.Name())
	}

	d := &Document{id: id, dtype: dtype, title: title, author: author}
	d.ctime = time.Now().UTC()
	d.state = instate
	d.events = make([]*DocEvent, 0, 1)
	d.revision = 1
	d.tags = make([]string, 0, 1)
	return d, nil
}

// TODO(js): OpenDocument()

// ID answers this document's globally-unique ID.
func (d *Document) ID() uint64 {
	return d.id
}

// Type answers this document's type.
func (d *Document) Type() DocType {
	return d.dtype
}

// Title answers this document's title.
func (d *Document) Title() string {
	return d.title
}

// SetText sets the primary content of this document.
func (d *Document) SetText(t string) error {
	if d.revision > 1 {
		return fmt.Errorf("document revision > 1 : %d", d.revision)
	}
	if d.text != "" {
		return fmt.Errorf("document text already set")
	}
	if t == "" {
		return fmt.Errorf("empty content given")
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	d.text = t
	return nil
}

// Text answers this document's primary content.
func (d *Document) Text() string {
	return d.text
}

// Author answers the user who created this document.
func (d *Document) Author() *User {
	return d.author
}

// Ctime answers the creation time of this document.
func (d *Document) Ctime() time.Time {
	return d.ctime
}

// State answer this document's current state.
func (d *Document) State() *DocState {
	return d.state
}

// Mtime answers the time when this document was most-recently
// modified.  It answers the creation time, if it has not been updated
// since.
func (d *Document) Mtime() time.Time {
	l := len(d.events)
	if l == 0 {
		return d.ctime
	}

	return d.events[l-1].mtime
}

// Revision answers the current revision number of this document.
func (d *Document) Revision() uint16 {
	return d.revision
}

// Events answers a copy of the sequence of events that has
// transformed this document so far.
func (d *Document) Events() []*DocEvent {
	// Synchronised because events are dynamic.
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	es := make([]*DocEvent, 0, len(d.events))
	copy(es, d.events)
	return es
}

// applyEvent transitions this document into a new state as per the
// applied event.
//
// We perform as many validations as possible when constructing the
// event, so that we spend a minimum amount of time in this
// synchronised method.
func (d *Document) applyEvent(e *DocEvent) error {
	if e.doc.id != d.id {
		return fmt.Errorf("mismatched document IDs -- current: %d, event's: %d", d.id, e.doc.id)
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	if !e.mtime.IsZero() {
		return fmt.Errorf("event already applied")
	}
	if e.oldRev != d.revision {
		return fmt.Errorf("revision mismatch -- document rev: %d, event rev: %d", d.revision, e.oldRev)
	}

	e.mtime = time.Now().UTC()
	d.revision++
	e.newRev = d.revision
	d.state = e.state
	d.events = append(d.events, e)
	return nil
}

// AddTag associates the given tag with this document.
func (d *Document) AddTag(tag string) bool {
	if tag == "" {
		log.Printf("empty tag given")
		return false
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	for _, el := range d.tags {
		if el == tag {
			return false
		}
	}

	d.tags = append(d.tags, tag)
	return true
}

// RemoveTag disassociates the given tag from this document.
func (d *Document) RemoveTag(tag string) bool {
	if tag == "" {
		return false
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	idx := -1
	for i, el := range d.tags {
		if el == tag {
			idx = i
			break
		}
	}

	if idx > -1 {
		d.tags = append(d.tags[:idx], d.tags[idx+1:]...)
		return true
	}

	return false
}

// Tags answers a copy of the tags associated with this document.
func (d *Document) Tags() []string {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	ts := make([]string, 0, len(d.tags))
	copy(ts, d.tags)
	return ts
}
<commit_msg>Modify `Document` to a multi-section container - WIP<commit_after>// (c) Copyright 2015 JONNALAGADDA Srinivas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package flow

import (
	"fmt"
	"log"
	"sync"
	"time"
)

// DocSection represents one user-edited section of text and,
// possibly, enclosures.
type DocSection struct {
	id     uint16    // serial number of this section in the document
	text   string    // textual context of this section
	blobs  []string  // paths to enclosures
	author uint64    // editor of this section
	mtime  time.Time // modification time of this section
}

// Document represents a task in a workflow, whose life cycle it
// tracks.
//
// Documents are central to the workflow engine and its operations.
// Each document represents a task whose life cycle it tracks.  In the
// process, it accumulates various details, and tracks the times of
// its modifications.  The life cycle typically involves several state
// transitions, whose details are also tracked.
//
// Most applications should embed `Document` in their document
// structures rather than use this directly.
type Document struct {
	id       uint64       // globally-unique
	dtype    DocType      // for namespacing
	title    string       // human-readable title
	sections []DocSection // sections in this document
	owner    uint64       // creator of this document
	ctime    time.Time    // creation time of this revision

	mutex    sync.RWMutex
	state    *DocState   // current state
	events   []*DocEvent // state transitions so far, tracked in order
	revision uint16      // running revision number
	tags     []string    // user-defined tags associated with this document
}

// NewDocument creates and initialises a document.
//
// The document created through this method has a life cycle that is
// associated with it through a particular workflow.
func NewDocument(id uint64, dtype DocType, title string, owner uint64, instate *DocState) (*Document, error) {
	if id == 0 || string(dtype) == "" || title == "" || owner == 0 {
		return nil, fmt.Errorf("invalid initialisation data -- id: %d, dtype: %s, title: %s, author: %d", id, dtype, title, owner)
	}

	d := &Document{id: id, dtype: dtype, title: title, owner: owner}
	d.ctime = time.Now().UTC()
	d.state = instate
	d.events = make([]*DocEvent, 0, 1)
	d.revision = 1
	d.tags = make([]string, 0, 1)
	return d, nil
}

// TODO(js): OpenDocument()

// ID answers this document's globally-unique ID.
func (d *Document) ID() uint64 {
	return d.id
}

// Type answers this document's type.
func (d *Document) Type() DocType {
	return d.dtype
}

// Title answers this document's title.
func (d *Document) Title() string {
	return d.title
}

// AddText adds the primary content of this document.
func (d *Document) AddText(t string) error {
	if t == "" {
		return fmt.Errorf("empty content given")
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	d.text = append(d.text, t)
	return nil
}

// Text answers this document's primary content.
func (d *Document) Text() []string {
	return d.text
}

// Author answers the user who created this document.
func (d *Document) Author() *User {
	return d.author
}

// Ctime answers the creation time of this document.
func (d *Document) Ctime() time.Time {
	return d.ctime
}

// State answer this document's current state.
func (d *Document) State() *DocState {
	return d.state
}

// Mtime answers the time when this document was most-recently
// modified.  It answers the creation time, if it has not been updated
// since.
func (d *Document) Mtime() time.Time {
	l := len(d.events)
	if l == 0 {
		return d.ctime
	}

	return d.events[l-1].mtime
}

// Revision answers the current revision number of this document.
func (d *Document) Revision() uint16 {
	return d.revision
}

// Events answers a copy of the sequence of events that has
// transformed this document so far.
func (d *Document) Events() []*DocEvent {
	// Synchronised because events are dynamic.
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	es := make([]*DocEvent, 0, len(d.events))
	copy(es, d.events)
	return es
}

// applyEvent transitions this document into a new state as per the
// applied event.
//
// We perform as many validations as possible when constructing the
// event, so that we spend a minimum amount of time in this
// synchronised method.
func (d *Document) applyEvent(e *DocEvent) error {
	if e.doc.id != d.id {
		return fmt.Errorf("mismatched document IDs -- current: %d, event's: %d", d.id, e.doc.id)
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	if !e.mtime.IsZero() {
		return fmt.Errorf("event already applied")
	}
	if e.oldRev != d.revision {
		return fmt.Errorf("revision mismatch -- document rev: %d, event rev: %d", d.revision, e.oldRev)
	}

	e.mtime = time.Now().UTC()
	d.revision++
	e.newRev = d.revision
	d.state = e.state
	d.events = append(d.events, e)
	return nil
}

// AddTag associates the given tag with this document.
func (d *Document) AddTag(tag string) bool {
	if tag == "" {
		log.Printf("empty tag given")
		return false
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	for _, el := range d.tags {
		if el == tag {
			return false
		}
	}

	d.tags = append(d.tags, tag)
	return true
}

// RemoveTag disassociates the given tag from this document.
func (d *Document) RemoveTag(tag string) bool {
	if tag == "" {
		return false
	}

	d.mutex.Lock()
	defer d.mutex.Unlock()

	idx := -1
	for i, el := range d.tags {
		if el == tag {
			idx = i
			break
		}
	}

	if idx > -1 {
		d.tags = append(d.tags[:idx], d.tags[idx+1:]...)
		return true
	}

	return false
}

// Tags answers a copy of the tags associated with this document.
func (d *Document) Tags() []string {
	d.mutex.RLock()
	defer d.mutex.RUnlock()

	ts := make([]string, 0, len(d.tags))
	copy(ts, d.tags)
	return ts
}
<|endoftext|>


<commit_before>package gocouchlib

import (
	"net/http"
)

type Document struct {
	Id      string `json:"_id,omitempty"`
	Rev     string `json:"_rev,omitempty"`
	Deleted bool   `json:"_deleted,omitempty"`
	Json    JsonObj

	Db *Database `json:"-"`
}

func (doc *Document) dbEndpoint() string {
	return doc.Db.Server.FullUrl() + "/" + doc.Db.DbName
}

func (doc *Document) docEndpoint(params map[string]string) string {
	queryString := "?"
	if params != nil {
		for key, val := range params {
			queryString += key + "=" + val + "&"
		}
	}

	return doc.dbEndpoint() + "/" + doc.Id + queryString
}

func (doc *Document) Exists() (bool, *CouchResponse) {

	headers := make(http.Header)

	if doc.Rev != "" {
		headers.Add("If-None-Match", "\""+doc.Rev+"\"")
	}

	couchResp, _ := httpClient.Head(doc.docEndpoint(nil), headers)

	// Set doc.Rev using the ETag on the response if it is currently empty
	if (couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified) && doc.Rev == "" && couchResp.Headers.Get("ETag") != "" {
		doc.Rev = couchResp.Headers.Get("ETag")
	}

	return (couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified), couchResp
}

func (doc *Document) Get() (JsonObj, *CouchResponse) {

	headers := make(http.Header)

	if doc.Rev != "" {
		headers.Add("If-None-Match", "\""+doc.Rev+"\"")
	}

	couchResp, _ := httpClient.Get(doc.docEndpoint(nil), headers)
	var json JsonObj = nil
	if couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified {
		json = couchResp.Json
		doc.Json = couchResp.Json
		doc.Rev = couchResp.Headers.Get("ETag")
	}

	return json, couchResp
}

func (doc *Document) Delete() (bool, *CouchResponse) {
	var params map[string]string
	params = make(map[string]string)

	params["rev"] = doc.Rev
	couchResp, _ := httpClient.Delete(doc.docEndpoint(params))

	if couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusAccepted {
		doc.Rev = couchResp.Headers.Get("ETag")
		doc.Deleted = true
	}

	return couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusAccepted, couchResp
}

func (doc *Document) Save() (bool, *CouchResponse) {
	var couchResp *CouchResponse

	if doc.Id == "" {
		couchResp, _ = httpClient.Post(doc.dbEndpoint(), doc.Json)
	} else {
		headers := make(http.Header)
		if doc.Rev != "" {
			headers.Add("If-Match", "\""+doc.Rev+"\"")
		}
		couchResp, _ = httpClient.Put(doc.docEndpoint(nil), doc.Json, headers)
	}

	if couchResp.StatusCode == http.StatusCreated || couchResp.StatusCode == http.StatusAccepted {
		doc.Rev = couchResp.Headers.Get("ETag")
	}

	return couchResp.StatusCode == http.StatusCreated || couchResp.StatusCode == http.StatusAccepted, couchResp
}
<commit_msg>Trimmed ETag of double quotes<commit_after>package gocouchlib

import (
	"net/http"
)

type Document struct {
	Id      string `json:"_id,omitempty"`
	Rev     string `json:"_rev,omitempty"`
	Deleted bool   `json:"_deleted,omitempty"`
	Json    JsonObj

	Db *Database `json:"-"`
}

func (doc *Document) dbEndpoint() string {
	return doc.Db.Server.FullUrl() + "/" + doc.Db.DbName
}

func (doc *Document) docEndpoint(params map[string]string) string {
	queryString := "?"
	if params != nil {
		for key, val := range params {
			queryString += key + "=" + val + "&"
		}
	}

	return doc.dbEndpoint() + "/" + doc.Id + queryString
}

func (doc *Document) Exists() (bool, *CouchResponse) {

	headers := make(http.Header)

	if doc.Rev != "" {
		headers.Add("If-None-Match", "\""+doc.Rev+"\"")
	}

	couchResp, _ := httpClient.Head(doc.docEndpoint(nil), headers)

	// Set doc.Rev using the ETag on the response if it is currently empty
	if (couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified) && doc.Rev == "" && couchResp.Headers.Get("ETag") != "" {
		doc.Rev = TrimEtag(couchResp.Headers.Get("ETag"))
	}

	return (couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified), couchResp
}

func (doc *Document) Get() (JsonObj, *CouchResponse) {

	headers := make(http.Header)

	if doc.Rev != "" {
		headers.Add("If-None-Match", "\""+doc.Rev+"\"")
	}

	couchResp, _ := httpClient.Get(doc.docEndpoint(nil), headers)
	var json JsonObj = nil
	if couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusNotModified {
		json = couchResp.Json
		doc.Json = couchResp.Json
		doc.Rev = TrimEtag(couchResp.Headers.Get("ETag"))
	}

	return json, couchResp
}

func (doc *Document) Delete() (bool, *CouchResponse) {
	var params map[string]string
	params = make(map[string]string)

	params["rev"] = doc.Rev
	couchResp, _ := httpClient.Delete(doc.docEndpoint(params))

	if couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusAccepted {
		doc.Rev = TrimEtag(couchResp.Headers.Get("ETag"))
		doc.Deleted = true
	}

	return couchResp.StatusCode == http.StatusOK || couchResp.StatusCode == http.StatusAccepted, couchResp
}

func (doc *Document) Save() (bool, *CouchResponse) {
	var couchResp *CouchResponse

	if doc.Id == "" {
		couchResp, _ = httpClient.Post(doc.dbEndpoint(), doc.Json)
	} else {
		headers := make(http.Header)
		if doc.Rev != "" {
			headers.Add("If-Match", "\""+doc.Rev+"\"")
		}
		couchResp, _ = httpClient.Put(doc.docEndpoint(nil), doc.Json, headers)
	}

	if couchResp.StatusCode == http.StatusCreated || couchResp.StatusCode == http.StatusAccepted {
		doc.Rev = TrimEtag(couchResp.Headers.Get("ETag"))
	}

	return couchResp.StatusCode == http.StatusCreated || couchResp.StatusCode == http.StatusAccepted, couchResp
}

<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"io"
	"os"
	"regexp"
	"unicode/utf8"

	"github.com/mibk/syd/ui"
)

const EOF = utf8.MaxRune + 1

type Window struct {
	col      *Column
	filename string
	win      ui.Updater
	con      Content

	buf  *UndoBuffer
	tag  *Text
	body *Text

	// used by Read and flush methods
	insertbuf bytes.Buffer

	y float64

	next *Window
}

func (win *Window) SetFilename(filename string) {
	win.filename = filename
	win.tag.buf.Insert(0, filename)
	win.col.ed.wins[filename] = win
}

func (win *Window) Dirty() bool {
	return win.buf.Dirty()
}

func (win *Window) Y() float64 { return win.y }

func (win *Window) bottom() float64 {
	if win.next == nil {
		return 1
	}
	return win.next.y
}

func (win *Window) SetY(y float64) {
	if y < 0 || y > 1 {
		panic("y must be in the range 0..1")
	}
	win.y = y
}

func (win *Window) Tag() *Text { return win.tag }

func (win *Window) Body() *Text { return win.body }

func (win *Window) Close() error {
	win.win.Update(ui.Delete)
	win.col.removeWindow(win)
	if ed := win.col.ed; ed.errWin == win {
		ed.errWin = nil
	}
	if win.filename != "" {
		delete(win.col.ed.wins, win.filename)
	}
	return win.con.Close()
}

func (win *Window) Write(b []byte) (n int, err error) {
	return win.insertbuf.Write(b)
}

func (win *Window) flush() {
	s := win.insertbuf.String()
	win.insertbuf.Reset()
	q := win.body.q0
	win.body.Insert(s)
	win.body.Select(q, q+int64(utf8.RuneCountInString(s)))

	// TODO: Come up with a better solution?
	win.buf.Commit()
}

func (win *Window) saveFile() {
	if win.filename == "" {
		win.readFilename()
	}

	// TODO: Don't use '~' suffix, make saving safer.
	f, err := os.Create(win.filename + "~")
	if err != nil {
		panic(err)
	}
	r := io.NewSectionReader(win.buf, 0, win.buf.Size())
	if _, err := io.Copy(f, r); err != nil {
		panic(err)
	}
	f.Close()

	if err := os.Rename(win.filename+"~", win.filename); err != nil {
		panic(err)
	}
}

func (win *Window) readFilename() {
	var runes []rune
	var p int64
	for {
		r := win.tag.readRuneAt(p)
		if r == 0 || r == EOF {
			break
		}
		runes = append(runes, r)
		p++
	}
	if len(runes) == 0 {
		return
	}
	win.filename = string(runes)
}

func (win *Window) findNextExactMatch(s string) {
	rx := regexp.MustCompile(regexp.QuoteMeta(s))

	body := win.body
	buf := win.buf
	for _, q := range []int64{body.q1, 0} {
		r, off := buf.RuneReaderFrom(q)
		if loc := rx.FindReaderIndex(r); loc != nil {
			q0, q1 := buf.FindRange(off+int64(loc[0]), int64(loc[1]-loc[0]))
			body.Select(q0, q1)
			return
		}
	}
}

func (win *Window) editor() (ed *Editor)           { return win.col.ed }
func (win *Window) column() (col *Column, ok bool) { return win.col, true }
func (win *Window) window() (w *Window, ok bool)   { return win, true }
<commit_msg>core: Use ioutil.TempFile when saving files<commit_after>package core

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"unicode/utf8"

	"github.com/mibk/syd/ui"
)

const EOF = utf8.MaxRune + 1

type Window struct {
	col      *Column
	filename string
	win      ui.Updater
	con      Content

	buf  *UndoBuffer
	tag  *Text
	body *Text

	// used by Read and flush methods
	insertbuf bytes.Buffer

	y float64

	next *Window
}

func (win *Window) SetFilename(filename string) {
	win.filename = filename
	win.tag.buf.Insert(0, filename)
	win.col.ed.wins[filename] = win
}

func (win *Window) Dirty() bool {
	return win.buf.Dirty()
}

func (win *Window) Y() float64 { return win.y }

func (win *Window) bottom() float64 {
	if win.next == nil {
		return 1
	}
	return win.next.y
}

func (win *Window) SetY(y float64) {
	if y < 0 || y > 1 {
		panic("y must be in the range 0..1")
	}
	win.y = y
}

func (win *Window) Tag() *Text { return win.tag }

func (win *Window) Body() *Text { return win.body }

func (win *Window) Close() error {
	win.win.Update(ui.Delete)
	win.col.removeWindow(win)
	if ed := win.col.ed; ed.errWin == win {
		ed.errWin = nil
	}
	if win.filename != "" {
		delete(win.col.ed.wins, win.filename)
	}
	return win.con.Close()
}

func (win *Window) Write(b []byte) (n int, err error) {
	return win.insertbuf.Write(b)
}

func (win *Window) flush() {
	s := win.insertbuf.String()
	win.insertbuf.Reset()
	q := win.body.q0
	win.body.Insert(s)
	win.body.Select(q, q+int64(utf8.RuneCountInString(s)))

	// TODO: Come up with a better solution?
	win.buf.Commit()
}

func (win *Window) saveFile() {
	if win.filename == "" {
		win.readFilename()
	}

	dst, err := filepath.Abs(win.filename)
	if err != nil {
		panic(err)
	}
	dir, file := filepath.Split(dst)
	f, err := ioutil.TempFile(dir, ".~"+file)
	if err != nil {
		panic(err)
	}
	r := io.NewSectionReader(win.buf, 0, win.buf.Size())
	if _, err := io.Copy(f, r); err != nil {
		panic(err)
	}
	f.Close()

	if err := os.Rename(f.Name(), dst); err != nil {
		panic(err)
	}
}

func (win *Window) readFilename() {
	var runes []rune
	var p int64
	for {
		r := win.tag.readRuneAt(p)
		if r == 0 || r == EOF {
			break
		}
		runes = append(runes, r)
		p++
	}
	if len(runes) == 0 {
		return
	}
	win.filename = string(runes)
}

func (win *Window) findNextExactMatch(s string) {
	rx := regexp.MustCompile(regexp.QuoteMeta(s))

	body := win.body
	buf := win.buf
	for _, q := range []int64{body.q1, 0} {
		r, off := buf.RuneReaderFrom(q)
		if loc := rx.FindReaderIndex(r); loc != nil {
			q0, q1 := buf.FindRange(off+int64(loc[0]), int64(loc[1]-loc[0]))
			body.Select(q0, q1)
			return
		}
	}
}

func (win *Window) editor() (ed *Editor)           { return win.col.ed }
func (win *Window) column() (col *Column, ok bool) { return win.col, true }
func (win *Window) window() (w *Window, ok bool)   { return win, true }
<|endoftext|>


<commit_before>/*
MIT License

Copyright (c) 2017 Simon Schmidt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package cparse

import "github.com/byte-mug/semiparse/scanlist"
import "github.com/byte-mug/semiparse/parser"
import "text/scanner"
import "fmt"

const (
	e_none = uint(iota)
	E_VAR
	E_INT
	E_FLOAT
	E_CHAR
	E_STRING

	E_INCR
	E_DECR
	E_FIELD_DOT // Expr.Field
	E_FIELD_PTR // Expr->Field
	E_UNARY_OP
	E_BINARY_OP
	E_BINARY_OP_ASSIGN
	E_COMPARE
	E_ASSIGN
	E_FUNCTION_CALL // Expr( [Expr [,Expr]* ] )
)

func aR(i ...interface{}) []interface{} { return i }

type Expr struct{
	Type uint
	Text string
	Data []interface{}
}
func (e *Expr) String() string {
	if e==nil { return "NIL" }
	switch e.Type {
	case E_BINARY_OP:
		return fmt.Sprint("(",e.Data[0],e.Text,e.Data[1],")")
	case E_BINARY_OP_ASSIGN:
		return fmt.Sprint("(",e.Data[0],e.Text,"=",e.Data[1],")")
	case E_INCR,E_DECR:
		return fmt.Sprint("(",e.Data[0],e.Text,")")
	case E_FIELD_DOT:
		return fmt.Sprint("(",e.Data,".",e.Text,")")
	case E_FIELD_PTR:
		return fmt.Sprint("(",e.Data,"->",e.Text,")")
	case E_UNARY_OP:
		return fmt.Sprint("(",e.Text,e.Data[0],")")
	case E_VAR,E_INT,E_FLOAT,E_CHAR,E_STRING:
		return fmt.Sprint(e.Text)
	case E_ASSIGN:
		return fmt.Sprint("(",e.Data[0],"=",e.Data[1],")")
	case E_FUNCTION_CALL:
		return fmt.Sprint("(",e.Data[0]," (",e.Data[1:],") )")
	}
	return fmt.Sprint("(",e.Type,"#",e.Text,e.Data,")")
}

func c_expr_list(p *parser.Parser,tokens *scanlist.Element, sep rune, r []interface{}) parser.ParserResult {
	sub := p.Match("Expr",tokens)
	if sub.Result!=parser.RESULT_OK { return sub }
	r = append(r,sub.Data)
	for sub.Next.SafeToken() == sep {
		sub = p.Match("Expr",sub.Next.Next())
		if sub.Result!=parser.RESULT_OK { return sub }
		r = append(r,sub.Data)
	}
	sub.Data = r
	return sub
}
func c_expr(p *parser.Parser,tokens *scanlist.Element, left interface{}) parser.ParserResult {
	if tokens==nil { return parser.ResultFail("EOF!",scanner.Position{}) }
	switch tokens.Token {
	case scanner.Ident: return parser.ResultOk(tokens.Next(),&Expr{E_VAR,tokens.TokenText,nil})
	case scanner.Int: return parser.ResultOk(tokens.Next(),&Expr{E_INT,tokens.TokenText,nil})
	case scanner.Float: return parser.ResultOk(tokens.Next(),&Expr{E_FLOAT,tokens.TokenText,nil})
	case scanner.Char: return parser.ResultOk(tokens.Next(),&Expr{E_CHAR,tokens.TokenText,nil})
	case scanner.String,scanner.RawString: return parser.ResultOk(tokens.Next(),&Expr{E_STRING,tokens.TokenText,nil})
	case '*','+','-','!','~':{
		sub := p.MatchNoLeftRecursion("Expr",tokens.Next())
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_UNARY_OP,tokens.TokenText,aR(sub.Data)}
		}
		return sub
	    }
	case '(': /*)*/{
		sub := p.Match("Expr",tokens.Next())
		if sub.Result==parser.RESULT_OK {/*(*/
			e,t := parser.Match(parser.Textify,sub.Next,')')
			if e!=nil { return parser.ResultFail(fmt.Sprint(e),sub.Next.SafePos()) }
			sub.Next = t
		}
		return sub
	    }
	}
	return parser.ResultFail("Invalid Expression!",tokens.Pos)
}
func c_expr_trailer(p *parser.Parser,tokens *scanlist.Element, left interface{}) parser.ParserResult {
	if ok,t := parser.FastMatch(tokens,'+','+'); ok {
		return parser.ResultOk(t,&Expr{E_INCR,"++",aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'-','-'); ok {
		return parser.ResultOk(t,&Expr{E_DECR,"--",aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'-','>',scanner.Ident); ok {
		return parser.ResultOk(t,&Expr{E_FIELD_PTR,tokens.Next().Next().TokenText,aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'.',scanner.Ident); ok {
		return parser.ResultOk(t,&Expr{E_FIELD_DOT,tokens.Next().TokenText,aR(left)})
	}
	if tokens.SafeToken()=='(' /*)*/ {
		sub := c_expr_list(p,tokens.Next(),',',aR(left))
		if sub.Result==parser.RESULT_OK {/*(*/
			e,t := parser.Match(parser.Textify,sub.Next,')')
			if e!=nil { return parser.ResultFail(fmt.Sprint(e),sub.Next.SafePos()) }
			sub.Next = t
			sub.Data = &Expr{E_FUNCTION_CALL,"()",sub.Data.([]interface{})}
		}
		return sub
	}

	ok,t := parser.FastMatch(tokens,'+')
	s := ""
	if !ok { ok,t = parser.FastMatch(tokens,'-') }
	if !ok { ok,t = parser.FastMatch(tokens,'*') }
	if !ok { ok,t = parser.FastMatch(tokens,'/') }
	if !ok { ok,t = parser.FastMatch(tokens,'%') }
	if !ok { ok,t = parser.FastMatch(tokens,'>','>'); if ok { s=">>" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<','<'); if ok { s="<<" } }
	if !ok { ok,t = parser.FastMatch(tokens,'&','&'); if ok { s="&&" } }
	if !ok { ok,t = parser.FastMatch(tokens,'|','|'); if ok { s="||" } }
	if !ok { ok,t = parser.FastMatch(tokens,'^') }

	if ok {
		if s=="" { s = tokens.TokenText }
		op := E_BINARY_OP
		if t.SafeToken()=='=' {
			op = E_BINARY_OP_ASSIGN
			t = t.SafeNext()
		}
		sub := p.MatchNoLeftRecursion("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{op,s,aR(left,sub.Data)}
		}
		return sub
		/*
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_BINARY_OP,tokens.TokenText,aR(left,sub.Data)}
		}
		*/
	}

	ok,t = parser.FastMatch(tokens,'=','='); if ok { s="==" }
	if !ok { ok,t = parser.FastMatch(tokens,'!','='); if ok { s="!=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<','='); if ok { s="<=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<'); if ok { s="<" } }
	if !ok { ok,t = parser.FastMatch(tokens,'>','='); if ok { s=">=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'>'); if ok { s=">" } }
	if ok {
		sub := p.MatchNoLeftRecursion("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_COMPARE,s,aR(left,sub.Data)}
		}
		return sub
	}

	ok,t = parser.FastMatch(tokens,'=')

	if ok {
		sub := p.Match("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_ASSIGN,"=",aR(left,sub.Data)}
		}
		return sub
	}

	return parser.ResultFail("No trailer.",tokens.SafePos())
}

func RegisterExpr(p *parser.Parser) {
	p.Define("Expr",false,parser.Pfunc(c_expr))
	p.Define("Expr",true,parser.Pfunc(c_expr_trailer))
}

<commit_msg>Conditional expressions<commit_after>/*
MIT License

Copyright (c) 2017 Simon Schmidt

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package cparse

import "github.com/byte-mug/semiparse/scanlist"
import "github.com/byte-mug/semiparse/parser"
import "text/scanner"
import "fmt"

const (
	e_none = uint(iota)
	E_VAR
	E_INT
	E_FLOAT
	E_CHAR
	E_STRING

	E_INCR
	E_DECR
	E_FIELD_DOT // Expr.Field
	E_FIELD_PTR // Expr->Field
	E_UNARY_OP
	E_BINARY_OP
	E_BINARY_OP_ASSIGN
	E_COMPARE
	E_ASSIGN
	E_FUNCTION_CALL // Expr( [Expr [,Expr]* ] )
	E_CONDITIONAL
)

func aR(i ...interface{}) []interface{} { return i }

type Expr struct{
	Type uint
	Text string
	Data []interface{}
}
func (e *Expr) String() string {
	if e==nil { return "NIL" }
	switch e.Type {
	case E_BINARY_OP:
		return fmt.Sprint("(",e.Data[0],e.Text,e.Data[1],")")
	case E_BINARY_OP_ASSIGN:
		return fmt.Sprint("(",e.Data[0],e.Text,"=",e.Data[1],")")
	case E_INCR:
		return fmt.Sprint("(",e.Data[0],"++)")
	case E_DECR:
		return fmt.Sprint("(",e.Data[0],"--)")
	case E_FIELD_DOT:
		return fmt.Sprint("(",e.Data,".",e.Text,")")
	case E_FIELD_PTR:
		return fmt.Sprint("(",e.Data,"->",e.Text,")")
	case E_UNARY_OP:
		return fmt.Sprint("(",e.Text,e.Data[0],")")
	case E_VAR,E_INT,E_FLOAT,E_CHAR,E_STRING:
		return fmt.Sprint(e.Text)
	case E_ASSIGN:
		return fmt.Sprint("(",e.Data[0],"=",e.Data[1],")")
	case E_FUNCTION_CALL:
		return fmt.Sprint("(",e.Data[0]," (",e.Data[1:],") )")
	case E_CONDITIONAL:
		return fmt.Sprint("(",e.Data[0],"?",e.Data[1],":",e.Data[2],")")
	}
	return fmt.Sprint("(",e.Type,"#",e.Text,e.Data,")")
}

func c_expr_list(p *parser.Parser,tokens *scanlist.Element, sep rune, r []interface{}) parser.ParserResult {
	sub := p.Match("Expr",tokens)
	if sub.Result!=parser.RESULT_OK { return sub }
	r = append(r,sub.Data)
	for sub.Next.SafeToken() == sep {
		sub = p.Match("Expr",sub.Next.Next())
		if sub.Result!=parser.RESULT_OK { return sub }
		r = append(r,sub.Data)
	}
	sub.Data = r
	return sub
}
func c_expr(p *parser.Parser,tokens *scanlist.Element, left interface{}) parser.ParserResult {
	if tokens==nil { return parser.ResultFail("EOF!",scanner.Position{}) }
	switch tokens.Token {
	case scanner.Ident: return parser.ResultOk(tokens.Next(),&Expr{E_VAR,tokens.TokenText,nil})
	case scanner.Int: return parser.ResultOk(tokens.Next(),&Expr{E_INT,tokens.TokenText,nil})
	case scanner.Float: return parser.ResultOk(tokens.Next(),&Expr{E_FLOAT,tokens.TokenText,nil})
	case scanner.Char: return parser.ResultOk(tokens.Next(),&Expr{E_CHAR,tokens.TokenText,nil})
	case scanner.String,scanner.RawString: return parser.ResultOk(tokens.Next(),&Expr{E_STRING,tokens.TokenText,nil})
	case '*','+','-','!','~':{
		sub := p.MatchNoLeftRecursion("Expr",tokens.Next())
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_UNARY_OP,tokens.TokenText,aR(sub.Data)}
		}
		return sub
	    }
	case '(': /*)*/{
		sub := p.Match("Expr",tokens.Next())
		if sub.Result==parser.RESULT_OK {/*(*/
			e,t := parser.Match(parser.Textify,sub.Next,')')
			if e!=nil { return parser.ResultFail(fmt.Sprint(e),sub.Next.SafePos()) }
			sub.Next = t
		}
		return sub
	    }
	}
	return parser.ResultFail("Invalid Expression!",tokens.Pos)
}
func c_expr_trailer(p *parser.Parser,tokens *scanlist.Element, left interface{}) parser.ParserResult {
	if ok,t := parser.FastMatch(tokens,'+','+'); ok {
		return parser.ResultOk(t,&Expr{E_INCR,"++",aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'-','-'); ok {
		return parser.ResultOk(t,&Expr{E_DECR,"--",aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'-','>',scanner.Ident); ok {
		return parser.ResultOk(t,&Expr{E_FIELD_PTR,tokens.Next().Next().TokenText,aR(left)})
	}
	if ok,t := parser.FastMatch(tokens,'.',scanner.Ident); ok {
		return parser.ResultOk(t,&Expr{E_FIELD_DOT,tokens.Next().TokenText,aR(left)})
	}
	if tokens.SafeToken()=='(' /*)*/ {
		sub := c_expr_list(p,tokens.Next(),',',aR(left))
		if sub.Result==parser.RESULT_OK {/*(*/
			e,t := parser.Match(parser.Textify,sub.Next,')')
			if e!=nil { return parser.ResultFail(fmt.Sprint(e),sub.Next.SafePos()) }
			sub.Next = t
			sub.Data = &Expr{E_FUNCTION_CALL,"()",sub.Data.([]interface{})}
		}
		return sub
	}
	if tokens.SafeToken()=='?' {
		sub := p.Match("Expr",tokens.Next())
		if sub.Result!=parser.RESULT_OK { return sub }
		e,t := parser.Match(parser.Textify,sub.Next,':')
		if e!=nil { return parser.ResultFail(fmt.Sprint(e),sub.Next.SafePos()) }
		sub2 := p.MatchNoLeftRecursion("Expr",t)
		if sub2.Result!=parser.RESULT_OK { return sub2 }
		return parser.ResultOk(sub2.Next,&Expr{E_CONDITIONAL,"?:",aR(left,sub.Data,sub2.Data)})
	}

	ok,t := parser.FastMatch(tokens,'+')
	s := ""
	if !ok { ok,t = parser.FastMatch(tokens,'-') }
	if !ok { ok,t = parser.FastMatch(tokens,'*') }
	if !ok { ok,t = parser.FastMatch(tokens,'/') }
	if !ok { ok,t = parser.FastMatch(tokens,'%') }
	if !ok { ok,t = parser.FastMatch(tokens,'>','>'); if ok { s=">>" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<','<'); if ok { s="<<" } }
	if !ok { ok,t = parser.FastMatch(tokens,'&','&'); if ok { s="&&" } }
	if !ok { ok,t = parser.FastMatch(tokens,'|','|'); if ok { s="||" } }
	if !ok { ok,t = parser.FastMatch(tokens,'^') }

	if ok {
		if s=="" { s = tokens.TokenText }
		op := E_BINARY_OP
		if t.SafeToken()=='=' {
			op = E_BINARY_OP_ASSIGN
			t = t.SafeNext()
		}
		sub := p.MatchNoLeftRecursion("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{op,s,aR(left,sub.Data)}
		}
		return sub
		/*
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_BINARY_OP,tokens.TokenText,aR(left,sub.Data)}
		}
		*/
	}

	ok,t = parser.FastMatch(tokens,'=','='); if ok { s="==" }
	if !ok { ok,t = parser.FastMatch(tokens,'!','='); if ok { s="!=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<','='); if ok { s="<=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'<'); if ok { s="<" } }
	if !ok { ok,t = parser.FastMatch(tokens,'>','='); if ok { s=">=" } }
	if !ok { ok,t = parser.FastMatch(tokens,'>'); if ok { s=">" } }
	if ok {
		sub := p.MatchNoLeftRecursion("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_COMPARE,s,aR(left,sub.Data)}
		}
		return sub
	}

	ok,t = parser.FastMatch(tokens,'=')

	if ok {
		sub := p.Match("Expr",t)
		if sub.Result==parser.RESULT_OK {
			sub.Data = &Expr{E_ASSIGN,"=",aR(left,sub.Data)}
		}
		return sub
	}

	return parser.ResultFail("No trailer.",tokens.SafePos())
}

func RegisterExpr(p *parser.Parser) {
	p.Define("Expr",false,parser.Pfunc(c_expr))
	p.Define("Expr",true,parser.Pfunc(c_expr_trailer))
}

<|endoftext|>