View Javadoc
1   package org.sentrysoftware.jawk;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2023 Sentry Software
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.io.PrintStream;
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.InvocationTargetException;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.StringTokenizer;
41  
42  import org.sentrysoftware.jawk.backend.AVM;
43  import org.sentrysoftware.jawk.ext.JawkExtension;
44  import org.sentrysoftware.jawk.frontend.AwkParser;
45  import org.sentrysoftware.jawk.frontend.AwkSyntaxTree;
46  import org.sentrysoftware.jawk.intermediate.AwkTuples;
47  import org.sentrysoftware.jawk.util.AwkLogger;
48  import org.sentrysoftware.jawk.util.AwkSettings;
49  import org.sentrysoftware.jawk.util.ScriptSource;
50  import org.slf4j.Logger;
51  
52  /**
53   * Entry point into the parsing, analysis, and execution
54   * of a Jawk script.
55   * This entry point is used when Jawk is executed as a library.
56   * If you want to use Jawk as a stand-alone application, please use {@see Main}.
57   * <p>
58   * The overall process to execute a Jawk script is as follows:
59   * <ul>
60   * <li>Parse the Jawk script, producing an abstract syntax tree.
61   * <li>Traverse the abstract syntax tree, producing a list of
62   *	 instruction tuples for the interpreter.
63   * <li>Traverse the list of tuples, providing a runtime which
64   *	 ultimately executes the Jawk script, <strong>or</strong>
65   *   Command-line parameters dictate which action is to take place.
66   * </ul>
67   * Two additional semantic checks on the syntax tree are employed
68   * (both to resolve function calls for defined functions).
69   * As a result, the syntax tree is traversed three times.
70   * And the number of times tuples are traversed is depends
71   * on whether interpretation or compilation takes place.
72   * <p>
73   * By default a minimal set of extensions are automatically
74   * included. Please refer to the EXTENSION_PREFIX static field
75   * contents for an up-to-date list. As of the initial release
76   * of the extension system, the prefix defines the following
77   * extensions:
78   * <ul>
79   * <li>CoreExtension
80   * <li>SocketExtension
81   * <li>StdinExtension
82   * </ul>
83   *
84   * @see org.sentrysoftware.jawk.backend.AVM
85   * @author Danny Daglas
86   */
87  public class Awk {
88  
89  	private static final String DEFAULT_EXTENSIONS
90  			= org.sentrysoftware.jawk.ext.CoreExtension.class.getName()
91  			+ "#" + org.sentrysoftware.jawk.ext.StdinExtension.class.getName();
92  
93  	private static final Logger LOG = AwkLogger.getLogger(Awk.class);
94  
95  	/**
96  	 * Create a new instance of Awk
97  	 */
98  	public Awk() {}
99  
100 	/**
101 	 * <p>invoke.</p>
102 	 *
103 	 * @param settings This tells AWK what to do
104 	 *   (where to get input from, where to write it to, in what mode to run,
105 	 *   ...)
106 	 * @throws java.io.IOException upon an IO error.
107 	 * @throws java.lang.ClassNotFoundException if intermediate code is specified
108 	 *           but deserialization fails to load in the JVM
109 	 * @throws org.sentrysoftware.jawk.ExitException if interpretation is requested,
110 	 *	 and a specific exit code is requested.
111 	 */
112 	public void invoke(AwkSettings settings)
113 			throws IOException, ClassNotFoundException, ExitException
114 	{
115 		AVM avm = null;
116 		try {
117 			// key = Keyword, value = JawkExtension
118 			Map<String, JawkExtension> extensions;
119 			if (settings.isUserExtensions()) {
120 				extensions = getJawkExtensions();
121 				LOG.trace("user extensions = {}", extensions.keySet());
122 			} else {
123 				extensions = Collections.emptyMap();
124 				LOG.trace("user extensions not enabled");
125 			}
126 
127 			AwkTuples tuples = new AwkTuples();
128 			// to be defined below
129 
130 			List<ScriptSource> notIntermediateScriptSources = new ArrayList<ScriptSource>(settings.getScriptSources().size());
131 			for (ScriptSource scriptSource : settings.getScriptSources()) {
132 				if (scriptSource.isIntermediate()) {
133 					// read the intermediate file, bypassing frontend processing
134 					tuples = (AwkTuples) readObjectFromInputStream(scriptSource.getInputStream()); // FIXME only the last intermediate file is used!
135 				} else {
136 					notIntermediateScriptSources.add(scriptSource);
137 				}
138 			}
139 			if (!notIntermediateScriptSources.isEmpty()) {
140 				AwkParser parser = new AwkParser(
141 						settings.isAdditionalFunctions(),
142 						settings.isAdditionalTypeFunctions(),
143 						extensions);
144 				// parse the script
145 				AwkSyntaxTree ast = parser.parse(notIntermediateScriptSources);
146 
147 				if (settings.isDumpSyntaxTree()) {
148 					// dump the syntax tree of the script to a file
149 					String filename = settings.getOutputFilename("syntax_tree.lst");
150 					LOG.info("writing to '{}'", filename);
151 					PrintStream ps = new PrintStream(new FileOutputStream(filename));
152 					if (ast != null) {
153 						ast.dump(ps);
154 					}
155 					ps.close();
156 					return;
157 				}
158 				// otherwise, attempt to traverse the syntax tree and build
159 				// the intermediate code
160 				if (ast != null) {
161 					// 1st pass to tie actual parameters to back-referenced formal parameters
162 					ast.semanticAnalysis();
163 					// 2nd pass to tie actual parameters to forward-referenced formal parameters
164 					ast.semanticAnalysis();
165 					// build tuples
166 					int result = ast.populateTuples(tuples);
167 					// ASSERTION: NOTHING should be left on the operand stack ...
168 					assert result == 0;
169 					// Assign queue.next to the next element in the queue.
170 					// Calls touch(...) per Tuple so that addresses can be normalized/assigned/allocated
171 					tuples.postProcess();
172 					// record global_var -> offset mapping into the tuples
173 					// so that the interpreter/compiler can assign variables
174 					// on the "file list input" command line
175 					parser.populateGlobalVariableNameToOffsetMappings(tuples);
176 				}
177 				if (settings.isWriteIntermediateFile()) {
178 					// dump the intermediate code to an intermediate code file
179 					String filename = settings.getOutputFilename("a.ai");
180 					LOG.info("writing to '{}'", filename);
181 					writeObjectToFile(tuples, filename);
182 					return;
183 				}
184 			}
185 			if (settings.isDumpIntermediateCode()) {
186 				// dump the intermediate code to a human-readable text file
187 				String filename = settings.getOutputFilename("avm.lst");
188 				LOG.info("writing to '{}'", filename);
189 				PrintStream ps = new PrintStream(new FileOutputStream(filename));
190 				tuples.dump(ps);
191 				ps.close();
192 				return;
193 			}
194 
195 			// interpret!
196 			avm = new AVM(settings, extensions);
197 			avm.interpret(tuples);
198 
199 		} finally {
200 			if (avm != null) {
201 				avm.waitForIO();
202 			}
203 		}
204 	}
205 
206 	private static Object readObjectFromInputStream(InputStream is)
207 			throws IOException, ClassNotFoundException
208 	{
209 		ObjectInputStream ois = new ObjectInputStream(is);
210 		Object retval = ois.readObject();
211 		ois.close();
212 		return retval;
213 	}
214 
215 	private static void writeObjectToFile(Object object, String filename)
216 			throws IOException
217 	{
218 		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
219 		oos.writeObject(object);
220 		oos.close();
221 	}
222 
223 	private static Map<String, JawkExtension> getJawkExtensions() {
224 		String extensionsStr = System.getProperty("jawk.extensions", null);
225 		if (extensionsStr == null) {
226 			//return Collections.emptyMap();
227 			extensionsStr = DEFAULT_EXTENSIONS;
228 		} else {
229 			extensionsStr = DEFAULT_EXTENSIONS + "#" + extensionsStr;
230 		}
231 
232 		// use reflection to obtain extensions
233 
234 		Set<Class<?>> extensionClasses = new HashSet<Class<?>>();
235 		Map<String, JawkExtension> retval = new HashMap<String, JawkExtension>();
236 
237 		StringTokenizer st = new StringTokenizer(extensionsStr, "#");
238 		while (st.hasMoreTokens()) {
239 			String cls = st.nextToken();
240 			LOG.trace("cls = {}", cls);
241 			try {
242 				Class<?> c = Class.forName(cls);
243 				// check if it's a JawkException
244 				if (!JawkExtension.class.isAssignableFrom(c)) {
245 					throw new ClassNotFoundException(cls + " does not implement JawkExtension");
246 				}
247 				if (extensionClasses.contains(c)) {
248 					LOG.warn("class {} is multiple times referred in extension class list. Skipping.", cls);
249 					continue;
250 				} else {
251 					extensionClasses.add(c);
252 				}
253 
254 				// it is...
255 				// create a new instance and put it here
256 				try {
257 					Constructor<?> constructor = c.getDeclaredConstructor(); // Default constructor
258 					JawkExtension ji = (JawkExtension) constructor.newInstance();
259 					String[] keywords = ji.extensionKeywords();
260 					for (String keyword : keywords) {
261 						if (retval.get(keyword) != null) {
262 							throw new IllegalArgumentException("keyword collision : " + keyword
263 									+ " for both " + retval.get(keyword).getExtensionName()
264 									+ " and " + ji.getExtensionName());
265 						}
266 						retval.put(keyword, ji);
267 					}
268 				} catch (InstantiationException |
269 						IllegalAccessException |
270 						NoSuchMethodException |
271 						SecurityException |
272 						IllegalArgumentException |
273 						InvocationTargetException e) {
274 					LOG.warn("Cannot instantiate " + c.getName(), e);
275 				}
276 			} catch (ClassNotFoundException cnfe) {
277 				LOG.warn("Cannot classload {} : {}", new Object[] {cls, cnfe});
278 			}
279 		}
280 
281 		return retval;
282 	}
283 }