mirror of https://github.com/golang/go.git
cmd/internal/gc, cmd/yacc: restore custom syntax error messages
This restores go.errors from before3af0d79along with a fixed up version of the bisonerrors AWK script, translated to Go. However, this means Yyerror needs access to the yacc parser's state, which is currently private. To workaround that, add a "state" accessor method like the Lookahead method added inc7fa3c6. Update issue #9968. Change-Id: Ib868789e92fdb7d135442120a392457923e50121 Reviewed-on: https://go-review.googlesource.com/7270 Reviewed-by: Russ Cox <rsc@golang.org> Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
e5a0d6399e
commit
398bf9d5a0
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Example-based syntax error messages.
|
||||||
|
// See yaccerrors.go.
|
||||||
|
|
||||||
|
package gc
|
||||||
|
|
||||||
|
var yymsg = []struct {
|
||||||
|
yystate int
|
||||||
|
yychar int
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
// Each line of the form % token list
|
||||||
|
// is converted by yaccerrors.go into the yystate and yychar caused
|
||||||
|
// by that token list.
|
||||||
|
|
||||||
|
% loadsys package LIMPORT '(' LLITERAL import_package import_there ','
|
||||||
|
"unexpected comma during import block"},
|
||||||
|
|
||||||
|
% loadsys package LIMPORT LNAME ';'
|
||||||
|
"missing import path; require quoted string"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LIF if_header ';'
|
||||||
|
"missing { after if clause"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LSWITCH if_header ';'
|
||||||
|
"missing { after switch clause"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LFOR for_header ';'
|
||||||
|
"missing { after for clause"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LFOR ';' LBODY
|
||||||
|
"missing { after for clause"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' ';' '{'
|
||||||
|
"unexpected semicolon or newline before {"},
|
||||||
|
|
||||||
|
% loadsys package imports LTYPE LNAME ';'
|
||||||
|
"unexpected semicolon or newline in type declaration"},
|
||||||
|
|
||||||
|
% loadsys package imports LCHAN '}'
|
||||||
|
"unexpected } in channel type"},
|
||||||
|
|
||||||
|
% loadsys package imports LCHAN ')'
|
||||||
|
"unexpected ) in channel type"},
|
||||||
|
|
||||||
|
% loadsys package imports LCHAN ','
|
||||||
|
"unexpected comma in channel type"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' if_stmt ';' LELSE
|
||||||
|
"unexpected semicolon or newline before else"},
|
||||||
|
|
||||||
|
% loadsys package imports LTYPE LNAME LINTERFACE '{' LNAME ',' LNAME
|
||||||
|
"name list not allowed in interface type"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LFOR LVAR LNAME '=' LNAME
|
||||||
|
"var declaration not allowed in for initializer"},
|
||||||
|
|
||||||
|
% loadsys package imports LVAR LNAME '[' ']' LNAME '{'
|
||||||
|
"unexpected { at end of statement"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LVAR LNAME '[' ']' LNAME '{'
|
||||||
|
"unexpected { at end of statement"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LDEFER LNAME ';'
|
||||||
|
"argument to go/defer must be function call"},
|
||||||
|
|
||||||
|
% loadsys package imports LVAR LNAME '=' LNAME '{' LNAME ';'
|
||||||
|
"need trailing comma before newline in composite literal"},
|
||||||
|
|
||||||
|
% loadsys package imports LVAR LNAME '=' comptype '{' LNAME ';'
|
||||||
|
"need trailing comma before newline in composite literal"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LFUNC LNAME
|
||||||
|
"nested func not allowed"},
|
||||||
|
|
||||||
|
% loadsys package imports LFUNC LNAME '(' ')' '{' LIF if_header loop_body LELSE ';'
|
||||||
|
"else must be followed by if or statement block"},
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:generate go tool yacc go.y
|
//go:generate go tool yacc go.y
|
||||||
|
//go:generate go run yaccerrors.go
|
||||||
//go:generate go run mkbuiltin.go runtime unsafe
|
//go:generate go run mkbuiltin.go runtime unsafe
|
||||||
|
|
||||||
package gc
|
package gc
|
||||||
|
|
@ -1782,11 +1783,6 @@ func pragcgo(text string) {
|
||||||
|
|
||||||
type yy struct{}
|
type yy struct{}
|
||||||
|
|
||||||
var yymsg []struct {
|
|
||||||
yystate, yychar int
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (yy) Lex(v *yySymType) int {
|
func (yy) Lex(v *yySymType) int {
|
||||||
return int(yylex(v))
|
return int(yylex(v))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,10 +122,6 @@ func yyerrorl(line int, fmt_ string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var yystate int
|
|
||||||
|
|
||||||
var yychar_subr int
|
|
||||||
|
|
||||||
var yyerror_lastsyntax int
|
var yyerror_lastsyntax int
|
||||||
|
|
||||||
func Yyerror(fmt_ string, args ...interface{}) {
|
func Yyerror(fmt_ string, args ...interface{}) {
|
||||||
|
|
@ -139,8 +135,11 @@ func Yyerror(fmt_ string, args ...interface{}) {
|
||||||
if strings.HasPrefix(fmt_, "syntax error") {
|
if strings.HasPrefix(fmt_, "syntax error") {
|
||||||
nsyntaxerrors++
|
nsyntaxerrors++
|
||||||
|
|
||||||
|
yystate := theparser.(*yyParserImpl).state()
|
||||||
|
yychar := theparser.Lookahead()
|
||||||
|
|
||||||
if Debug['x'] != 0 {
|
if Debug['x'] != 0 {
|
||||||
fmt.Printf("yyerror: yystate=%d yychar=%d\n", yystate, yychar_subr)
|
fmt.Printf("yyerror: yystate=%d yychar=%d\n", yystate, yychar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// An unexpected EOF caused a syntax error. Use the previous
|
// An unexpected EOF caused a syntax error. Use the previous
|
||||||
|
|
@ -166,8 +165,8 @@ func Yyerror(fmt_ string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for parse state-specific errors in list (see go.errors).
|
// look for parse state-specific errors in list (see go.errors).
|
||||||
for i := 0; i < len(yymsg); i++ {
|
for i := range yymsg {
|
||||||
if yymsg[i].yystate == yystate && yymsg[i].yychar == yychar_subr {
|
if yymsg[i].yystate == yystate && yymsg[i].yychar == yychar {
|
||||||
yyerrorl(int(lexlineno), "syntax error: %s", yymsg[i].msg)
|
yyerrorl(int(lexlineno), "syntax error: %s", yymsg[i].msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -857,6 +857,7 @@ type yyParser interface {
|
||||||
|
|
||||||
type yyParserImpl struct {
|
type yyParserImpl struct {
|
||||||
lookahead func() int
|
lookahead func() int
|
||||||
|
state func() int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *yyParserImpl) Lookahead() int {
|
func (p *yyParserImpl) Lookahead() int {
|
||||||
|
|
@ -866,6 +867,7 @@ func (p *yyParserImpl) Lookahead() int {
|
||||||
func yyNewParser() yyParser {
|
func yyNewParser() yyParser {
|
||||||
p := &yyParserImpl{
|
p := &yyParserImpl{
|
||||||
lookahead: func() int { return -1 },
|
lookahead: func() int { return -1 },
|
||||||
|
state: func() int { return -1 },
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
@ -942,9 +944,11 @@ func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
|
||||||
yystate := 0
|
yystate := 0
|
||||||
yychar := -1
|
yychar := -1
|
||||||
yytoken := -1 // yychar translated into internal numbering
|
yytoken := -1 // yychar translated into internal numbering
|
||||||
|
yyrcvr.state = func() int { return yystate }
|
||||||
yyrcvr.lookahead = func() int { return yychar }
|
yyrcvr.lookahead = func() int { return yychar }
|
||||||
defer func() {
|
defer func() {
|
||||||
// Make sure we report no lookahead when not parsing.
|
// Make sure we report no lookahead when not parsing.
|
||||||
|
yystate = -1
|
||||||
yychar = -1
|
yychar = -1
|
||||||
yytoken = -1
|
yytoken = -1
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// This program implements the core idea from
|
||||||
|
//
|
||||||
|
// Clinton L. Jeffery, Generating LR syntax error messages from examples,
|
||||||
|
// ACM TOPLAS 25(5) (September 2003). http://doi.acm.org/10.1145/937563.937566
|
||||||
|
//
|
||||||
|
// It reads Bison's summary of a grammar followed by a file
|
||||||
|
// like go.errors, replacing lines beginning with % by the
|
||||||
|
// yystate and yychar that will be active when an error happens
|
||||||
|
// while parsing that line.
|
||||||
|
//
|
||||||
|
// Unlike the system described in the paper, the lines in go.errors
|
||||||
|
// give grammar symbol name lists, not actual program fragments.
|
||||||
|
// This is a little less programmer-friendly but doesn't require being
|
||||||
|
// able to run the text through lex.c.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func xatoi(s string) int {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimParen(s string) string {
|
||||||
|
s = strings.TrimPrefix(s, "(")
|
||||||
|
s = strings.TrimSuffix(s, ")")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type action struct {
|
||||||
|
token string
|
||||||
|
n int
|
||||||
|
}
|
||||||
|
|
||||||
|
var shift = map[int][]action{}
|
||||||
|
var reduce = map[int][]action{}
|
||||||
|
|
||||||
|
type rule struct {
|
||||||
|
lhs string
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules = map[int]rule{}
|
||||||
|
|
||||||
|
func readYaccOutput() {
|
||||||
|
r, err := os.Open("y.output")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
var state int
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
f := strings.Fields(scanner.Text())
|
||||||
|
nf := len(f)
|
||||||
|
|
||||||
|
if nf >= 4 && f[1] == "terminals," && f[3] == "nonterminals" {
|
||||||
|
// We're done.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if nf >= 2 && f[0] == "state" {
|
||||||
|
state = xatoi(f[1])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nf >= 3 && (f[1] == "shift" || f[1] == "goto") {
|
||||||
|
shift[state] = append(shift[state], action{f[0], xatoi(f[2])})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nf >= 3 && f[1] == "reduce" {
|
||||||
|
reduce[state] = append(reduce[state], action{f[0], xatoi(f[2])})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nf >= 3 && strings.HasSuffix(f[0], ":") && strings.HasPrefix(f[nf-1], "(") && strings.HasSuffix(f[nf-1], ")") {
|
||||||
|
n := xatoi(trimParen(f[nf-1]))
|
||||||
|
|
||||||
|
size := nf - 2
|
||||||
|
if size == 1 && f[1] == "." {
|
||||||
|
size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rules[n] = rule{strings.TrimSuffix(f[0], ":"), size}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMachine(w io.Writer, s string) {
|
||||||
|
f := strings.Fields(s)
|
||||||
|
|
||||||
|
// Run it through the LR machine and print the induced "yystate, yychar,"
|
||||||
|
// at the point where the error happens.
|
||||||
|
|
||||||
|
var stack []int
|
||||||
|
state := 0
|
||||||
|
i := 1
|
||||||
|
tok := ""
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
if tok == "" && i < len(f) {
|
||||||
|
tok = f[i]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range shift[state] {
|
||||||
|
if a.token == tok {
|
||||||
|
if false {
|
||||||
|
fmt.Println("SHIFT ", tok, " ", state, " -> ", a)
|
||||||
|
}
|
||||||
|
stack = append(stack, state)
|
||||||
|
state = a.n
|
||||||
|
tok = ""
|
||||||
|
goto Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range reduce[state] {
|
||||||
|
if a.token == tok || a.token == "." {
|
||||||
|
stack = append(stack, state)
|
||||||
|
rule, ok := rules[a.n]
|
||||||
|
if !ok {
|
||||||
|
log.Fatal("missing rule")
|
||||||
|
}
|
||||||
|
stack = stack[:len(stack)-rule.size]
|
||||||
|
state = stack[len(stack)-1]
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
if tok != "" {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
tok = rule.lhs
|
||||||
|
if false {
|
||||||
|
fmt.Println("REDUCE ", stack, " ", state, " ", tok, " rule ", rule)
|
||||||
|
}
|
||||||
|
goto Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No shift or reduce applied - found the error.
|
||||||
|
fmt.Fprintf(w, "\t{%d, %s,\n", state, tok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processGoErrors() {
|
||||||
|
r, err := os.Open("go.errors")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
w, err := os.Create("yymsg.go")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "// DO NOT EDIT - generated with go generate\n\n")
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
|
||||||
|
// Treat % as first field on line as introducing a pattern (token sequence).
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(s), "%") {
|
||||||
|
runMachine(w, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
readYaccOutput()
|
||||||
|
processGoErrors()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
// DO NOT EDIT - generated with go generate
|
||||||
|
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Example-based syntax error messages.
|
||||||
|
// See yaccerrors.go.
|
||||||
|
|
||||||
|
package gc
|
||||||
|
|
||||||
|
var yymsg = []struct {
|
||||||
|
yystate int
|
||||||
|
yychar int
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
// Each line of the form % token list
|
||||||
|
// is converted by bisonerrors into the yystate and yychar caused
|
||||||
|
// by that token list.
|
||||||
|
|
||||||
|
{332, ',',
|
||||||
|
"unexpected comma during import block"},
|
||||||
|
|
||||||
|
{89, ';',
|
||||||
|
"missing import path; require quoted string"},
|
||||||
|
|
||||||
|
{390, ';',
|
||||||
|
"missing { after if clause"},
|
||||||
|
|
||||||
|
{387, ';',
|
||||||
|
"missing { after switch clause"},
|
||||||
|
|
||||||
|
{279, ';',
|
||||||
|
"missing { after for clause"},
|
||||||
|
|
||||||
|
{498, LBODY,
|
||||||
|
"missing { after for clause"},
|
||||||
|
|
||||||
|
{17, '{',
|
||||||
|
"unexpected semicolon or newline before {"},
|
||||||
|
|
||||||
|
{111, ';',
|
||||||
|
"unexpected semicolon or newline in type declaration"},
|
||||||
|
|
||||||
|
{78, '}',
|
||||||
|
"unexpected } in channel type"},
|
||||||
|
|
||||||
|
{78, ')',
|
||||||
|
"unexpected ) in channel type"},
|
||||||
|
|
||||||
|
{78, ',',
|
||||||
|
"unexpected comma in channel type"},
|
||||||
|
|
||||||
|
{416, LELSE,
|
||||||
|
"unexpected semicolon or newline before else"},
|
||||||
|
|
||||||
|
{329, ',',
|
||||||
|
"name list not allowed in interface type"},
|
||||||
|
|
||||||
|
{279, LVAR,
|
||||||
|
"var declaration not allowed in for initializer"},
|
||||||
|
|
||||||
|
{25, '{',
|
||||||
|
"unexpected { at end of statement"},
|
||||||
|
|
||||||
|
{371, '{',
|
||||||
|
"unexpected { at end of statement"},
|
||||||
|
|
||||||
|
{122, ';',
|
||||||
|
"argument to go/defer must be function call"},
|
||||||
|
|
||||||
|
{398, ';',
|
||||||
|
"need trailing comma before newline in composite literal"},
|
||||||
|
|
||||||
|
{414, ';',
|
||||||
|
"need trailing comma before newline in composite literal"},
|
||||||
|
|
||||||
|
{124, LNAME,
|
||||||
|
"nested func not allowed"},
|
||||||
|
|
||||||
|
{650, ';',
|
||||||
|
"else must be followed by if or statement block"},
|
||||||
|
}
|
||||||
|
|
@ -3212,6 +3212,7 @@ type $$Parser interface {
|
||||||
|
|
||||||
type $$ParserImpl struct {
|
type $$ParserImpl struct {
|
||||||
lookahead func() int
|
lookahead func() int
|
||||||
|
state func() int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *$$ParserImpl) Lookahead() int {
|
func (p *$$ParserImpl) Lookahead() int {
|
||||||
|
|
@ -3221,6 +3222,7 @@ func (p *$$ParserImpl) Lookahead() int {
|
||||||
func $$NewParser() $$Parser {
|
func $$NewParser() $$Parser {
|
||||||
p := &$$ParserImpl{
|
p := &$$ParserImpl{
|
||||||
lookahead: func() int { return -1 },
|
lookahead: func() int { return -1 },
|
||||||
|
state: func() int { return -1 },
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
@ -3297,9 +3299,11 @@ func ($$rcvr *$$ParserImpl) Parse($$lex $$Lexer) int {
|
||||||
$$state := 0
|
$$state := 0
|
||||||
$$char := -1
|
$$char := -1
|
||||||
$$token := -1 // $$char translated into internal numbering
|
$$token := -1 // $$char translated into internal numbering
|
||||||
|
$$rcvr.state = func() int { return $$state }
|
||||||
$$rcvr.lookahead = func() int { return $$char }
|
$$rcvr.lookahead = func() int { return $$char }
|
||||||
defer func() {
|
defer func() {
|
||||||
// Make sure we report no lookahead when not parsing.
|
// Make sure we report no lookahead when not parsing.
|
||||||
|
$$state = -1
|
||||||
$$char = -1
|
$$char = -1
|
||||||
$$token = -1
|
$$token = -1
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
// skip
|
|
||||||
// TODO(rsc): Reenable. See issue 9968.
|
|
||||||
|
|
||||||
// errorcheck
|
// errorcheck
|
||||||
|
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
|
@ -18,4 +15,3 @@ type I interface {
|
||||||
type J interface {
|
type J interface {
|
||||||
h T; // ERROR "syntax|signature"
|
h T; // ERROR "syntax|signature"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
// skip
|
|
||||||
// TODO(rsc): Reenable. See issue 9968.
|
|
||||||
|
|
||||||
// errorcheck
|
// errorcheck
|
||||||
|
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue