1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.millscript.millscript.vm;
22
23 import org.millscript.commons.alert.Alert;
24 import org.millscript.commons.alert.EscapeException;
25 import org.millscript.commons.util.EMap;
26 import org.millscript.commons.util.MapIterator;
27 import org.millscript.commons.util.map.EHashMap;
28 import org.millscript.commons.vfs.VEntry;
29 import org.millscript.commons.vfs.VFile;
30 import org.millscript.commons.vfs.VFolder;
31 import org.millscript.millscript.Source;
32 import org.millscript.millscript.alert.Alerts;
33 import org.millscript.millscript.alert.Phases;
34 import org.millscript.millscript.conf.Configuration;
35 import org.millscript.millscript.expr.GlobalIdent;
36 import org.millscript.millscript.functions.Function;
37 import org.millscript.millscript.loaders.Loader;
38 import org.millscript.millscript.syntax.ConfigurationParserImpl;
39 import org.millscript.millscript.syntax.Parser;
40 import org.millscript.millscript.syntax.ParserImpl;
41 import org.millscript.millscript.syntax.TokenType;
42
43 import java.io.IOException;
44 import java.io.Reader;
45 import java.io.StringReader;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import java.util.List;
49
50 /**
51 * This class represents a MillScript Package.
52 */
53 public class Package {
54
55 /**
56 * Maps from symbols to Loaders. This is used to autoload values for
57 * symbols.
58 */
59 private final EMap< String, Loader > autoloadableSymbols = new EHashMap< String, Loader >();
60
61 /**
62 * This packages configuration.
63 */
64 private final Configuration config;
65
66 /**
67 * The engine this package will use to execute code
68 */
69 private final Engine engine;
70
71 /**
72 * This packages fully qualified name.
73 */
74 private final String fullyQualifiedName;
75
76 /**
77 * Maps from symbols to ident records.
78 */
79 private final EMap< String, GlobalIdent > globalSymbols = new EHashMap< String, GlobalIdent >();
80
81 /**
82 * This packages imports, mapping a packages nickname to its package
83 * object.
84 */
85 private final EMap< String, Package > imports = new EHashMap< String, Package >();
86
87 /**
88 * Indicates if this package is pervasive.
89 */
90 private final boolean isPervasive;
91
92 /**
93 * Constructs a new package with the given fully qualified name and
94 * nickname.
95 *
96 * @param e the engine this package is associated with
97 * @param fqn the new packages fully qualified name
98 * @param b a boolean indicating if this package is pervasive
99 */
100 public Package( final Engine e, final String fqn, final boolean b ) {
101 this.engine = e;
102 this.config = e.getConfig();
103 this.fullyQualifiedName = fqn;
104 this.isPervasive = b;
105 }
106
107 /**
108 * Adds the contents of the specified Inventory to the set of autoloadable
109 * variables.
110 *
111 * @param dir the inventory to add
112 */
113 public void addInventory( final VFolder dir ) {
114 if ( dir != null && dir.exists() ) {
115 final List entries = dir.listEntries();
116 final Iterator it = entries.iterator();
117 while ( it.hasNext() ) {
118 Loader loader = this.makeLoaderFor( (VEntry) it.next() );
119 if ( loader != null ) {
120 autoloadableSymbols.insert( loader.getSymbol(), loader );
121 }
122 }
123 }
124 }
125
126 /**
127 * Autoloads the bindings for the specified symbol.
128 *
129 * @param sym the symbol to load bindings for
130 */
131 public void autoload( final String sym ) {
132 final Loader loader = autoloadableSymbols.get( sym );
133 if ( loader != null ) {
134 config.getLogger().info( "AUTOLOADING " );
135 config.getLogger().infoLine( sym );
136 try {
137 loader.loadBindings();
138 } catch ( IOException ioex ) {
139 throw(
140 Alerts.compile( "IOException thrown while loading", null ).
141 origin( loader ).
142 culprit( "reason", ioex.getMessage() ).
143 mishap()
144 );
145 } catch ( Alert ex ) {
146
147 throw(
148 ex.origin( loader ).setPhase( Phases.AUTOLOAD ).remishap()
149 );
150 }
151 }
152 }
153
154 /**
155 * Returns an iterator over all the autoloadable symbols in this package.
156 *
157 * @return an iterator over all the autoloadable symbols in this package.
158 */
159 public MapIterator< String, Loader > autoloadablesIterator() {
160 return autoloadableSymbols.iterator( true );
161 }
162
163 /**
164 * Makes a binding in this package from the specified symbol to the
165 * specified object.
166 *
167 * @param sym the symbol to bind the object to.
168 * @param obj the object to bind to the specified symbol.
169 * @return the GlobalIdent for the new global variable
170 */
171 public GlobalIdent bind( final String sym, final Object obj ) {
172
173
174
175 Ref ref = null;
176 if ( obj == null ) {
177 ref = new Ref( obj );
178 } else if ( obj instanceof Function ) {
179 Function fn = (Function)obj;
180 if ( fn.getName() == null ) {
181 fn.modName( sym );
182 }
183 ref = new Ref( fn );
184 } else if ( obj instanceof Ref ) {
185 ref = (Ref)obj;
186 } else {
187 ref = new Ref( obj );
188 }
189 return this.bindRef( sym, ref );
190 }
191
192 /**
193 * Makes a binding in this package from the specified symbol to the
194 * specified object. The bound symbol is a const.
195 *
196 * @param sym the symbol to bind the object to.
197 * @param obj the object to bind to the specified symbol.
198 */
199 public void bindConst( final String sym, final Object obj ) {
200 final GlobalIdent gid = this.bind( sym, obj );
201 gid.setIsProtected( false );
202 gid.setIsConst( true );
203 }
204
205 /**
206 * Makes a binding in this package from the specified symbol to the
207 * specified object. The bound symbol is a variable.
208 *
209 * @param sym the symbol to bind the object to.
210 * @param obj the object to bind to the specified symbol.
211 */
212 public void bindVar( final String sym, final Object obj ) {
213 final GlobalIdent gid = this.bind( sym, obj);
214 gid.setIsProtected( false );
215 gid.setIsConst( false );
216 }
217
218 /**
219 * Makes a binding in this package from the specified symbol to the
220 * specified object. The bound symbol is a const.
221 *
222 * @param sym the symbol to bind the object to.
223 * @param obj the object to bind to the specified symbol.
224 */
225 public void bindProtectedConst( final String sym, final Object obj ) {
226 final GlobalIdent gid = this.bind( sym, obj );
227 gid.setIsProtected( true );
228 gid.setIsConst( true );
229 }
230
231 /**
232 * Makes a binding in this package from the specified symbol to the
233 * specified object. The bound symbol is a variable.
234 *
235 * @param sym the symbol to bind the object to.
236 * @param obj the object to bind to the specified symbol.
237 */
238 public void bindProtectedVar( final String sym, final Object obj ) {
239 final GlobalIdent gid = this.bind( sym, obj );
240 gid.setIsProtected( true );
241 gid.setIsConst( false );
242 }
243
244 /**
245 * Makes a binding in this package from the specified symbol to the
246 * specifed reference.
247 *
248 * @param sym the symbol to bind the reference to.
249 * @param ref the reference to bind to the specified symbol.
250 * @return the GlobalIdent for the new global variable
251 */
252 protected GlobalIdent bindRef( final String sym, final Ref ref ) {
253 final String symbol = sym.intern();
254 final GlobalIdent gid = this.declareGlobal( symbol );
255 gid.getRef().value = ref.value;
256 return gid;
257 }
258
259 /**
260 * Returns an iterator over all the bindings in this package.
261 *
262 * @return an iterator over all the bindings in this package.
263 */
264 public MapIterator< String, GlobalIdent > bindingsIterator() {
265 return globalSymbols.iterator( true );
266 }
267
268 /**
269 * Declares the specified symbol as a global symbol. The symbol will be
270 * bound to a <code>null</code> reference. This method is
271 *
272 * @param sym the symbol to declare as a global variable
273 * @return the GlobalIdent for the new global variable
274 */
275 public GlobalIdent declareGlobal( final String sym ) {
276
277 GlobalIdent id = globalSymbols.get( sym );
278 if ( id == null ) {
279 final Package origin = this.hasPervasiveImport( sym );
280 if ( origin != null ) {
281 throw(
282 Alerts.compile(
283 "This variable is imported and cannot be shadowed",
284 "It is imported from a pervasive package (e.g. the standard package)"
285 ).
286 culprit( "variable", sym ).
287 culprit( "origin", origin.fullyQualifiedName ).
288 mishap()
289 );
290 } else {
291 GlobalIdent gid = new GlobalIdent( sym, new Ref() );
292 globalSymbols.insert( sym, gid );
293
294
295 return gid;
296 }
297 } else {
298 this.config.reportAlertAsWarning(
299 Alerts.compile(
300 "This variable is already declared",
301 null
302 ).culprit( "variable", sym )
303 );
304 return id;
305 }
306 }
307
308 /**
309 * Returns the ident for the specified global symbol. The package which the
310 * symbol was imported with can be specified by the optional nickname.
311 *
312 * @param nn the nickname of the package the request symbol was imported
313 * with
314 * @param sym the symbol you wish to find.
315 * @return the Ident for the specified symbol.
316 */
317 public GlobalIdent findIdentFor( final String nn, final String sym ) {
318
319 HashSet< String > foundIdents = new HashSet< String >();
320 if ( nn == null ) {
321 GlobalIdent id = this.loadIdent( sym );
322 if ( id == null ) {
323
324
325
326
327 MapIterator< String, Package > it = imports.iterator( true );
328 while ( it.hasNext() ) {
329 String nick = it.nextKey();
330 Package p = it.currentValue();
331 GlobalIdent gid = p.loadIdent( sym );
332 if ( gid != null ) {
333 foundIdents.add( nick );
334 id = gid;
335 }
336 }
337 if ( foundIdents.size() > 1 ) {
338
339
340 Alert alert = Alerts.compile(
341 "This global name is ambiguous",
342 "The following imported packages publish that name"
343 ).culprit( "name", sym );
344
345 Iterator< String > pkgIt = foundIdents.iterator();
346 int pkgNum = 0;
347 while ( pkgIt.hasNext() ) {
348 String me = pkgIt.next();
349 alert = alert.culprit( "package " + Integer.toString( pkgNum ) , me );
350 }
351
352 throw alert.mishap();
353 }
354 }
355
356 if ( id == null ) {
357 throw(
358 Alerts.compile(
359 "Could not find the definition for this variable",
360 null
361 ).culprit( "name", sym ).mishap()
362 );
363 }
364
365 return id;
366 } else {
367
368 Package p = imports.get( nn );
369
370 if ( p == null ) {
371 throw(
372 Alerts.compile(
373 "Cannot access this package",
374 "Perhaps this package has not been imported?"
375 ).culprit( "nickname", nn ).mishap()
376 );
377 }
378
379 GlobalIdent id = p.loadIdent( sym );
380
381 if ( id == null ) {
382 throw(
383 Alerts.compile(
384 "Could not find the definition for this variable",
385 null
386 ).culprit( "nickname", nn ).culprit( "name", sym ).mishap()
387 );
388 }
389
390 return id;
391 }
392 }
393
394 /**
395 * Returns a new compiler state for this package.
396 *
397 * @return a new compiler state for this package.
398 */
399 public CompilerState getCompilerState() {
400 final CompilerState cs = new CompilerState( engine );
401 cs.changePackage( this );
402 return cs;
403 }
404
405 /**
406 * Returns this packages configuration.
407 *
408 * @return this packages configuration
409 */
410 public Configuration getConfig() {
411 return config;
412 }
413
414 /**
415 * Returns this packages fully qualified name.
416 *
417 * @return this packages fully qualified name
418 */
419 public String getFullyQualifiedPackageName() {
420 return fullyQualifiedName;
421 }
422
423 /**
424 * Returns <code>true</code> if the specified symbol is protected.
425 *
426 * @param sym the symbol to test protected status for
427 * @return <code>true</code> if the symbol is protected.
428 */
429 public boolean getIsProtected( final String sym ) {
430
431
432
433
434 GlobalIdent id = globalSymbols.get( sym );
435 return id != null && id.getIsProtected();
436 }
437
438 /**
439 * Returns true if the supplied symbol has a binding in this package.
440 *
441 * @param sym the symbol to test for
442 * @return <code>true</code> if the symbol has a binding and
443 * <code>false</code> otherwise.
444 * @todo Should we also search all public imports.
445 */
446 public boolean hasBindingFor( final String sym ) {
447
448
449 return globalSymbols.get( sym ) != null;
450 }
451
452 /**
453 * Looks to see if the specified symbol is already imported in a pervasive
454 * package.
455 *
456 * @param sym the symbol to look for.
457 * @return the pervasive Package defining the symbol or <code>null</code>
458 */
459 public final Package hasPervasiveImport( final String sym ) {
460 final MapIterator< String, Package > it = this.imports.iterator( true );
461 while ( it.hasNext() ) {
462 Package p = it.nextValue();
463 if ( p.isPervasive ) {
464
465
466 if (
467 p.hasBindingFor( sym ) ||
468 p.autoloadableSymbols.get( sym ) != null
469 ) {
470 return p;
471 }
472 }
473 }
474 return null;
475 }
476
477 /**
478 * Imports the specified fully qualified package into this package, with
479 * the given nickname.
480 *
481 * @param nn the nickname for the imported package
482 * @param fqn the fully qualified name of the package to import.
483 */
484 public void importPackage( final String nn, final String fqn ) {
485 this.importPackage( nn, engine.getPackage( fqn ) );
486 }
487
488 /**
489 * Imports the specified package object into this package, with the given
490 * nickname.
491 *
492 * @param nn the nickname for the imported package
493 * @param p the package object to import.
494 */
495 public void importPackage( final String nn, final Package p ) {
496 imports.insert( nn, p );
497 }
498
499 /**
500 * Interprets the bytes returned from reading the specified file as UTF-8
501 * text, into this package.
502 *
503 * @param f the File to read from.
504 */
505 public void interpret( final VFile f ) {
506 this.interpret( f.toString(), f.getReader() );
507 }
508
509 /**
510 * Interprets the characters returned by the specified reader into this
511 * package. This may result in new variables being declared in this, or
512 * other packages.
513 *
514 * @param origin the origin message for the reader
515 * @param r the reader to interpret characters from.
516 */
517 public void interpret( final String origin, final Reader r ) {
518
519 Parser parser = new ParserImpl( origin, r, true, config );
520
521
522 this.getCompilerState().compileNoResults( parser );
523 }
524
525 /**
526 * Interprets the contents of the specified string into this package. This
527 * may result in new variables being declared in this, or other packages.
528 *
529 * @param origin the origin message for the reader
530 * @param s the string to interpret.
531 */
532 public void interpret( final String origin, final String s ) {
533 this.interpret( origin, new StringReader( s ) );
534 }
535
536 /**
537 * Loads the specified configuration for this package.
538 *
539 * @param origin the origin message for the configuration
540 * @param r a reader to read the configuration from
541 */
542 public void loadConf( final String origin, final Reader r ) {
543
544 final Parser confParser = new ConfigurationParserImpl(
545 origin,
546 r,
547 true,
548 this.config
549 );
550
551 final CompilerState state = new CompilerState( this.engine );
552
553
554
555
556 state.changePackage( new ConfigurationPackage( this.engine, this, "millscript.configuration" ) );
557 try {
558 while ( confParser.peekToken() != TokenType.EOF ) {
559 state.compileExpr( confParser.readExpr() );
560 if ( this.engine.getMachine().getCount() > 0 ) {
561 throw(
562 Alerts.eval(
563 "Package configuration trying to return results",
564 "Results can only be returned at top level"
565 ).origin( confParser ).mishap()
566 );
567 }
568 }
569 } catch ( Alert ex ) {
570
571
572
573
574
575 throw ex.origin( confParser ).escape();
576 } catch ( EscapeException ex ) {
577
578 throw ex;
579 } catch ( Exception ex ) {
580
581 throw(
582 Alerts.parse(
583 "Cannot parse configuration file",
584 null
585 ).culprit( "file", origin ).culprit( "reason", ex.getMessage() ).setParentThrowable( ex ).mishap()
586 );
587 }
588 }
589
590 /**
591 * Loads the specified configuration for this package.
592 *
593 * @param packageConf a File specifying the configuration file for this
594 * package.
595 */
596 public void loadConf( final VFile packageConf ) {
597
598 if ( packageConf.exists() ) {
599 this.loadConf( packageConf.toString(), packageConf.getReader() );
600 }
601 }
602
603 /**
604 * Loads the specified configuration for this package.
605 *
606 * @param source a Source to read configuration from
607 */
608 public void loadConf( final Source source ) {
609 this.loadConf( source.getOrigin(), source.getReader() );
610 }
611
612 /**
613 * Returns the global ident for the specified symbol, attempting to
614 * autoload it if available.
615 *
616 * @param sym the symbol to get the ident for
617 * @return the Globaldent for the specified symbol
618 */
619 private GlobalIdent loadIdent( final String sym ) {
620 GlobalIdent id = globalSymbols.get( sym );
621 if ( id != null ) {
622 return id;
623 }
624 this.autoload( sym );
625 return globalSymbols.get( sym );
626 }
627
628 /**
629 * Returns the relevant loader for the specified virtual entry, if one is
630 * available.
631 *
632 * @param e the VEntry to make a loader for.
633 * @return a loader for the specified file or <code>null</code>
634 */
635 public Loader makeLoaderFor( final VEntry e ) {
636 return this.config.getLoaderBuilder().getLoaderFor( this, e );
637 }
638
639 /**
640 * Returns the reference associated with the specified symbol.
641 *
642 * @param sym the symbol to find a value for.
643 * @return the reference associated with the specified symbol.
644 * @todo Should we also search all public imports.
645 */
646 public Object valueOf( final String sym ) {
647
648
649 final GlobalIdent id = globalSymbols.get( sym );
650 if ( id != null ) {
651 return id.getValue();
652 } else {
653 throw(
654 Alerts.compile(
655 "Could not find the definition for this variable",
656 null
657 ).culprit( "name", sym ).mishap()
658 );
659 }
660 }
661
662 }