1    	package acl2s.uilib.cmd;
2    	
3    	import java.io.Serializable;
4    	import java.nio.ByteBuffer;
5    	import java.nio.CharBuffer;
6    	import java.nio.charset.Charset;
7    	import java.nio.charset.CharsetDecoder;
8    	import java.nio.charset.CharacterCodingException;
9    	import java.util.ArrayList;
10   	
11   	import org.peterd.util.Misc;
12   	
13   	import acl2s.lib.parse.EmptyParse;
14   	import acl2s.lib.parse.IParseContext;
15   	import acl2s.lib.parse.ParseException;
16   	import acl2s.lib.parse.Parser;
17   	import acl2s.lib.parse.PositionableParser;
18   	import acl2s.lib.parse.StringParser;
19   	import acl2s.lib.parse.obj.Atom;
20   	import acl2s.lib.parse.obj.Char;
21   	import acl2s.lib.parse.obj.Cons;
22   	import acl2s.lib.parse.obj.LispObject;
23   	import acl2s.lib.parse.obj.NotListException;
24   	import acl2s.lib.parse.obj.Num;
25   	import acl2s.lib.parse.obj.ReadTimeConditional;
26   	import acl2s.lib.parse.obj.Str;
27   	import acl2s.lib.parse.obj.Sym;
28   	import acl2s.lib.session.SessionException;
29   	import acl2s.lib.session.SessionMode;
30   	import acl2s.plugin.ISessionDocument;
31   	import acl2s.plugin.prefs.InteractionPrefs;
32   	import acl2s.uilib.session.InteractiveSessionManager;
33   	
34   	public abstract class ResolvableUserInput implements Serializable {
35   		private static final long serialVersionUID = 3L;
36   		
37   		public abstract ResolvedInput resolve(InteractiveSessionManager session) throws SessionException;
38   		
39   		
40   		protected final String text;       // typed input (ish)
41   	
42   		protected final String source;
43   		
44   		public ResolvableUserInput(String text, String source) {
45   			this.text = text;
46   			this.source = source;
47   		}
48   		
49   		public String getInputString() {
50   			return text;
51   		}
52   	
53   		public String getSource() {
54   			return source;
55   		}
56   	
57   		public boolean equals(Object o) {
58   			if (o instanceof ResolvableUserInput) {
59   				ResolvableUserInput that = (ResolvableUserInput) o;
60   				return this.exactlyEquals(that);
61   			} else {
62   				return false;
63   			}
64   		}
65   		
66   		public boolean exactlyEquals(ResolvableUserInput that) {
67   			return (this.text).equals(that.text);
68   		}
69   		
70   		public boolean abstractlyEquals(ResolvableUserInput that, IParseContext pc) {
71   			LispObject[] thisLst = LispObject.robustParseAll(new StringParser(pc,this.text));
72   			LispObject[] thatLst = LispObject.robustParseAll(new StringParser(pc,that.text));
73   			if (thisLst.length != thatLst.length) return false;
74   			for (int i = 0; i < thisLst.length; i++) {
75   				if (!thisLst[i].equals(thatLst[i])) return false;
76   			}
77   			return true;
78   		}
79   		
80   		private static boolean isASCII(String text) {
81   			// Test text for non-ASCII characters
Event do_not_call: "java.lang.String.getBytes()" implicitly uses the environment's default character set, which might lead to unexpected behavior. Consider using getBytes(String charsetName).
82   			byte [] bytearray = text.getBytes();
83   			CharsetDecoder d = Charset.forName("US-ASCII").newDecoder();
84   			try {
85   				CharBuffer r = d.decode(ByteBuffer.wrap(bytearray));
86   				r.toString();
87   			} catch (CharacterCodingException cce) {
88   				return false;
89   			}
90   			return true;
91   		}
92   		
93   		
94   		// CREATION/PARSING
95   		
96   		public static ResolvableUserInput parse(PositionableParser p, String source, ISessionDocument sesdoc) throws ParseException, BadCmdException {
97   			p.skipSpaceAndComments();
98   			if (p.peek() == Parser.EOF) throw new EmptyParse();
99   			int start = p.getPos();
100  			
101  			IParseContext pc = p.getContext();
102  			LispObject cmd = LispObject.parse(p);     // default: entire expression
103  			Boolean checkASCII = InteractionPrefs.getCheckASCII(null, sesdoc);
104  			SessionMode mode = (checkASCII == null && sesdoc != null) ? sesdoc.getAcl2Mode() : null;
105  			boolean mcheck = (mode == null) ? true : mode.getIntroductory();
106  			if ((checkASCII == null ? mcheck :
107  					checkASCII.booleanValue()) && 
108  				!isASCII(cmd.toString(pc))) {
109  				throw new BadCmdException("Input may not contain non-ASCII characters.");			
110  			}
111  			
112  			LispObject cond = null;  // default: unguarded
113  			if (cmd instanceof ReadTimeConditional) {
114  				ReadTimeConditional rtc = (ReadTimeConditional) cmd;
115  				cond = rtc.fexpr;
116  				cmd = rtc.oexpr;
117  			}
118  			
119  			String text = p.substring(start,p.getPos());
120  	
121  			if (cmd instanceof Sym) {
122  				Sym s = (Sym) cmd;
123  				if  (s.isKeyword()) { // keyword command
124  					if (cond != null) {
125  						throw new BadCmdException("Keyword commands preceded by read-time conditionals not supported.");
126  					}
127  					ArrayList<LispObject> params = new ArrayList<LispObject>();
128  					for (;;) {
129  						p.skip(true,false,true);
130  						char c = p.peek();
131  						if (c == '\n' || c == '\r' || c == Parser.EOF) break;
132  						params.add(LispObject.parseOne(p));
133  					}
134  					if (p.peek() == Parser.EOF) {
135  						throw new ParseException("Keyword command must be terminated by a newline outside of a lisp expression.");
136  					}
137  					text = p.substring(start,p.getPos());
138  					p.skip();
139  					return new UnresolvedKeywordCmd(text,s,params.toArray(new LispObject[params.size()]),source);
140  				}
141  			}
142  			
143  			if (((cmd instanceof Cons) || (cmd instanceof Num)) && text.charAt(text.length()-1) == ')') {
144  				// Cons or Complex ending in ) is fine
145  			} else {
146  				// need whitespace; Char ending in ) is NOT fine
147  				if ("\n\r\t\f ;".indexOf(p.peek()) < 0) {
148  					throw new ParseException("This form must be followed by whitespace, so that modifying text after it cannot change how it would be parsed.");
149  				}
150  				p.skip();
151  			}
152  			
153  			if (cmd instanceof Cons) {
154  				LispObject[] objs;
155  				try {
156  					objs = ((Cons) cmd).listToArray();
157  					if (objs == null || objs.length < 1) throw new NotListException();
158  				} catch (NotListException e) {
159  					throw new BadCmdException("Invocation must be a true list.");
160  				}
161  				if (!(objs[0] instanceof Sym)) {
162  					// old
163  					//throw new BadCmdException("First element of invocation list must be a symbol.");
164  				}
165  				
166  			} else if (cmd instanceof Atom && cond == null) {
167  				Atom a = (Atom) cmd;
168  				if (a instanceof Char || a instanceof Num) {
169  					return new SimpleValueInput(text, a, source);
170  				} else if (a instanceof Str) {
171  					Str s = (Str) a;
172  					if (!Misc.equal(s.value.toUpperCase(),s.value)) { // can't be a package name
173  						return new SimpleValueInput(text, a, source);
174  					}
175  					// strings with no lowercase letters could be VALUE or COMMAND
176  				}
177  				// syms could be BAD or VALUE
178  			}
179  			
180  			return new UnresolvedCmd(text,cmd,cond,source);
181  		}
182  	
183  		
184  	}